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
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
typuDOMHighResTimeStamp
: czas, w którym klient użytkownika przesłał ramkę do skompilowania.expectedDisplayTime
typuDOMHighResTimeStamp
: czas, w którym klient użytkownika oczekuje, że ramka będzie widoczna.width
, typuunsigned long
: szerokość ramki wideo w pikselach.height
, typuunsigned long
: wysokość ramki wideo w pikselach mediów.mediaTime
, typudouble
: sygnatura czasowa (PTS) prezentowania multimediów w sekundach w ramce prezentowanego obrazu (np. jej sygnatura czasowa na osi czasuvideo.currentTime
).presentedFrames
, typuunsigned long
: liczba klatek przesłanych do kompozycji. Umożliwia klientom określenie, czy między wystąpieniamiVideoFrameRequestCallback
wystąpiły braki danych.processingDuration
, typudouble
: czas upłynął od momentu przesłania zakodowanego pakietu z tym samym sygnałem czasowym dekodowania (PTS) co ta klatka (np. taki sam jakmediaTime
) 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
, typuDOMHighResTimeStamp
: 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
, typuDOMHighResTimeStamp
: 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
typuunsigned 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.