requestVideoFrameCallback() ile videoda video karesi başına verimli işlemler gerçekleştirin

Tarayıcıda videolarla daha verimli çalışmak için requestVideoFrameCallback() uygulamasını nasıl kullanacağınızı öğrenin.

HTMLVideoElement.requestVideoFrameCallback() yöntemi, web yazarlarının, yeni bir video karesi kompozöre gönderildiğinde oluşturma adımlarında çalışan bir geri çağırma işlevi kaydetmesine olanak tanır. Bu sayede geliştiriciler videoda video işleme ve tuvale boyama, video analizi veya harici ses kaynaklarıyla senkronizasyon gibi video karesi başına verimli işlemler gerçekleştirebilir.

requestAnimationFrame() ile fark

Bu API üzerinden yapılan drawImage() kullanarak bir kanvas üzerine video karesi çizme gibi işlemler, ekranda oynatılan videonun kare hızıyla mümkün olduğunca senkronize edilir. Genellikle saniyede yaklaşık 60 kez tetiklenen window.requestAnimationFrame()'ten farklı olarak requestVideoFrameCallback(), önemli bir istisna dışında gerçek video kare hızına bağlıdır:

Geri çağırmaların çalıştırıldığı etkili hız, videonun hızı ile tarayıcı hızının arasındaki daha düşük hızdır. Bu, 60 Hz'de boyama yapan bir tarayıcıda oynatılan 25 fps'lik bir videonun geri çağırma işlevlerini 25 Hz'de tetikleyeceği anlamına gelir. Aynı 60 Hz tarayıcıda 120 fps video, geri çağırma işlevlerini 60 Hz'de tetikler.

Adın önemi var mı?

window.requestAnimationFrame() ile benzerliği nedeniyle, yöntem başlangıçta video.requestAnimationFrame() olarak önerildi ve requestVideoFrameCallback() olarak yeniden adlandırıldı. Yöntem, uzun bir tartışmadan sonra kararlaştırıldı.

Özellik algılama

if ('requestVideoFrameCallback' in HTMLVideoElement.prototype) {
  // The API is supported!
}

Tarayıcı desteği

Tarayıcı desteği

  • Chrome: 83.
  • Kenar: 83.
  • Firefox: 132.
  • Safari: 15.4.

Kaynak

Çoklu dolgu

Window.requestAnimationFrame() ve HTMLVideoElement.getVideoPlaybackQuality() tabanlı bir requestVideoFrameCallback() yöntemi için polyfill kullanılabilir. Bu özelliği kullanmadan önce README bölümünde belirtilen sınırlamalara dikkat edin.

requestVideoFrameCallback() yöntemini kullanma

requestAnimationFrame() yöntemini daha önce kullandıysanız requestVideoFrameCallback() yöntemine hemen alışırsınız. İlk geri aramayı bir kez kaydedersiniz ve geri arama tetiklendiğinde yeniden kaydedersiniz.

const doSomethingWithTheFrame = (now, metadata) => {
  // Do something with the frame.
  console.log(now, metadata);
  // Re-register the callback to be notified about the next frame.
  video.requestVideoFrameCallback(doSomethingWithTheFrame);
};
// Initially register the callback to be notified about the first frame.
video.requestVideoFrameCallback(doSomethingWithTheFrame);

Geri çağırma işlevinde now bir DOMHighResTimeStamp, metadata ise aşağıdaki özelliklere sahip bir VideoFrameMetadata sözlüğüdür:

  • DOMHighResTimeStamp türündeki presentationTime: Kullanıcı aracısının, çerçeveyi oluşturma için gönderdiği zaman.
  • DOMHighResTimeStamp türündeki expectedDisplayTime: Kullanıcı aracısının çerçevenin görünür olmasını beklediği zaman.
  • width, unsigned long türü: Video çerçevesinin genişliği (medya piksellerinde).
  • height, unsigned long türü: Video çerçevesinin yüksekliği (medya pikseli cinsinden).
  • mediaTime, double türü: Sunulan karenin saniye cinsinden medya sunma zaman damgası (PTS) (ör. video.currentTime zaman çizelgesindeki zaman damgası).
  • presentedFrames, unsigned long türü: Oluşturma için gönderilen kare sayısının sayısı. İstemcilerin, VideoFrameRequestCallback örnekleri arasında kare atlanıp atlanmadığını belirlemesine olanak tanır.
  • double türündeki processingDuration: Kodlanmış paketin, bu kareyle aynı sunma zaman damgasına (PTS) sahip olarak (ör. mediaTime ile aynı) kod çözücüye gönderilmesinden, kod çözülmüş karenin sunulmaya hazır hale gelmesine kadar geçen süre (saniye cinsinden).

