WebCodec ile video işleme

Video akışı bileşenlerini değiştirme.

Eugene Zemtsov
Eugene Zemtsov
François Beaufort
François Beaufort

Modern web teknolojileri, videolarla çalışmak için bol miktarda yol sunar. Media Stream API, Medya Kaydı API'si Medya Kaynağı API'si ve WebRTC API'lerin toplamı video akışlarını kaydetmek, aktarmak ve oynatmak için zengin bir araç setine dönüştürüyor. Bu API'ler, belirli üst düzey görevleri çözerken web'in Programcılar video akışının farklı bileşenleriyle (ör. kareler) kodlanmamış video veya ses parçalarından oluşur. Geliştiriciler bu temel bileşenlere alt düzey erişim elde etmek için uzun bir süredir WebAssembly ile video ve ses codec'lerini tarayıcıya ekleyin. Ama verilen modern tarayıcıların zaten çeşitli codec'ler (genellikle donanımla hızlandırılıyorsa), bunları WebAssembly olarak yeniden paketlemek ise insan ve bilgisayar kaynakları.

WebCodecs API bu verimsizliği ortadan kaldırır programcılara halihazırda mevcut medya bileşenlerini kullanmalarını sağlayarak tarayıcı. Özellikle:

  • Video ve ses kod çözücüleri
  • Video ve ses kodlayıcıları
  • Ham video kareleri
  • Resim kod çözücüler

WebCodecs API, tüm siteler üzerinde tam denetim gerektiren web video düzenleyiciler, video konferans, video konferans gibi medya içeriğinin işlenme şekli akış vb.

Video işleme iş akışı

Kareler, video işleme sürecinin en önemli unsurudur. Dolayısıyla, WebCodecs'te çoğu ya da kare tüketir. Video kodlayıcılar, kareleri kodlanmış biçime dönüştürür parçalar. Video kod çözücüler ise tam tersini yapar.

Ayrıca VideoFrame, bir CanvasImageSource olarak ve CanvasImageSource kabul eden bir oluşturucu sayesinde diğer Web API'leriyle uyumlu şekilde çalışır. Dolayısıyla, drawImage() ve texImage2D() gibi işlevlerde kullanılabilir. Ayrıca tuvallerden, bit eşlemlerden, video öğelerinden ve diğer video karelerinden de oluşturulabilir.

WebCodecs API, Insertable Streams API'deki sınıflarla birlikte iyi bir şekilde çalışır WebCodecs'i medya akış kanallarına bağlayan

  • MediaStreamTrackProcessor, medya kanallarını bağımsız karelere ayırır.
  • MediaStreamTrackGenerator, bir kare akışından medya kanalı oluşturur.

WebCodecs ve web çalışanları

WebCodecs API, tasarımı gereği tüm ağır işleri eşzamansız olarak ve ana iş parçacığı dışında yapar. Ancak çerçeve ve parça geri çağırmaları genellikle saniyede birden fazla kez çağrılabildiğinden, ana ileti dizisini karmaşık hale getirip web sitesini daha az duyarlı hale getirebilirler. Dolayısıyla, tek tek karelerin ve kodlanmış parçaların işlenmesinin tek bir kareye taşınması tercih edilir. yardımcı olur.

Bu konuda yardımcı olmak için ReadableStream bir medya kaynağından gelen tüm kareleri otomatik olarak aktarmak için uygun bir yol sunar. takip etmeniz gerekir. Örneğin MediaStreamTrackProcessor, Web kamerasından gelen medya akışı parçası için ReadableStream. Ondan sonra Akış, karelerin tek tek okunduğu ve sıraya alındığı bir web çalışanına aktarılır VideoEncoder dönüştürüldü.

HTMLCanvasElement.transferControlToOffscreen ile ana iş parçacığından eşit oluşturma işlemi de yapılabilir. Ancak tüm üst düzey araçlar rahatsızlık verdiğinden, VideoFrame alanının kendisi aktarılabilir ve nasıl yürütüleceğini planlayabilirsiniz.

WebCodecs'in işleyiş şekli

Kodlama

Bir Canvas veya ImageBitmap'ten ağ veya depolama alanına giden yol
Canvas veya ImageBitmap cihazından ağa veya depolama alanına giden yol

