Saltar al contenido principal

Aprenda a usar el requestVideoFrameCallback() para trabajar de manera más eficiente con videos en el navegador.


Actualizado

Hay una nueva API web en el bloque, definida en el
HTMLVideoElement.requestVideoFrameCallback()

especificación. los requestVideoFrameCallback() El método permite a los autores web registrar una devolución de llamada que se ejecuta en los pasos de renderizado cuando se envía un nuevo fotograma de vídeo al compositor. Esto está destinado a permitir a los desarrolladores realizar operaciones eficientes por cuadro de video en video, como procesamiento de video y pintura en un lienzo, análisis de video o sincronización con fuentes de audio externas.

Diferencia con requestAnimationFrame ()

Operaciones como dibujar un fotograma de video en un lienzo mediante
drawImage()

igraal_es-es

realizado a través de esta API se sincronizará como mejor esfuerzo con la velocidad de fotogramas del video que se reproduce en la pantalla. Diferente de
window.requestAnimationFrame(), que normalmente dispara unas 60 veces por segundo,
requestVideoFrameCallback() está vinculado a la velocidad de fotogramas de vídeo real, con una importante
excepción:

La tasa efectiva a la que se ejecutan las devoluciones de llamada es la tasa menor entre la tasa del video y la tasa del navegador. Esto significa que un video de 25 fps que se reproduce en un navegador que pinta a 60Hz dispararía devoluciones de llamada a 25Hz. Un video de 120 fps en ese mismo navegador de 60Hz dispararía devoluciones de llamada a 60Hz.

¿Lo que hay en un nombre?

Debido a su similitud con window.requestAnimationFrame(), el método inicialmente fue propuesto como video.requestAnimationFrame(), pero estoy contento con el nuevo nombre,
requestVideoFrameCallback(), que se acordó tras un larga discusión. Hurra, bicis ¡por la victoria!

Soporte de navegador y detección de funciones

El método es
implementado en Chromium
ya, y
A la gente de Mozilla le gusta. Por lo que vale, también he presentado una
Error de WebKit pedirlo. La detección de características de la API funciona así:

if ('requestVideoFrameCallback' in HTMLVideoElement.prototype) {
}

Usando el método requestVideoFrameCallback ()

Si alguna vez ha utilizado el requestAnimationFrame() método, inmediatamente se sentirá como en casa con el requestVideoFrameCallback() método. Usted registra una devolución de llamada inicial una vez y luego vuelve a registrarse cada vez que se activa la devolución de llamada.

const doSomethingWithTheFrame = (now, metadata) => {
console.log(now, metadata);
video.requestVideoFrameCallback(doSomethingWithTheFrame);
};
video.requestVideoFrameCallback(doSomethingWithTheFrame);

En la devolución de llamada, now es un DOMHighResTimeStamp

y metadata es un VideoFrameMetadata

diccionario con las siguientes propiedades:

  • presentationTime, de tipo DOMHighResTimeStamp: El momento en el que el agente de usuario envió el marco para su composición.
  • expectedDisplayTime, de tipo DOMHighResTimeStamp: El momento en el que el agente de usuario espera que el marco sea visible.
  • width, de tipo unsigned long: El ancho del fotograma de video, en píxeles de medios.
  • height, de tipo unsigned long: La altura del fotograma de vídeo, en píxeles multimedia.
  • mediaTime, de tipo double: La marca de tiempo de presentación de medios (PTS) en segundos del fotograma presentado (por ejemplo, su marca de tiempo en el video.currentTime línea de tiempo).
  • presentedFrames, de tipo unsigned long: Recuento del número de fotogramas enviados para composición. Permite a los clientes determinar si se perdieron tramas entre instancias de VideoFrameRequestCallback.
  • processingDuration, de tipo double: La duración transcurrida en segundos desde el envío del paquete codificado con la misma marca de tiempo de presentación (PTS) que esta trama (por ejemplo, igual que la mediaTime) al decodificador hasta que la trama decodificada esté lista para su presentación.

