Dowiedz się, jak korzystać z requestVideoFrameCallback()
, aby efektywniej pracować z filmami w przeglądarce.
Metoda HTMLVideoElement.requestVideoFrameCallback()
pozwala autorom stron internetowych zarejestrować funkcję wywołania zwrotnego, która jest wykonywana na etapach renderowania, gdy do kompozytora zostanie wysłany nowy kadr wideo.
Umożliwia to deweloperom wykonywanie wydajnych operacji na poszczególnych klatkach filmu, takich jak przetwarzanie filmu i malowanie na płótnie, analiza filmu czy synchronizacja z zewnętrznych źródeł dźwięku.
Różnica w stosunku do metody requestAnimationFrame()
Operacje takie jak rysowanie klatki filmu 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. Wyjątkiem jest ważna wyjątkowa sytuacja:
Skuteczna częstotliwość wywołania jest mniejsza z częstotliwości wideo i przeglądarki. Oznacza to, że film odtwarzany z częstotliwością 25 FPS w przeglądarce, która odświeża ekran z częstotliwością 60 Hz, wywołałaby funkcje zwrotne z częstotliwością 25 Hz. Film z 120 FPS w tym samym przeglądarce z częstotliwością 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 window.requestAnimationFrame()
metoda została początkowo zaproponowana jako video.requestAnimationFrame()
, a potem przemianowana 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
Wypełnienie
Dostępna jest polyfilla dla metody requestVideoFrameCallback()
na podstawie 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()
.
Po raz pierwszy rejestrujesz wywołanie zwrotne, a potem ponownie, gdy zostanie ono 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 parametr now
to obiekt 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ść kadru filmu 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 ramek.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 przechwycenia jest szacowany przy użyciu synchronizacji zegara i raportów nadawcy RTCP w celu przekształcenia sygnatur czasowych RTP na czas przechwycenia.receiveTime
typuDOMHighResTimeStamp
: w przypadku klatek wideo pochodzących z dalszego źródła 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ólną uwagę należy zwrócić na mediaTime
.
Implementacja w Chromium używa zegara audio jako źródła czasu, które obsługuje video.currentTime
, natomiast mediaTime
jest wypełniane 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 coś wydaje się nie pasować…
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ą kompozycja 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 się zdarzyć, że interfejs API jest o 1 synchronizację pionową za późno w stosunku do momentu renderowania klatki wideo.
Zmiany wprowadzone na stronie internetowej za pomocą interfejsu API wymagają 1 synchronizacji pionowej, aby pojawiły się na ekranie (to samo co window.requestAnimationFrame()
). Jeśli więc będziesz stale aktualizować mediaTime
lub numer kadru na stronie internetowej i porównywać go z ponumerowanymi klatkami filmu, w końcu film będzie wyglądać tak, jakby był o 1 klatkę do przodu.
W rzeczywistości klatka jest gotowa w vsync x, wywołanie zwrotne jest wywołane i klatka jest renderowana w vsync x+1, a zmiany wprowadzone w wywołaniu zwrotnym są renderowane w vsync x+2.
Aby sprawdzić, czy wywołanie zwrotne jest opóźnione o jeden vsync (a ramka jest już wyrenderowana na ekranie), sprawdź, czy metadata.expectedDisplayTime
jest w przybliżeniu now
lub opóźnione o jeden vsync.
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 lub 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ą wyświetlane na płótnie z dokładną częstotliwością klatek filmu i 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 API requestVideoFrameCallback
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.