Her şey bir VideoFrame ile başlar. Video kareleri oluşturmanın üç yolu vardır.

  • Tuval, resim bit eşlemi veya video öğesi gibi bir resim kaynağından.

    const canvas = document.createElement("canvas");
    // Draw something on the canvas...
    
    const frameFromCanvas = new VideoFrame(canvas, { timestamp: 0 });
    
  • MediaStreamTrack öğesinden kare almak için MediaStreamTrackProcessor işlemini kullanın

    const stream = await navigator.mediaDevices.getUserMedia({});
    const track = stream.getTracks()[0];
    
    const trackProcessor = new MediaStreamTrackProcessor(track);
    
    const reader = trackProcessor.readable.getReader();
    while (true) {
      const result = await reader.read();
      if (result.done) break;
      const frameFromCamera = result.value;
    }
    
  • BufferSource'da ikili piksel gösteriminden bir kare oluşturma

    const pixelSize = 4;
    const init = {
      timestamp: 0,
      codedWidth: 320,
      codedHeight: 200,
      format: "RGBA",
    };
    const data = new Uint8Array(init.codedWidth * init.codedHeight * pixelSize);
    for (let x = 0; x < init.codedWidth; x++) {
      for (let y = 0; y < init.codedHeight; y++) {
        const offset = (y * init.codedWidth + x) * pixelSize;
        data[offset] = 0x7f;      // Red
        data[offset + 1] = 0xff;  // Green
        data[offset + 2] = 0xd4;  // Blue
        data[offset + 3] = 0x0ff; // Alpha
      }
    }
    const frame = new VideoFrame(data, init);
    

Nereden gelirlerse görsün, kareler otomatik olarak VideoEncoder içeren EncodedVideoChunk nesne.

Kodlamadan önce VideoEncoder öğesine iki JavaScript nesnesi verilmesi gerekir:

  • Kodlanmış parçaları işlemek için iki işleve ve hatalar. Bu işlevler geliştirici tarafından tanımlanır ve daha sonra değiştirilemez VideoEncoder oluşturucuya geçirilirler.
  • Çıkışla ilgili parametreleri içeren kodlayıcı yapılandırma nesnesi video akışını izleyin. Bu parametreleri daha sonra configure() çağrısı yaparak değiştirebilirsiniz.

Yapılandırma uygun değilse configure() yöntemi NotSupportedError değerini atar tarayıcı tarafından destekleniyor. Statik yöntemi çağırmanız önerilir. yapılandırma ile VideoEncoder.isConfigSupported() uyumlu olup olmadığını önceden kontrol etmek için destekleniyorsa yapılandırmanın size ulaşmasını bekleyin.

const init = {
  output: handleChunk,
  error: (e) => {
    console.log(e.message);
  },
};

const config = {
  codec: "vp8",
  width: 640,
  height: 480,
  bitrate: 2_000_000, // 2 Mbps
  framerate: 30,
};

const { supported } = await VideoEncoder.isConfigSupported(config);
if (supported) {
  const encoder = new VideoEncoder(init);
  encoder.configure(config);
} else {
  // Try another config.
}

Kodlayıcı ayarlandıktan sonra encode() yöntemiyle kareleri kabul edebilir. Hem configure() hem de encode(), beklemeden hemen geri döner. gerçek işin tamamlanma tarihidir. Birkaç karenin aynı karede kodlama için sıraya girmesine olanak aynı anda, encodeQueueSize ise sırada bekleyen kaç istek olduğunu gösterir son kodlamaya göre test edin. Hatalar, bağımsız değişkenlerin sıfırlanması veya veya yöntem çağrılarının sırası API sözleşmesini ihlal ediyor ya da error() codec uygulamasında karşılaşılan sorunlar için geri çağırma olanağı sunar. Kodlama başarıyla tamamlanırsa output() geri çağırma, bağımsız değişken olarak yeni bir kodlanmış parçayla çağrılır. Buradaki önemli bir ayrıntı da, çerçevelerin daha uzun süre gerekiyor. close()

let frameCounter = 0;

const track = stream.getVideoTracks()[0];
const trackProcessor = new MediaStreamTrackProcessor(track);