Para las aplicaciones WebRTC, pueden aparecer propiedades adicionales:

  • captureTime, de tipo DOMHighResTimeStamp: Para los fotogramas de video que provienen de una fuente local o remota, este es el momento en el que la cámara capturó el fotograma. Para una fuente remota, el tiempo de captura se estima mediante la sincronización del reloj y los informes del remitente RTCP para convertir las marcas de tiempo RTP en tiempo de captura.
  • receiveTime, de tipo DOMHighResTimeStamp: Para las tramas de video que provienen de una fuente remota, esta es la hora a la que la plataforma recibió la trama codificada, es decir, la hora a la que se recibió el último paquete perteneciente a esta trama a través de la red.
  • rtpTimestamp, de tipo unsigned long: La marca de tiempo RTP asociada con este fotograma de vídeo.

Tenga en cuenta que width y height puede diferir de videoWidth y videoHeight en ciertos casos (por ejemplo, un video anamórfico puede tener píxeles rectangulares).

De especial interés en esta lista es mediaTime. En la implementación de Chromium, usamos el reloj de audio como fuente de tiempo que respalda video.currentTime, mientras que el mediaTime está poblado directamente por el presentationTimestamp del marco. los mediaTime es lo que debe utilizar si desea identificar exactamente los fotogramas de forma reproducible, incluso para identificar exactamente los fotogramas que se perdieron.

Desafortunadamente, el elemento de video no garantiza una precisión de cuadro buscando. Esta ha sido una continua tema de discusión.
WebCodecs eventualmente permitirá aplicaciones con precisión de cuadros.

Si las cosas parecen un cuadro de distancia …

La sincronización vertical (o simplemente vsync) es una tecnología de gráficos que sincroniza la frecuencia de cuadros de un video y la frecuencia de actualización de un monitor. Ya que requestVideoFrameCallback() se ejecuta en el hilo principal, pero, bajo el capó, la composición de video ocurre en el hilo del compositor, todo desde esta API es un mejor esfuerzo y no ofrecemos ninguna garantía estricta. Lo que puede estar sucediendo es que la API puede retrasarse un vsync en relación con el momento en que se procesa un fotograma de video. Se necesita un vsync para que los cambios realizados en la página web a través de la API aparezcan en la pantalla (igual que window.requestAnimationFrame()). Entonces, si sigues actualizando mediaTime o el número de fotograma en su página web y compárelo con los fotogramas de video numerados, eventualmente el video se verá como si estuviera un fotograma por delante.

Lo que realmente está sucediendo es que el marco está listo en vsync x, la devolución de llamada se activa y el marco se procesa en vsync x + 1, y los cambios realizados en la devolución de llamada se procesan en vsync x + 2. Puede verificar si la devolución de llamada es un vsync tardío (y el marco ya está renderizado en la pantalla) verificando si el metadata.expectedDisplayTime es aproximadamente now o un vsync en el futuro. Si está dentro de unos cinco a diez microsegundos de now, el marco ya está renderizado; Si el expectedDisplayTime es aproximadamente dieciséis milisegundos en el futuro (suponiendo que su navegador / pantalla se actualice a 60Hz), entonces está sincronizado con el marco.

Manifestación

He creado un pequeño
demostración en Glitch
que muestra cómo se dibujan los fotogramas en un lienzo exactamente a la velocidad de fotogramas del video y dónde se registran los metadatos del fotograma para fines de depuración. La lógica central es solo un par de líneas de JavaScript.

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

Conclusiones

He realizado procesamiento a nivel de fotogramas durante mucho tiempo, sin tener acceso a los fotogramas reales, solo en función de video.currentTime. Implementé la segmentación de tomas de video en JavaScript de una manera aproximada; todavía puedes leer el adjunto
trabajo de investigación. Tenía el requestVideoFrameCallback() existía en ese entonces, mi vida hubiera sido mucho más simple …

Agradecimientos

los requestVideoFrameCallback La API fue especificada e implementada por
Thomas Guilbert. Este artículo fue revisado por Joe Medley
y Kayce vascos.
Imagen de héroe por
Denise Jans en Unsplash.

error: Atención: Contenido protegido.