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() umożliwia autorom stron internetowych rejestrowanie wywołania zwrotnego, które jest uruchamiane w krokach renderowania, gdy do kompozytora jest wysyłana nowa klatka 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 ramki wideo 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 funkcji window.requestAnimationFrame(), która zwykle działa około 60 razy na sekundę, funkcja 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 25 FPS w przeglądarce, która odświeża się 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 60 Hz wywołałby wywołania zwrotne z częstotliwością 60 Hz.

Co kryje się pod nazwą?

Ze względu na podobieństwo do metody window.requestAnimationFrame() początkowo była ona zaproponowana jako video.requestAnimationFrame() i nazwana na requestVideoFrameCallback(). Uzgodniliśmy ją po długich dyskusjach.

Wykrywanie cech

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

Obsługa przeglądarek

Obsługa przeglądarek

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

Źródło

Watolina

Dostępny jest kod polyfill dla metody requestVideoFrameCallback() opartej na 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(), od razu poczujesz się swobodnie z metodą requestVideoFrameCallback(). Najpierw rejestrujesz pierwsze wywołanie zwrotne, a następnie rejestrujesz się ponownie po każdym wywołaniu.

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 now to 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ść ramki wideo w pikselach mediów.
  • 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 braki danych.
  • 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 przechwytywania jest szacowany na podstawie synchronizacji zegara i raportów nadawców RTCP do konwersji sygnatur czasowych RTP na potrzeby przechwytywania czasu.
  • receiveTime, typu DOMHighResTimeStamp: w przypadku klatek wideo pochodzących ze źródła zdalnego 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ólne znaczenie ma mediaTime. Implementacja w Chromium używa zegara audio jako źródła czasu, które obsługuje video.currentTime, podczas gdy mediaTime jest wypełniany 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 parametru mediaTime.

Jeśli wydaje Ci się, że obraz jest oddalony o jedną klatkę...

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ą składanie 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 to wynikać z tego, że interfejs API może działać z opóźnieniem vsync w stosunku do czasu renderowania klatki wideo. Zmiany wprowadzone na stronie internetowej za pomocą interfejsu API pojawiają się na ekranie za pomocą jednej synchronizacji vsync (tak samo jak w przypadku funkcji window.requestAnimationFrame()). Jeśli więc będziesz aktualizować mediaTime lub numer klatki na stronie internetowej i porównać go z ponumerowanymi klatkami wideo, w końcu film będzie wyglądał, jakby był widoczny jedną klatkę do przodu.

Rzeczywista klatka jest gotowa w momencie vsync x, następuje wywołanie wywołania zwrotnego i renderowanie klatki przy vsync x+1, a zmiany dokonane w wywołaniu zwrotnym są renderowane w trybie vsync x+2. Aby sprawdzić, czy wywołanie zwrotne jest opóźnione względem vsync (a klatka została już wyrenderowana na ekranie), sprawdź, czy metadata.expectedDisplayTime to w przybliżeniu now czy 1 synchronizacja vsync w przyszłości. Jeśli now jest o 5–10 mikrosekund odbiegająca od wartości now, oznacza to, że klatka została już wyrenderowana. Jeśli now jest o około 16 milisekund odbiegająca od wartości now (przy założeniu, ż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ą rysowane 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 trwa już od dawna, ale bez dostępu do samych klatek, tylko na podstawie video.currentTime. Metoda requestVideoFrameCallback() znacznie poprawia to obejście.

Podziękowania

Interfejs requestVideoFrameCallback API 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.