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ąpieniamiVideoFrameRequestCallbackpominię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.