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 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 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ątek:
Skuteczna częstotliwość wywołania jest 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 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
Watolina
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 się, gdy chcesz otrzymać połączenie zwrotne, a potem rejestrujesz się ponownie, gdy połączenie zwrotne 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 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ść 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
pominięto klatki.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 dekodowana 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 za pomocą 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 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 coś wydaje się nie pasować do kadru…
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, a wszystko z tego interfejsu API jest wykonywane w miarę możliwości. Przeglądarka nie oferuje żadnych ścisłych gwarancji.
Może się zdarzyć, że interfejs API opóźnia się o 1 synchronizację pionową 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 numerowanemi klatkami filmu, w końcu film będzie wyglądać tak, jakby był o 1 kadr 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.
Możesz sprawdzić, czy wywołanie zwrotne jest opóźnione o jeden vsync (a ramka jest już wyrenderowana na ekranie), sprawdzając, 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/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.