Dowiedz się, jak używać requestVideoFrameCallback(), aby wydajniej pracować z filmami w przeglądarce.
Data publikacji: 8 stycznia 2023 r.
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 wykonywane za pomocą tego interfejsu API, takie jak rysowanie klatki wideo na płótnie za pomocą funkcji
drawImage(),
są synchronizowane w miarę możliwości z liczbą klatek na sekundę odtwarzanego na ekranie filmu. Różni się to od window.requestAnimationFrame(), które zwykle jest wywoływane około 60 razy na sekundę.
requestVideoFrameCallback() jest powiązana z rzeczywistą liczbą klatek na sekundę filmu, z ważnym wyjątkiem:
Efektywna częstotliwość wywoływania funkcji zwrotnych to mniejsza z dwóch wartości: częstotliwości filmu i częstotliwości przeglądarki. Oznacza to, że film o częstotliwości 25 klatek na sekundę 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.
Wykrywanie cech
if ('requestVideoFrameCallback' in HTMLVideoElement.prototype) {
// The API is supported!
}
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.
Użyj metody
Jeśli używasz metody requestAnimationFrame(), rozpoznasz metodę requestVideoFrameCallback(). Zarejestruj początkowe wywołanie zwrotne raz, a następnie rejestruj je ponownie 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 now to DOMHighResTimeStamp, a metadata to słownik VideoFrameMetadata o tych właściwościach:
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, typdouble: Sygnatura czasowa prezentacji multimedialnej (PTS) w sekundach klatki prezentowanej (np. sygnatura czasowa na osi czasuvideo.currentTime).presentedFrames, typuunsigned long: Liczba klatek przesłanych do kompozycji. Umożliwia klientom określenie, czy między wystąpieniamiVideoFrameRequestCallbackpominięto klatki.processingDuration, typudouble: Czas w sekundach, który upłynął od przesłania zakodowanego pakietu z tym samym znacznikiem czasu prezentacji (PTS) co ta klatka (np. takim samym jakmediaTime) do dekodera, aż zdekodowana klatka była gotowa do prezentacji.
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 zdalnego źródła czas przechwytywania jest szacowany na podstawie synchronizacji zegara i raportów nadawcy RTCP, aby przekonwertować 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: Sygnatura czasowa RTP powiązana z tą klatką wideo.
Na tej liście szczególnie interesująca jest 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 obraz jest przesunięty o 1 klatkę
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 wątku głównym, ale w tle kompozycja wideo jest tworzona w wątku kompozytora, wszystko z tego interfejsu API jest wykonywane w miarę możliwości, a przeglądarka nie oferuje żadnych ścisłych gwarancji.
Możliwe, ż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 filmu, w końcu film będzie wyglądał tak, jakby był 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, a 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 ona w zakresie od około 5 do 10 mikrosekund od now, klatka jest już renderowana. Jeśli expectedDisplayTime przypada za około 16 milisekund (zakładając, że przeglądarka odświeża się z częstotliwością 60 Hz), oznacza to, że synchronizacja z klatką jest prawidłowa.
Prezentacja
Utworzyłem małą wersję demonstracyjną, która 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 requestVideoFrameCallback API został określony i wdrożony przez Thomasa Guilberta.
Ten post został sprawdzony przez Joego Medleya i Kayce Basques.