Effiziente Vorgänge pro Videoframe für Videos mit requestVideoFrameCallback() ausführen

Erfahre, wie du mit dem requestVideoFrameCallback() effizienter mit Videos im Browser arbeiten kannst.

Mit der Methode HTMLVideoElement.requestVideoFrameCallback() können Webautoren einen Callback registrieren, der in den Renderingschritten ausgeführt wird, wenn ein neuer Videoframe an den Compositor gesendet wird. So können Entwickler effiziente Operationen auf Video-Frames für Videos ausführen, z. B. die Videoverarbeitung und das Painting auf einem Canvas, eine Videoanalyse oder die Synchronisierung mit externen Audioquellen.

Unterschied zu requestAnimationFrame()

Über diese API ausgeführte Vorgänge wie das Zeichnen eines Videoframes auf einem Canvas mithilfe von drawImage() werden bestmöglich mit der Framerate des auf dem Bildschirm wiedergegebenen Videos synchronisiert. Im Unterschied zu window.requestAnimationFrame(), das normalerweise etwa 60 Mal pro Sekunde ausgelöst wird, ist requestVideoFrameCallback() an die tatsächliche Framerate des Videos gebunden – mit einer wichtigen Ausnahme:

Die effektive Rate, mit der Callbacks ausgeführt werden, ist die geringere Rate zwischen der Rate des Videos und der Rate des Browsers. Das bedeutet, dass ein Video mit 25 fps, das in einem Browser mit 60 Hz malt, Callbacks mit 25 Hz auslösen. Ein Video mit 120 fps im selben 60-Hz-Browser würde Callbacks mit 60 Hz auslösen.

Tipps zur Benennung von Sitemaps

Aufgrund der Ähnlichkeit mit window.requestAnimationFrame() wurde die Methode anfangs als video.requestAnimationFrame() vorgeschlagen und in requestVideoFrameCallback() umbenannt. Dies wurde nach einer langen Diskussion vereinbart.

Funktionserkennung

if ('requestVideoFrameCallback' in HTMLVideoElement.prototype) {
  // The API is supported!
}

Unterstützte Browser

Unterstützte Browser

  • 83
  • 83
  • x
  • 15,4

Quelle

Polyfill

Es ist ein Polyfill für die Methode requestVideoFrameCallback() verfügbar, die auf Window.requestAnimationFrame() und HTMLVideoElement.getVideoPlaybackQuality() basiert. Beachten Sie vor der Verwendung die Einschränkungen in der README.

Methode „requestVideoFrameCallback()“ verwenden

Wenn Sie schon einmal die requestAnimationFrame()-Methode verwendet haben, werden Sie sich sofort mit der requestVideoFrameCallback()-Methode vertraut machen. Sie registrieren einen ersten Callback einmal und werden jedes Mal neu registriert, wenn der Callback ausgelöst wird.

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);

Im Callback ist now ein DOMHighResTimeStamp und metadata ein VideoFrameMetadata-Wörterbuch mit den folgenden Attributen:

  • presentationTime vom Typ DOMHighResTimeStamp: Der Zeitpunkt, zu dem der User-Agent den Frame zur Komposition übermittelt hat.
  • expectedDisplayTime vom Typ DOMHighResTimeStamp: Die Zeit, zu der der User-Agent erwartet, dass der Frame sichtbar ist.
  • width vom Typ unsigned long: Die Breite des Videoframes in Medienpixeln.
  • height vom Typ unsigned long: die Höhe des Videoframes in Medienpixeln.
  • mediaTime vom Typ double: Der Zeitstempel der Medienpräsentation (PTS) in Sekunden des dargestellten Frames (z.B. der Zeitstempel auf der video.currentTime-Zeitachse).
  • presentedFrames vom Typ unsigned long: Die Anzahl der Frames, die zur Komposition gesendet wurden. Ermöglicht Clients festzustellen, ob Frames zwischen Instanzen von VideoFrameRequestCallback verpasst wurden.
  • processingDuration vom Typ double: Die verstrichene Zeit in Sekunden ab der Übertragung des codierten Pakets mit demselben Präsentationszeitstempel (PTS) wie dieser Frame (z.B. mit dem mediaTime) an den Decodierer, bis der decodierte Frame für die Präsentation bereit war.

