Efektywne wykonywanie operacji na poszczególnych klatkach wideo za pomocą metody requestVideoFrameCallback()

Dowiedz się, jak korzystać z requestVideoFrameCallback(), aby efektywniej pracować z filmami w przeglądarce.

Metoda HTMLVideoElement.requestVideoFrameCallback() pozwala autorom stron internetowych zarejestrować funkcję wywołania zwrotnego, która jest wykonywana na etapach renderowania, gdy do kompozytora zostanie wysłany nowy kadr wideo. Umożliwia to deweloperom wydajne wykonywanie operacji na poszczególnych klatkach filmu, takich jak przetwarzanie i malowanie na płótnie, analiza wideo czy synchronizacja z zewnętrznych źródeł dźwięku.

Różnica w stosunku do metody requestAnimationFrame()

Operacje takie jak rysowanie klatki filmu na płótnie za pomocą drawImage()wykonywane za pomocą tego interfejsu API będą synchronizowane w najlepszy możliwy sposób z częstotliwością odświeżania filmu odtwarzanego na ekranie. W przeciwieństwie do window.requestAnimationFrame(), która zwykle uruchamia się około 60 razy na sekundę, requestVideoFrameCallback() jest powiązana z rzeczywistą liczbą klatek na sekundę w filmie, z jednym ważnym wyjątkiem:

Skuteczna częstotliwość wywołania funkcji zwrotnej to mniejsza z częstotliwości wideo i przeglądarki. Oznacza to, że film odtwarzany z prędkością 25 FPS w przeglądarce, która odświeża ekran z częstotliwością 60 Hz, wywołałaby wywołania zwrotne z częstotliwością 25 Hz. Film z 120 FPS w tym samym przeglądarce z częstotliwością 60 Hz wywoływałby funkcje zwrotne z częstotliwością 60 Hz.

Co kryje się pod nazwą?

Ze względu na podobieństwo do window.requestAnimationFrame() metoda została początkowo zaproponowana jako video.requestAnimationFrame(), a potem przemianowana na requestVideoFrameCallback(), co zostało uzgodnione po długiej dyskusji.

Wykrywanie funkcji

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

Obsługa przeglądarek

Browser Support

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

Source

Watolina

Dostępna jest polyfilla dla metody requestVideoFrameCallback() na podstawie Window.requestAnimationFrame() i HTMLVideoElement.getVideoPlaybackQuality(). Zanim użyjesz tej funkcji, zapoznaj się z ograniczeniami wymienionymi w README.

Korzystanie z metody requestVideoFrameCallback()

Jeśli kiedykolwiek korzystałeś(-aś) z metody requestAnimationFrame(), metoda requestVideoFrameCallback() będzie Ci się wydawać znajoma. Po raz pierwszy rejestrujesz wywołanie zwrotne, a potem ponownie rejestrujesz je za każdym razem, gdy zostanie wywołane.

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);

W wywołaniu zwrotnym parametr now to obiekt DOMHighResTimeStamp, a metadata to słownik VideoFrameMetadata z tymi właściwościami:

  • presentationTime typu DOMHighResTimeStamp: czas, w którym klient użytkownika przesłał ramkę do skompilowania.
  • expectedDisplayTime typu DOMHighResTimeStamp: czas, w którym klient użytkownika oczekuje, że ramka będzie widoczna.
  • width, typu unsigned long: szerokość ramki wideo w pikselach.
  • height, typu unsigned long: wysokość kadru filmu w pikselach medialnych.
  • mediaTime, typu double: sygnatura czasowa (PTS) prezentowania multimediów w sekundach w ramce prezentowanego obrazu (np. jej sygnatura czasowa na osi czasu video.currentTime).
  • presentedFrames, typu unsigned long: liczba klatek przesłanych do kompozycji. Umożliwia klientom określenie, czy między wystąpieniami VideoFrameRequestCallback wystąpiły utracone klatki.
  • processingDuration, typu double: czas upłynął od momentu przesłania zakodowanego pakietu z tym samym sygnałem czasowym dekodowania (PTS) co ta klatka (np. taki sam jak mediaTime) do dekodera do momentu, gdy zdekodowana klatka była gotowa do wyświetlenia.