const reader = trackProcessor.readable.getReader();
while (true) {
  const result = await reader.read();
  if (result.done) break;

  const frame = result.value;
  if (encoder.encodeQueueSize > 2) {
    // Too many frames in flight, encoder is overwhelmed
    // let's drop this frame.
    frame.close();
  } else {
    frameCounter++;
    const keyFrame = frameCounter % 150 == 0;
    encoder.encode(frame, { keyFrame });
    frame.close();
  }
}

Son olarak, kodu işleyen bir fonksiyon yazarak parçaları kodlayıcıdan çıktıktan sonra gösterir. Genellikle bu işlev, veri parçalarını ağ üzerinden gönderir veya mux bir medyaya gönderir bir depolama konteyneri.

function handleChunk(chunk, metadata) {
  if (metadata.decoderConfig) {
    // Decoder needs to be configured (or reconfigured) with new parameters
    // when metadata has a new decoderConfig.
    // Usually it happens in the beginning or when the encoder has a new
    // codec specific binary configuration. (VideoDecoderConfig.description).
    fetch("/upload_extra_data", {
      method: "POST",
      headers: { "Content-Type": "application/octet-stream" },
      body: metadata.decoderConfig.description,
    });
  }

  // actual bytes of encoded data
  const chunkData = new Uint8Array(chunk.byteLength);
  chunk.copyTo(chunkData);

  fetch(`/upload_chunk?timestamp=${chunk.timestamp}&type=${chunk.type}`, {
    method: "POST",
    headers: { "Content-Type": "application/octet-stream" },
    body: chunkData,
  });
}

Herhangi bir noktada, bekleyen tüm kodlama isteklerinde tamamlandıysa flush() numaralı telefonu arayıp söz vermesini bekleyebilirsiniz.

await encoder.flush();

Kod çözme

Ağdan veya depolama alanından bir Canvas veya ImageBitmap&#39;e giden yol.
Ağdan veya depolama alanından bir Canvas ya da ImageBitmap öğesine giden yol.

VideoDecoder kurulumu, VideoEncoder: Kod çözücü oluşturulduğunda iki işlev aktarılır ve codec parametreleri configure() değerine verilir.

Codec parametreleri grubu, codec'ten codec'e değişir. Örneğin, H.264 codec'i ikili blob gerekebilir Ek B biçiminde (encoderConfig.avc = { format: "annexb" }) kodlanmadığı sürece

const init = {
  output: handleFrame,
  error: (e) => {
    console.log(e.message);
  },
};

const config = {
  codec: "vp8",
  codedWidth: 640,
  codedHeight: 480,
};

const { supported } = await VideoDecoder.isConfigSupported(config);
if (supported) {
  const decoder = new VideoDecoder(init);
  decoder.configure(config);
} else {
  // Try another config.
}

Kod çözücü başlatıldıktan sonra EncodedVideoChunk nesneyle beslemeye başlayabilirsiniz. Yığın oluşturmak için gerekenler:

  • Kodlanmış video verilerinin BufferSource değeri
  • parçanın mikrosaniye cinsinden başlangıç zaman damgası (parçadaki ilk kodlanmış karenin medya zamanı)
  • şunlardan biri:
    • Parçanın kodu önceki parçalardan bağımsız olarak çözülebiliyorsa key
    • Parçanın kodu yalnızca bir veya daha fazla önceki parçanın kodu çözüldükten sonra çözülebiliyorsa delta

Ayrıca kodlayıcı tarafından yayınlanan parçalar, kod çözücü için olduğu gibi hazırdır. Hata raporlama ve eşzamansız doğa hakkında yukarıda söylenenlerin tümü yöntem, kod çözücüler için de aynı şekilde geçerlidir.

const responses = await downloadVideoChunksFromServer(timestamp);
for (let i = 0; i < responses.length; i++) {
  const chunk = new EncodedVideoChunk({
    timestamp: responses[i].timestamp,
    type: responses[i].key ? "key" : "delta",
    data: new Uint8Array(responses[i].body),
  });
  decoder.decode(chunk);
}
await decoder.flush();

Şimdi sıra kodu çözülmüş yeni bir karenin sayfada nasıl gösterilebileceğini göstermeye geldi. İnsanların kod çözücü çıkışı geri çağırmasının (handleFrame()) sağlandığından emin olun geri dönüyor. Aşağıdaki örnekte, yalnızca kareler oluşturulmaya hazır. Oluşturma işlemi ayrı olarak gerçekleşir ve iki adımdan oluşur:

  1. Karenin gösterilmesi için doğru zaman bekleniyor.
  2. Çerçeve tuvale çiziliyor.