WebRTC uygulamaları için ek özellikler görünebilir:

  • captureTime, DOMHighResTimeStamp türü: Yerel veya uzak bir kaynaktan gelen video kareleri için bu, karenin kamera tarafından yakalandığı zamandır. Uzak bir kaynak için yakalama zamanı, RTP zaman damgalarını yakalama zamanına dönüştürmek üzere saat senkronizasyonu ve RTCP gönderen raporları kullanılarak tahmin edilir.
  • receiveTime, DOMHighResTimeStamp türünde: Uzak bir kaynaktan gelen video kareleri için kodlanmış karenin platform tarafından alındığı zamanı, yani bu kareye ait son paketin ağ üzerinden alındığı zamanı gösterir.
  • rtpTimestamp, unsigned long türü: Bu video karesiyle ilişkili RTP zaman damgası.

Bu liste özellikle mediaTime ilgilendiriyor. Chromium uygulaması, video.currentTime öğesini destekleyen zaman kaynağı olarak ses saatini kullanırken mediaTime öğesi doğrudan karenin presentationTimestamp öğesi tarafından doldurulur. Kaçırdığınız kareleri tam olarak belirlemek de dahil olmak üzere, kareleri tekrarlanabilir bir şekilde tam olarak tanımlamak istiyorsanız mediaTime kullanmanız gerekir.

Her şey bir kare uzak gibi görünüyorsa...

Dikey senkronizasyon (veya kısaca vsync), bir videonun kare hızını ve monitörün yenileme hızını senkronize eden bir grafik teknolojisidir. requestVideoFrameCallback() ana iş parçacığında çalışır ancak video oluşturma işlemi, arka planda oluşturucu iş parçacığında gerçekleşir. Bu nedenle, bu API'deki her şey olabildiğince yapılır ve tarayıcı herhangi bir kesin garanti sunmaz. API, bir video çerçevesinin oluşturulmasına kıyasla bir vsync geç olabilir. API aracılığıyla web sayfasında yapılan değişikliklerin ekranda görünmesi için bir vsync gerekir (window.requestAnimationFrame() ile aynıdır). Dolayısıyla, web sayfanızdaki mediaTime veya kare numarasını güncellemeye devam ederseniz ve bunu numaralı video kareleriyle karşılaştırırsanız video bir kare ilerideymiş gibi görünür.

Gerçekte olan şey, karenin vsync x'te hazır olması, geri çağırma işlevinin tetiklenmesi ve karenin vsync x+1'de oluşturulması, geri çağırma işlevinde yapılan değişikliklerin ise vsync x+2'de oluşturulmasıdır. metadata.expectedDisplayTime değerinin yaklaşık olarak now değerine eşit olup olmadığını veya bir vsync sonra olup olmadığını kontrol ederek geri çağırma işlevinin vsync gecikmeli olup olmadığını (ve karenin ekranda zaten oluşturulup oluşturulmadığını) kontrol edebilirsiniz. now ile yaklaşık beş ila on mikrosaniye arasındaysa kare zaten oluşturulmuştur; expectedDisplayTime yaklaşık on altı milisaniye sonraysa (tarayıcınızın/ekranınızın 60 Hz'de yenilendiği varsayılırsa) kareyle senkronize olursunuz.

Demo

Tam olarak videonun kare hızında bir kanvas üzerinde karelerin nasıl çizildiğini ve hata ayıklama amacıyla kare meta verilerinin nereye kaydedildiğini gösteren küçük bir Glitch demosu oluşturdum.

let paintCount = 0;
let startTime = 0.0;

const updateCanvas = (now, metadata) => {
  if (startTime === 0.0) {
    startTime = now;
  }

  ctx.drawImage(video, 0, 0, canvas.width, canvas.height);

  const elapsed = (now - startTime) / 1000.0;
  const fps = (++paintCount / elapsed).toFixed(3);
  fpsInfo.innerText = `video fps: ${fps}`;
  metadataInfo.innerText = JSON.stringify(metadata, null, 2);

  video.requestVideoFrameCallback(updateCanvas);
};

video.requestVideoFrameCallback(updateCanvas);

Sonuçlar

Kullanıcılar uzun süredir kare düzeyinde işlem yapıyor. Bunun için gerçek karelere erişmek yerine yalnızca video.currentTime'e dayanıyor. requestVideoFrameCallback() yöntemi, bu geçici çözümü büyük ölçüde iyileştirir.

Teşekkür ederiz

requestVideoFrameCallback API'si Thomas Guilbert tarafından tanımlanıp uygulanmıştır. Bu yayın Joe Medley ve Kayce Basques tarafından incelendi. Unsplash'taki Denise Jans tarafından oluşturulan lokomotif resim.