W przypadku aplikacji WebRTC mogą się pojawić dodatkowe właściwości:

  • captureTime, typu DOMHighResTimeStamp: w przypadku klatek wideo pochodzących z źródła lokalnego lub zdalnego jest to czas, w którym kamera zarejestrowała daną klatkę. W przypadku źródła zdalnego czas przechwycenia jest szacowany na podstawie synchronizacji zegara i raportów dotyczących nadawcy RTCP, aby przekształcić sygnatury czasowe RTP w czas przechwycenia.
  • receiveTime typu DOMHighResTimeStamp: w przypadku klatek wideo pochodzących z dalszego źródła jest to czas, w którym platforma otrzymała zakodowaną klatkę, czyli czas, w którym ostatni pakiet należący do tej klatki został odebrany przez sieć.
  • rtpTimestamp, typu unsigned long: stempel czasu RTP powiązany z tym obrazem wideo.

Na tej liście szczególną uwagę należy zwrócić na mediaTime. Implementacja w Chromium używa zegara audio jako źródła czasu, które obsługuje video.currentTime, natomiast mediaTime jest wypełniane bezpośrednio przez presentationTimestamp klatki. Jeśli chcesz dokładnie identyfikować klatki w powtarzalny sposób, na przykład aby określić, które klatki Ci umknęły, użyj funkcji mediaTime.

Jeśli coś wydaje się nie pasować…

Synchronizacja pionowa (lub po prostu vsync) to technologia graficzna, która synchronizuje liczbę klatek na sekundę filmu z częstotliwością odświeżania monitora. Funkcja requestVideoFrameCallback() działa w głównym wątku, ale pod maską kompozycja wideo odbywa się w wątku kompozytora. Wszystko w tym interfejsie API jest wykonywane w miarę możliwości, a przeglądarka nie oferuje żadnych ścisłych gwarancji. Może się zdarzyć, że interfejs API opóźnia się o 1 synchronizację pionową w stosunku do renderowania klatki wideo. Zmiany wprowadzone na stronie internetowej za pomocą interfejsu API wymagają jednego synchronizowania pionowego, aby pojawiły się na ekranie (to samo co window.requestAnimationFrame()). Jeśli więc będziesz stale aktualizować mediaTime lub numer kadru na stronie internetowej i porównywać go z numerowanemi klatkami filmu, w końcu film będzie wyglądać tak, jakby był o 1 krok do przodu.

W rzeczywistości klatka jest gotowa w vsync x, wywołanie zwrotne jest wywołane i klatka jest renderowana w vsync x+1, a zmiany wprowadzone w wywołaniu zwrotnym są renderowane w vsync x+2. Możesz sprawdzić, czy wywołanie zwrotne jest opóźnione o jeden vsync (a ramka jest już wyrenderowana na ekranie), sprawdzając, czy metadata.expectedDisplayTime jest w przybliżeniu now lub opóźnione o jeden vsync. Jeśli now jest w zakresie 5–10 mikrosekund, oznacza to, że klatka została już wyrenderowana. Jeśli now jest o około 16 milisekund w przyszłości (zakładając, że przeglądarka/ekran odświeża się z częstotliwością 60 Hz), oznacza to, że jesteś zsynchronizowany z klatką.expectedDisplayTime

Prezentacja

Utworzyłem małą prezentację na Glitch, która pokazuje, jak klatki są wyświetlane na płótnie z dokładną częstotliwością klatek filmu, oraz gdzie są rejestrowane metadane klatek na potrzeby debugowania.

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);

Podsumowanie

Przetwarzanie na poziomie klatek było stosowane od dawna, bez dostępu do rzeczywistych klatek, tylko na podstawie video.currentTime. Metoda requestVideoFrameCallback() znacznie poprawia to obejście.

Podziękowania

Interfejs API requestVideoFrameCallback został określony i wdrożony przez Thomasa Guilberta. Ten post został sprawdzony przez Joe Medley i Kayce Basques. Baner powitalny autorstwa Denise Jans z Unsplash.