Für WebRTC-Anwendungen können zusätzliche Eigenschaften angezeigt werden:

  • captureTime vom Typ DOMHighResTimeStamp: Bei Videoframes, die aus einer lokalen oder Remote-Quelle stammen, ist dies die Zeit, zu der der Frame von der Kamera aufgenommen wurde. Bei einer Remote-Quelle wird die Erfassungszeit mithilfe der Uhrsynchronisierung und RTCP-Absenderberichte geschätzt, um RTP-Zeitstempel in die Uhrzeit zu konvertieren.
  • receiveTime vom Typ DOMHighResTimeStamp: Bei Videoframes, die aus einer Remote-Quelle stammen, ist dies die Zeit, zu der der codierte Frame von der Plattform empfangen wurde, d. h. der Zeitpunkt, zu dem das letzte Paket, das zu diesem Frame gehört, über das Netzwerk empfangen wurde.
  • rtpTimestamp vom Typ unsigned long: Der RTP-Zeitstempel, der diesem Videoframe zugeordnet ist.

Von besonderem Interesse auf dieser Liste ist mediaTime. Die Implementierung von Chromium verwendet den Audiouhr als Zeitquelle für das video.currentTime, während mediaTime direkt durch die presentationTimestamp des Frames gefüllt wird. Die mediaTime sollten Sie verwenden, wenn Sie Frames auf reproduzierbare Weise genau identifizieren möchten, z. B. um genau zu ermitteln, welche Frames Sie verpasst haben.

Wenn alles einen Frame zu weit weg scheint...

Die vertikale Synchronisierung (oder einfach vsync) ist eine Grafiktechnologie, die die Framerate eines Videos und die Aktualisierungsrate eines Monitors synchronisiert. Da requestVideoFrameCallback() im Hauptthread ausgeführt wird, der Video-Compositing aber im Hintergrund im Compositor-Thread stattfindet, ist mit dieser API alles möglich und der Browser bietet keine strengen Garantien. Das kann daran liegen, dass die API eine vsync-verspätete Ausführung aufweist, wenn ein Videoframe gerendert wurde. Es ist eine Vsync erforderlich, bis Änderungen, die über die API an der Webseite vorgenommen wurden, auf dem Bildschirm angezeigt werden (wie bei window.requestAnimationFrame()). Wenn Sie also mediaTime oder die Frame-Nummer auf Ihrer Webseite mit den nummerierten Videoframes vergleichen, sieht das Video so aus, als wäre es einen Frame voraus.

Tatsächlich ist der Frame bei vsync x bereit, der Callback wird ausgelöst und der Frame wird bei vsync x+1 gerendert. Änderungen im Callback werden bei vsync x+2 gerendert. Sie können prüfen, ob der Callback über eine vsync-Funktion verzögert ist (und der Frame bereits auf dem Bildschirm gerendert wurde). Dazu prüfen Sie, ob der metadata.expectedDisplayTime-Wert ungefähr now oder in der Zukunft eine vsync ist. Wenn der Abstand innerhalb von fünf bis zehn Mikrosekunden von now liegt, ist der Frame bereits gerendert. Wenn expectedDisplayTime etwa sechzehn Millisekunden in der Zukunft liegt (wenn Ihr Browser bzw. Bildschirm mit 60 Hz aktualisiert wird), sind Sie mit dem Frame synchronisiert.

Demo

Ich habe eine kleine Demo zu Glitch erstellt, die zeigt, wie Frames mit genau der Framerate des Videos auf einem Canvas gezeichnet werden und wo die Frame-Metadaten zu Debugging-Zwecken protokolliert werden.

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);

Ergebnisse

Es wird schon sehr lange eine Verarbeitung auf Frameebene durchgeführt – ohne Zugriff auf die tatsächlichen Frames zu haben, sondern nur auf Grundlage von video.currentTime. Die requestVideoFrameCallback()-Methode bietet eine deutliche Verbesserung dieser Problemumgehung.

Danksagungen

Die requestVideoFrameCallback API wurde von Thomas Guilbert angegeben und implementiert. Dieser Beitrag wurde von Joe Medley und Kayce Basques geprüft. Hero-Image von Denise Jans auf Unsplash.