Dowiedz się, jak używać requestVideoFrameCallback()
, aby wydajniej pracować z filmami w przeglądarce.
Metoda HTMLVideoElement.requestVideoFrameCallback()
umożliwia autorom stron internetowych zarejestrowanie wywołania zwrotnego, które jest wykonywane w krokach renderowania, gdy do kompozytora wysyłana jest nowa klatka wideo.
Umożliwia to deweloperom wykonywanie wydajnych operacji na poszczególnych klatkach filmu, takich jak przetwarzanie filmu i rysowanie na płótnie, analiza filmu czy synchronizacja z zewnętrznymi źródłami dźwięku.
Różnica w stosunku do metody requestAnimationFrame()
Operacje takie jak rysowanie klatki filmu na obszarze roboczym za pomocą interfejsu drawImage()
będą synchronizowane w miarę możliwości z liczbą klatek na sekundę filmu odtwarzanego na ekranie.
W przeciwieństwie do zdarzenia
window.requestAnimationFrame()
,
które zwykle uruchamia się około 60 razy na sekundę, zdarzenie requestVideoFrameCallback()
jest powiązane z rzeczywistą liczbą klatek na sekundę w filmie. Istnieje jednak ważny
wyjątek:
Efektywna częstotliwość wywoływania funkcji zwrotnych jest mniejszą z dwóch wartości: częstotliwości wideo i częstotliwości przeglądarki. Oznacza to, że film o częstotliwości 25 kl./s odtwarzany w przeglądarce, która odświeża obraz z częstotliwością 60 Hz, będzie wywoływać wywołania zwrotne z częstotliwością 25 Hz. Film o częstotliwości 120 klatek na sekundę w przeglądarce o częstotliwości 60 Hz będzie wywoływać wywołania zwrotne z częstotliwością 60 Hz.
Co kryje się pod nazwą?
Ze względu na podobieństwo do window.requestAnimationFrame()
metoda ta została początkowo zaproponowana jako video.requestAnimationFrame()
i zmieniona na requestVideoFrameCallback()
, co zostało uzgodnione po długiej dyskusji.
Wykrywanie cech
if ('requestVideoFrameCallback' in HTMLVideoElement.prototype) {
// The API is supported!
}
Obsługa przeglądarek
Watolina poliestrowa
Dostępny jest polyfill dla metody requestVideoFrameCallback()
oparty na Window.requestAnimationFrame()
i HTMLVideoElement.getVideoPlaybackQuality()
. Zanim zaczniesz korzystać z 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()
.
Początkową funkcję zwrotną rejestrujesz raz, a potem ponownie za każdym razem, gdy jest ona wywoływana.
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 słownik DOMHighResTimeStamp
, a metadata
to słownik VideoFrameMetadata
z tymi właściwościami:
presentationTime
, typuDOMHighResTimeStamp
: czas, w którym klient użytkownika przesłał ramkę do kompozycji.expectedDisplayTime
, typuDOMHighResTimeStamp
: czas, w którym klient użytkownika oczekuje, że ramka będzie widoczna.width
, typuunsigned long
: Szerokość klatki filmu w pikselach multimedialnych.height
, typuunsigned long
: Wysokość klatki filmu w pikselach multimedialnych.mediaTime
, typudouble
: Sygnatura czasowa prezentacji multimediów (PTS) w sekundach wyświetlanej klatki (np. sygnatura czasowa na osi czasuvideo.currentTime
).presentedFrames
, typunsigned long
: Liczba klatek przesłanych do złożenia. Umożliwia klientom określenie, czy między wystąpieniamiVideoFrameRequestCallback
pominięto klatki.processingDuration
, typudouble
: Czas, który upłynął od przesłania zakodowanego pakietu z tą samą sygnaturą czasową prezentacji (PTS) co ta klatka (np. taką samą jakmediaTime
) do dekodera, aż zdekodowana klatka była gotowa do wyświetlenia.
W przypadku aplikacji WebRTC mogą się pojawić dodatkowe właściwości:
captureTime
, typuDOMHighResTimeStamp
: W przypadku klatek wideo pochodzących ze źródła lokalnego lub zdalnego jest to czas, w którym klatka została zarejestrowana przez kamerę. W przypadku źródła zdalnego czas przechwytywania jest szacowany na podstawie synchronizacji zegara i raportów nadawcy RTCP, aby przekształcić sygnatury czasowe RTP na czas przechwytywania.receiveTime
, typuDOMHighResTimeStamp
: W przypadku klatek wideo pochodzących ze zdalnego źródła jest to czas, w którym zakodowana klatka została odebrana przez platformę, czyli czas, w którym ostatni pakiet należący do tej klatki został odebrany przez sieć.rtpTimestamp
, typuunsigned long
: Znacznik czasu RTP powiązany z tą klatką wideo.
Na tej liście szczególnie interesująca jest pozycja mediaTime
.
Implementacja Chromium używa zegara audio jako źródła czasu, które obsługuje video.currentTime
, natomiast mediaTime
jest bezpośrednio wypełniana przez presentationTimestamp
klatki.
mediaTime
to element, którego należy użyć, jeśli chcesz dokładnie identyfikować klatki w powtarzalny sposób, w tym określać, których klatek brakuje.
Jeśli wydaje Ci się, że coś jest nie tak…
Synchronizacja pionowa (lub po prostu vsync) to technologia graficzna, która synchronizuje liczbę klatek filmu z częstotliwością odświeżania monitora.
Ponieważ funkcja requestVideoFrameCallback()
działa w głównym wątku, ale w tle kompozycja wideo jest tworzona w wątku kompozytora, wszystko w tym interfejsie API jest wykonywane w miarę możliwości, a przeglądarka nie daje żadnych ścisłych gwarancji.
Może się zdarzyć, że interfejs API jest o 1 synchronizację pionową opóźniony w stosunku do momentu renderowania klatki wideo.
Wyświetlenie na ekranie zmian wprowadzonych na stronie internetowej za pomocą interfejsu API zajmuje 1 synchronizację pionową (tak samo jak w przypadku window.requestAnimationFrame()
).
Jeśli więc będziesz aktualizować mediaTime
lub numer klatki na stronie internetowej i porównywać to z numerowanymi klatkami wideo, w końcu wideo będzie wyglądać tak, jakby było o 1 klatkę do przodu.
W rzeczywistości klatka jest gotowa przy synchronizacji pionowej x, wywoływane jest wywołanie zwrotne, a klatka jest renderowana przy synchronizacji pionowej x+1. Zmiany wprowadzone w wywołaniu zwrotnym są renderowane przy synchronizacji pionowej x+2.
Możesz sprawdzić, czy wywołanie zwrotne jest opóźnione w stosunku do synchronizacji pionowej (a klatka jest już renderowana na ekranie), sprawdzając, czy wartość metadata.expectedDisplayTime
jest w przybliżeniu równa now
lub o 1 synchronizację pionową w przyszłości.
Jeśli jest to około 5–10 mikrosekund od now
, klatka jest już wyrenderowana. Jeśli expectedDisplayTime
jest około 16 milisekund w przyszłości (zakładając, że przeglądarka lub ekran odświeża się z częstotliwością 60 Hz), to synchronizacja z klatką jest prawidłowa.
Prezentacja
Utworzyłem małe demo, które pokazuje, jak klatki są rysowane na płótnie dokładnie z częstotliwością klatek filmu oraz gdzie metadane klatek są rejestrowane 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 jest stosowane od dawna, ale bez dostępu do rzeczywistych klatek, tylko na podstawie video.currentTime
.
Metoda requestVideoFrameCallback()
znacznie ulepsza to obejście.
Podziękowania
Interfejs API requestVideoFrameCallback
został określony i wdrożony przez Thomasa Guilberta.
Ten post został sprawdzony przez Joego Medleya i Kayce Basques.
Baner powitalny:
Denise Jans, Unsplash.