Bir kareye artık ihtiyaç kalmadığında temel belleği serbest bırakmak için close() çağrısı yapın daha kendisine ulaşmadan önce depolama alanının web uygulaması tarafından kullanılan bellek.

const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
let pendingFrames = [];
let underflow = true;
let baseTime = 0;

function handleFrame(frame) {
  pendingFrames.push(frame);
  if (underflow) setTimeout(renderFrame, 0);
}

function calculateTimeUntilNextFrame(timestamp) {
  if (baseTime == 0) baseTime = performance.now();
  let mediaTime = performance.now() - baseTime;
  return Math.max(0, timestamp / 1000 - mediaTime);
}

async function renderFrame() {
  underflow = pendingFrames.length == 0;
  if (underflow) return;

  const frame = pendingFrames.shift();

  // Based on the frame's timestamp calculate how much of real time waiting
  // is needed before showing the next frame.
  const timeUntilNextFrame = calculateTimeUntilNextFrame(frame.timestamp);
  await new Promise((r) => {
    setTimeout(r, timeUntilNextFrame);
  });
  ctx.drawImage(frame, 0, 0);
  frame.close();

  // Immediately schedule rendering of the next frame
  setTimeout(renderFrame, 0);
}

Geliştiriciler İpuçları

Medya Paneli'ni kullanın Chrome Geliştirici Araçları'nı kullanabilirsiniz.

WebCodec&#39;lerde hata ayıklamaya ilişkin Medya Paneli ekran görüntüsü
WebCodec'lerde hata ayıklama için Chrome Geliştirici Araçları'ndaki Medya Paneli.

Demo

Aşağıdaki demoda, tuvaldeki animasyon karelerinin nasıl olduğu gösterilmektedir:

  • MediaStreamTrackProcessor tarafından ReadableStream video ile 25 fps'de yakalandı
  • bir web çalışanına aktarıldı
  • H.264 video biçiminde kodlanmış
  • kodu bir dizi video karesine dönüştürüldü
  • ve transferControlToOffscreen() kullanılarak ikinci tuvalde oluşturulur

Diğer demolar

Diğer demolarımıza da göz atın:

WebCodecs API'yi kullanma

Özellik algılama

WebCodecs desteğini kontrol etmek için:

if ('VideoEncoder' in window) {
  // WebCodecs API is supported.
}

WebCodecs API'nin yalnızca güvenli bağlamlarda kullanılabildiğini unutmayın. dolayısıyla self.isSecureContext yanlış değerine ayarlanırsa algılama başarısız olur.

Geri bildirim

Chrome ekibi, WebCodecs API ile ilgili deneyimlerinizi öğrenmek istiyor.

Bize API tasarımı hakkında bilgi verin

API'de beklediğiniz gibi çalışmayan bir şey mi var? Veya fikrinizi uygulamak için gereken yöntemler veya özellikler var mı? Bir hakkında soru sormak veya yorum yapmak mı istiyorsunuz? Spesifikasyon sorununu GitHub kod deposu'na gidin veya aklınızdan çıkarabilirsiniz.

Uygulamayla ilgili bir sorunu bildirin

Chrome'un uygulanmasıyla ilgili bir hata buldunuz mu? Yoksa ve spesifikasyondan farklı mı? new.crbug.com adresinden hata bildiriminde bulunun. Açıklamanıza mümkün olduğunca fazla ayrıntı, basit talimatlar oluşturup Bileşenler kutusuna Blink>Media>WebCodecs yazın. Glitch, hızlı ve kolay yeniden oluşturmalar paylaşmak için idealdir.

API'ye desteğinizi gösterin

WebCodecs API'yi kullanmayı planlıyor musunuz? Herkese açık desteğiniz, Chrome ekibinin özellikleri önceliklendirmesi ve diğer tarayıcı tedarikçilerine asıl amaç onları desteklemek.

media-dev@chromium.org adresine e-posta veya tweet gönderin @ChromiumDev hashtag'ini kullanarak #WebCodecs ve nerede ve nasıl kullandığınızı bize bildirin.

Hero resim: Deniz Can Unsplash'i açın.