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. Dzięki temu deweloperzy mogą wykonywać efektywne operacje na klatce filmu, np. przetwarzanie i malowanie obrazu na płótnie, analizę wideo lub synchronizację z zewnętrznymi źródłami audio.

Różnica w metodzie requestAnimationFrame()

Operacje takie jak rysowanie klatki wideo do obszaru roboczego za pomocą drawImage() wykonane za pomocą tego interfejsu API będą w miarę możliwości synchronizowane z liczbą klatek filmu odtwarzanego na ekranie. W przeciwieństwie do metody window.requestAnimationFrame(), która zwykle uruchamia się około 60 razy na sekundę, parametr requestVideoFrameCallback() jest powiązany z rzeczywistą liczbą klatek na sekundę – z ważnym wyjątkiem:

Efektywna częstotliwość wykonywania wywołań zwrotnych to mniejsza wartość między częstotliwością reklamy wideo a częstotliwością przeglądarki. Oznacza to, że film z szybkością 25 kl./s odtwarzany w przeglądarce, który maluje z częstotliwością 60 Hz, uruchamia wywołania zwrotne z częstotliwością 25 Hz. Film z szybkością 120 kl./s w tej samej przeglądarce 60 Hz uruchamiał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 została ona zaproponowana jako video.requestAnimationFrame() i zmieniła nazwę na requestVideoFrameCallback(), co zostało uzgodnione po długiej dyskusji.

Wykrywanie funkcji

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

Obsługiwane przeglądarki

Obsługa przeglądarek

  • 83
  • 83
  • x
  • 15,4

Źródło

Włókno poliestrowe

Dostępny jest kod polyfill dla metody requestVideoFrameCallback() oparty na Window.requestAnimationFrame() i HTMLVideoElement.getVideoPlaybackQuality(). Zanim z niego skorzystasz, zapoznaj się z ograniczeniami wymienionymi w README.

Korzystanie z metody requestVideoFrameCallback()

Jeśli kiedykolwiek korzystałeś(-aś) z metody requestAnimationFrame(), od razu znasz metodę requestVideoFrameCallback(). Jednorazowo rejestrujesz pierwsze wywołanie zwrotne, a potem rejestrujesz się ponownie po uruchomieniu wywołania zwrotnego.

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 język DOMHighResTimeStamp, a metadata to słownik VideoFrameMetadata z tymi właściwościami:

  • presentationTime, typ DOMHighResTimeStamp: czas, w którym klient użytkownika przesłał ramkę do kompozycji.
  • expectedDisplayTime, typ DOMHighResTimeStamp: godzina, o której klient użytkownika spodziewa się zobaczyć klatkę.
  • width, typ unsigned long: szerokość klatki wideo w pikselach multimediów.
  • height, typ unsigned long: wysokość klatki wideo (w pikselach multimediów).
  • mediaTime typu double: sygnatura czasowa prezentacji multimediów (PTS) w sekundach wyświetlonej klatki (np. jej sygnatura czasowa na osi czasu video.currentTime).
  • presentedFrames, typ unsigned long: liczba klatek przesłanych do kompozycji. Umożliwia klientom określenie, czy klatki zostały pominięte między instancjami VideoFrameRequestCallback.
  • processingDuration, typu double: Czas, jaki upływa od przesłania zakodowanego pakietu o tej samej sygnaturze czasowej prezentacji (PTS) co ta ramka (np. mediaTime) do dekodera do momentu, gdy zdekodowana ramka była gotowa do prezentacji.

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

  • captureTime, typu DOMHighResTimeStamp: w przypadku klatek wideo pochodzących ze źródła lokalnego lub zdalnego jest to godzina zarejestrowania klatki przez kamerę. W przypadku źródła zdalnego czas przechwytywania jest szacowany na podstawie synchronizacji zegara i raportów nadawców RTCP w celu konwersji sygnatur czasowych RTP na czas rejestracji.
  • receiveTime typu DOMHighResTimeStamp: w przypadku klatek wideo pochodzących ze źródła zdalnego jest to czas odebrania zakodowanej ramki przez platformę, czyli czas odebrania przez sieć ostatniego pakietu należącego do tej klatki.
  • rtpTimestamp, typu unsigned long: sygnatura czasowa RTP powiązana z tą klatką wideo.

Ta lista ma szczególne znaczenie: mediaTime. Implementacja Chromium używa zegara audio jako źródła czasu wspierającego parametr video.currentTime, podczas gdy pole mediaTime jest bezpośrednio wypełniane przez presentationTimestamp ramki. Atrybutu mediaTime należy użyć, jeśli chcesz dokładnie identyfikować klatki, które można odtworzyć, w tym ustalać, które klatki zostały pominięte.

Jeśli obraz wydaje Ci się odsunięty w jedną klatkę...

Synchronizacja pionowa (lub zwykła vsync) to technologia graficzna, która synchronizuje liczbę klatek filmu i częstotliwość odświeżania na monitorze. Interfejs requestVideoFrameCallback() działa w wątku głównym, ale komponowanie wideo odbywa się w wątku kompozytora, dlatego wszystko robimy przy użyciu tego interfejsu API, a przeglądarka nie daje żadnych ścisłych gwarancji. Wynika to z tego, że interfejs API może być opóźniony o jedną późną synchronizację w stosunku do czasu renderowania klatki wideo. Zmiany wprowadzone przez interfejs API są widoczne na ekranie dopiero po jednej sesji vsync (tak samo jak w przypadku window.requestAnimationFrame()). Jeśli więc stale aktualizujesz numer mediaTime lub numer klatki na stronie internetowej i porównujesz go z numerowanymi klatkami wideo, w końcu film będzie wyglądać tak, jakby był o jedną klatkę do przodu.

W rzeczywistości ramka jest gotowa na vsync x, wywoływane jest wywołanie zwrotne, a klatka renderowana jest przy użyciu vsync x+1, a zmiany w wywołaniu zwrotnym są renderowane przy użyciu vsync x+2. Aby sprawdzić, czy wywołanie zwrotne jest opóźnione w przypadku vsync (a klatka została już wyrenderowana na ekranie), sprawdź, czy metadata.expectedDisplayTime to około now czy 1 synchronizacja vsync w przyszłości. Jeśli od now do 5–10 mikrosekund, klatka jest już renderowana. Jeśli expectedDisplayTime będzie renderować się za około 16 milisekund (przy założeniu, że przeglądarka/ekran jest odświeżana przy 60 Hz), następuje synchronizacja z klatką.

Pokaz

Przygotowałem(-am) krótką prezentację w Glitchu, która pokazuje, jak klatki są rysowane na płótnie z taką samą liczbą klatek na sekundę, jaka jest liczba 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

Użytkownicy już od dawna korzystają z przetwarzania na poziomie klatki, nie mając dostępu do samych klatek, tylko na podstawie danych video.currentTime. Metoda requestVideoFrameCallback() znacznie usprawnia to obejście.

Podziękowania

Interfejs API requestVideoFrameCallback został stworzony i wdrożony przez Thomasa Guilberta. Ten post został zweryfikowany przez Joe Medley i Kayce Basques. Baner powitalny autorstwa Denise Jans w aplikacji Unsplash.