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

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

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

requestAnimationFrame() ile arasındaki fark

Bu API aracılığıyla yapılan drawImage() kullanılarak bir video karesini tuvale çizme gibi işlemler, ekranda oynatılan videonun kare hızıyla en iyi şekilde senkronize edilir. Saniyede yaklaşık 60 kez tetiklenen window.requestAnimationFrame()'den farklı olarak requestVideoFrameCallback(), önemli bir istisna ile birlikte 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ının hızı arasındaki daha düşük olan hızdır. Bu, 60 Hz'de yeniden çizim yapan bir tarayıcıda oynatılan 25 FPS'lik bir videonun geri çağırmaları 25 Hz'de tetikleyeceği anlamına gelir. Aynı 60 Hz tarayıcıdaki 120 FPS video, geri çağırmaları 60 Hz'de tetikler.

Adın önemi var mı?

window.requestAnimationFrame() ile benzerliği nedeniyle yöntem başlangıçta video.requestAnimationFrame() olarak önerilmiş ve uzun bir tartışmanın ardından requestVideoFrameCallback() olarak yeniden adlandırılmıştır.

Özellik algılama

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

Tarayıcı desteği

Browser Support

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

Source

Polyfill

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

requestVideoFrameCallback() yöntemini kullanma

Daha önce requestAnimationFrame() yöntemini kullandıysanız requestVideoFrameCallback() yöntemine hemen alışırsınız. İlk geri aramayı bir kez kaydedersiniz ve geri arama her 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ğırmada now, DOMHighResTimeStamp, metadata ise aşağıdaki özelliklere sahip bir VideoFrameMetadata sözlüktür:

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

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

  • captureTime, DOMHighResTimeStamp türünde: Yerel veya uzak bir kaynaktan gelen video kareleri için bu, karenin kamera tarafından çekildiği zamandır. Uzak bir kaynak için yakalama süresi, RTP zaman damgalarını yakalama süresine 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 bu, kodlanmış karenin platform tarafından alındığı zamandır. Yani bu kareye ait son paketin ağ üzerinden alındığı zamandır.
  • rtpTimestamp, unsigned long türünde: Bu video karesiyle ilişkili RTP zaman damgası.

Bu listede özellikle mediaTime dikkat çekiyor. Chromium'un uygulamasında, video.currentTime öğesini destekleyen zaman kaynağı olarak ses saati kullanılırken mediaTime, doğrudan karenin presentationTimestamp öğesiyle doldurulur. Çerçeveleri tam olarak tanımlamak istiyorsanız (hangi çerçeveleri kaçırdığınızı tam olarak belirlemek dahil) mediaTime kullanmanız gerekir.

Her şey bir kare kaymış gibi görünüyorsa…

Dikey senkronizasyon (veya yalnızca 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ışsa da video birleştirme, oluşturucu iş parçacığında gerçekleştiğinden bu API'den gelen her şey en iyi çaba ile yapılır ve tarayıcı herhangi bir katı garanti vermez. API, bir video karesinin oluşturulmasına kıyasla bir dikey senkronizasyon geç kalmış olabilir. API aracılığıyla web sayfasında yapılan değişikliklerin ekranda görünmesi için bir dikey senkronizasyon gerekir (window.requestAnimationFrame() ile aynı). Bu nedenle, web sayfanızdaki mediaTime veya kare numarasını sürekli olarak güncelleyip bunu numaralandırılmış video kareleriyle karşılaştırırsanız video sonunda bir kare ileride görünür.

Aslında olan şudur: Kare, dikey senkronizasyon x'te hazır olur, geri çağırma tetiklenir ve kare, dikey senkronizasyon x+1'de oluşturulur. Geri çağırmada yapılan değişiklikler ise dikey senkronizasyon x+2'de oluşturulur. Geri çağırmanın vsync geç mi olduğunu (ve karenin ekranda zaten oluşturulmuş olup olmadığını) metadata.expectedDisplayTime değerinin yaklaşık olarak now olup olmadığını veya bir vsync sonra olup olmadığını kontrol ederek doğrulayabilirsiniz. now'dan yaklaşık 5-10 mikrosaniye sonra ise kare zaten oluşturulmuştur. expectedDisplayTime yaklaşık 16 milisaniye sonra ise (tarayıcınızın/ekranınızın 60 Hz'de yenilendiği varsayılarak) kareyle senkronize olursunuz.

Demo

Karelerin tam olarak videonun kare hızında bir tuval üzerine nasıl çizildiğini ve hata ayıklama amacıyla kare meta verilerinin nereye kaydedildiğini gösteren küçük bir demo 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 gerçek karelere erişmeden yalnızca video.currentTime temelinde kare düzeyinde işleme yapıyor. requestVideoFrameCallback() yöntemi, bu geçici çözümü büyük ölçüde iyileştirir.

Teşekkür

requestVideoFrameCallback API'si, Thomas Guilbert tarafından belirtilmiş ve uygulanmıştır. Bu gönderi, Joe Medley ve Kayce Basques tarafından incelendi. Unsplash'teki Denise Jans'ın hero resmi.