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 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.

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()'den farklı olarak requestVideoFrameCallback(), gerçek video kare hızına bağlıdır. Bununla birlikte, önemli bir istisna vardı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 görüntü oluşturan 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 uzun bir tartışma sonrasında requestVideoFrameCallback() olarak yeniden adlandırıldı.

Özellik algılama

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

Tarayıcı desteği

Tarayıcı desteği

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

Kaynak

Çoklu dolgu

Window.requestAnimationFrame() ve HTMLVideoElement.getVideoPlaybackQuality() tabanlı bir requestVideoFrameCallback() yöntemi için polyfill mevcuttur. 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ü: 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ü: Bu video karesiyle ilişkili RTP zaman damgası.

Bu listede özellikle mediaTime dikkat çekiyor. Chromium'un uygulamasında, video.currentTime değerini destekleyen zaman kaynağı olarak sesli saat kullanılır. mediaTime ise doğrudan karenin presentationTimestamp değeriyle doldurulur. Tam olarak hangi kareleri kaçırdığınızı belirlemek de dahil olmak üzere kareleri tam olarak ve tekrarlanabilir bir şekilde tanımlamak istiyorsanız mediaTime değerini kullanmanız gerekir.

Bir kare kaymış 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 tuvale 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.