Manipular los componentes de la transmisión de video
Las tecnologías web modernas ofrecen muchas formas de trabajar con videos. La API de Media Stream, la API de Media Recording, la API de Media Source y la API de WebRTC se suman para crear un conjunto de herramientas enriquecido para grabar, transferir y reproducir transmisiones de video. Si bien estas APIs permiten resolver ciertas tareas de alto nivel, no permiten que los programadores web trabajen con componentes individuales de una transmisión de video, como fotogramas y fragmentos sin multiplexar de audio o video codificados. Para obtener acceso de bajo nivel a estos componentes básicos, los desarrolladores han estado usando WebAssembly para incorporar códecs de audio y video en el navegador. Sin embargo, dado que los navegadores modernos ya incluyen una variedad de códecs (que a menudo se aceleran por hardware), volver a empaquetarlos como WebAssembly parece un desperdicio de recursos humanos y de computadoras.
La API de WebCodecs elimina esta ineficiencia, ya que les brinda a los programadores una forma de usar componentes multimedia que ya están presentes en el navegador. En particular, haz lo siguiente:
- Decodificadores de audio y video
- Codificadores de audio y video
- Fotogramas de video sin procesar
- Decodificadores de imágenes
La API de WebCodecs es útil para las aplicaciones web que requieren un control total sobre la forma en que se procesa el contenido multimedia, como los editores de video, las videoconferencias, la transmisión de video, etcétera.
Flujo de trabajo de procesamiento de video
Los fotogramas son la pieza central del procesamiento de video. Por lo tanto, en WebCodecs, la mayoría de las clases consumen o producen fotogramas. Los codificadores de video convierten los fotogramas en fragmentos codificados. Los decodificadores de video hacen lo contrario.
Además, VideoFrame
funciona bien con otras APIs web, ya que es un CanvasImageSource
y tiene un constructor que acepta CanvasImageSource
.
Por lo tanto, se puede usar en funciones como drawImage()
ytexImage2D()
. También se puede construir a partir de lienzos, mapas de bits, elementos de video y otros fotogramas de video.
La API de WebCodecs funciona bien en conjunto con las clases de la API de Insertable Streams, que conectan WebCodecs a los tracks de transmisión de medios.
MediaStreamTrackProcessor
divide las pistas de medios en fotogramas individuales.MediaStreamTrackGenerator
crea un segmento de medios a partir de un flujo de fotogramas.
WebCodecs y Web Workers
Por diseño, la API de WebCodecs realiza todo el trabajo pesado de forma asíncrona y fuera del subproceso principal. Sin embargo, dado que las devoluciones de llamada de fotogramas y fragmentos a menudo se pueden llamar varias veces por segundo, pueden sobrecargar el subproceso principal y, por lo tanto, hacer que el sitio web sea menos responsivo. Por lo tanto, es preferible trasladar el procesamiento de los fotogramas individuales y los fragmentos codificados a un trabajador web.
Para ayudar con eso, ReadableStream proporciona una forma conveniente de transferir automáticamente todos los fotogramas provenientes de un segmento de medios al trabajador. Por ejemplo, MediaStreamTrackProcessor
se puede usar para obtener un ReadableStream
para un segmento de pista de transmisión de medios proveniente de la cámara web. Después de eso, la transmisión se transfiere a un trabajador web en el que los fotogramas se leen uno por uno y se ponen en cola en un VideoEncoder
.
Con HTMLCanvasElement.transferControlToOffscreen
, incluso la renderización se puede realizar fuera del subproceso principal. Sin embargo, si todas las herramientas de alto nivel resultan ser inconvenientes, VideoFrame
en sí es transferible y se puede mover entre los trabajadores.
WebCodecs en acción
Codificación

Canvas
o un ImageBitmap
a la red o al almacenamientoTodo comienza con un VideoFrame
.
Existen tres formas de construir fotogramas de video.
Desde una fuente de imagen, como un lienzo, un mapa de bits de imagen o un elemento de video
const canvas = document.createElement("canvas"); // Draw something on the canvas... const frameFromCanvas = new VideoFrame(canvas, { timestamp: 0 });
Usa
MediaStreamTrackProcessor
para extraer fotogramas de unMediaStreamTrack
const stream = await navigator.mediaDevices.getUserMedia({…}); const track = stream.getTracks()[0]; const trackProcessor = new MediaStreamTrackProcessor(track); const reader = trackProcessor.readable.getReader(); while (true) { const result = await reader.read(); if (result.done) break; const frameFromCamera = result.value; }
Crea un fotograma a partir de su representación de píxeles binaria en un
BufferSource
const pixelSize = 4; const init = { timestamp: 0, codedWidth: 320, codedHeight: 200, format: "RGBA", }; const data = new Uint8Array(init.codedWidth * init.codedHeight * pixelSize); for (let x = 0; x < init.codedWidth; x++) { for (let y = 0; y < init.codedHeight; y++) { const offset = (y * init.codedWidth + x) * pixelSize; data[offset] = 0x7f; // Red data[offset + 1] = 0xff; // Green data[offset + 2] = 0xd4; // Blue data[offset + 3] = 0x0ff; // Alpha } } const frame = new VideoFrame(data, init);
Sin importar de dónde provengan, los fotogramas se pueden codificar en objetos EncodedVideoChunk
con un VideoEncoder
.
Antes de la codificación, se deben proporcionar dos objetos JavaScript a VideoEncoder
:
- Inicializa el diccionario con dos funciones para controlar errores y fragmentos codificados. Estas funciones las define el desarrollador y no se pueden cambiar después de pasarlas al constructor
VideoEncoder
. - Objeto de configuración del codificador, que contiene parámetros para el flujo de video de salida. Puedes cambiar estos parámetros más adelante llamando a
configure()
.
El método configure()
arrojará NotSupportedError
si el navegador no admite la configuración. Te recomendamos que llames al método estático VideoEncoder.isConfigSupported()
con la configuración para verificar de antemano si se admite y esperar su promesa.
const init = {
output: handleChunk,
error: (e) => {
console.log(e.message);
},
};
const config = {
codec: "vp8",
width: 640,
height: 480,
bitrate: 2_000_000, // 2 Mbps
framerate: 30,
};
const { supported } = await VideoEncoder.isConfigSupported(config);
if (supported) {
const encoder = new VideoEncoder(init);
encoder.configure(config);
} else {
// Try another config.
}
Una vez que se configura el codificador, está listo para aceptar fotogramas a través del método encode()
.
Tanto configure()
como encode()
se muestran de inmediato sin esperar a que se complete el trabajo real. Permite que varios fotogramas se pongan en cola para la codificación al mismo tiempo, mientras que encodeQueueSize
muestra cuántas solicitudes están esperando en la cola a que finalicen las codificaciones anteriores.
Los errores se informan lanzando una excepción de inmediato, en caso de que los argumentos o el orden de las llamadas a métodos incumplan el contrato de la API, o llamando a la devolución de llamada error()
para los problemas encontrados en la implementación del códec.
Si la codificación se completa correctamente, se llama a la devolución de llamada output()
con un nuevo fragmento codificado como argumento.
Otro detalle importante es que se debe indicar a los fotogramas cuándo ya no son necesarios llamando a close()
.
let frameCounter = 0;
const track = stream.getVideoTracks()[0];
const trackProcessor = new MediaStreamTrackProcessor(track);
const reader = trackProcessor.readable.getReader();
while (true) {
const result = await reader.read();
if (result.done) break;
const frame = result.value;
if (encoder.encodeQueueSize > 2) {
// Too many frames in flight, encoder is overwhelmed
// let's drop this frame.
frame.close();
} else {
frameCounter++;
const keyFrame = frameCounter % 150 == 0;
encoder.encode(frame, { keyFrame });
frame.close();
}
}
Por último, es hora de terminar el código de codificación escribiendo una función que controle los fragmentos de video codificado a medida que salen del codificador. Por lo general, esta función enviaría fragmentos de datos a través de la red o los multiplexaría en un contenedor de medios para su almacenamiento.
function handleChunk(chunk, metadata) {
if (metadata.decoderConfig) {
// Decoder needs to be configured (or reconfigured) with new parameters
// when metadata has a new decoderConfig.
// Usually it happens in the beginning or when the encoder has a new
// codec specific binary configuration. (VideoDecoderConfig.description).
fetch("/upload_extra_data", {
method: "POST",
headers: { "Content-Type": "application/octet-stream" },
body: metadata.decoderConfig.description,
});
}
// actual bytes of encoded data
const chunkData = new Uint8Array(chunk.byteLength);
chunk.copyTo(chunkData);
fetch(`/upload_chunk?timestamp=${chunk.timestamp}&type=${chunk.type}`, {
method: "POST",
headers: { "Content-Type": "application/octet-stream" },
body: chunkData,
});
}
Si en algún momento necesitas asegurarte de que se hayan completado todas las solicitudes de codificación pendientes, puedes llamar a flush()
y esperar su promesa.
await encoder.flush();
Decodificación

Canvas
o un ImageBitmap
.Configurar un VideoDecoder
es similar a lo que se hizo para el VideoEncoder
: se pasan dos funciones cuando se crea el decodificador y se proporcionan parámetros de códec a configure()
.
El conjunto de parámetros del códec varía de un códec a otro. Por ejemplo, el códec H.264 podría necesitar un blob binario de AVCC, a menos que esté codificado en el llamado formato Annex B (encoderConfig.avc = { format: "annexb" }
).
const init = {
output: handleFrame,
error: (e) => {
console.log(e.message);
},
};
const config = {
codec: "vp8",
codedWidth: 640,
codedHeight: 480,
};
const { supported } = await VideoDecoder.isConfigSupported(config);
if (supported) {
const decoder = new VideoDecoder(init);
decoder.configure(config);
} else {
// Try another config.
}
Una vez que se inicializa el decodificador, puedes comenzar a alimentarlo con objetos EncodedVideoChunk
.
Para crear un fragmento, necesitarás lo siguiente:
- Un
BufferSource
de datos de video codificados - Marca de tiempo de inicio del fragmento en microsegundos (tiempo de los medios del primer fotograma codificado en el fragmento)
- Tipo del fragmento, uno de los siguientes:
key
si el fragmento se puede decodificar de forma independiente de los fragmentos anterioresdelta
si el fragmento solo se puede decodificar después de que se decodificaron uno o más fragmentos anteriores
Además, todos los fragmentos que emite el codificador están listos para el decodificador tal como están. Todo lo que se dijo anteriormente sobre los informes de errores y la naturaleza asíncrona de los métodos del codificador también se aplica a los decodificadores.
const responses = await downloadVideoChunksFromServer(timestamp);
for (let i = 0; i < responses.length; i++) {
const chunk = new EncodedVideoChunk({
timestamp: responses[i].timestamp,
type: responses[i].key ? "key" : "delta",
data: new Uint8Array(responses[i].body),
});
decoder.decode(chunk);
}
await decoder.flush();
Ahora es el momento de mostrar cómo se puede mostrar un fotograma recién decodificado en la página. Es mejor asegurarse de que la devolución de llamada de salida del decodificador (handleFrame()
) se devuelva rápidamente. En el siguiente ejemplo, solo se agrega un fotograma a la cola de fotogramas listos para la renderización.
La renderización se realiza por separado y consta de dos pasos:
- Espera el momento adecuado para mostrar el fotograma.
- Dibuja el marco en el lienzo.
Una vez que ya no se necesite un fotograma, llama a close()
para liberar la memoria subyacente antes de que el recolector de basura llegue a ella. Esto reducirá la cantidad promedio de memoria que usa la aplicación web.
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
let pendingFrames = [];
let underflow = true;
let baseTime = 0;
function handleFrame(frame) {
pendingFrames.push(frame);
if (underflow) setTimeout(renderFrame, 0);
}
function calculateTimeUntilNextFrame(timestamp) {
if (baseTime == 0) baseTime = performance.now();
let mediaTime = performance.now() - baseTime;
return Math.max(0, timestamp / 1000 - mediaTime);
}
async function renderFrame() {
underflow = pendingFrames.length == 0;
if (underflow) return;
const frame = pendingFrames.shift();
// Based on the frame's timestamp calculate how much of real time waiting
// is needed before showing the next frame.
const timeUntilNextFrame = calculateTimeUntilNextFrame(frame.timestamp);
await new Promise((r) => {
setTimeout(r, timeUntilNextFrame);
});
ctx.drawImage(frame, 0, 0);
frame.close();
// Immediately schedule rendering of the next frame
setTimeout(renderFrame, 0);
}
Sugerencias para desarrolladores
Usa el panel de medios en las Herramientas para desarrolladores de Chrome para ver los registros de medios y depurar WebCodecs.

Demostración
En la demostración, se muestra cómo los fotogramas de animación de un lienzo se:
- capturado a 25 fps en un
ReadableStream
porMediaStreamTrackProcessor
- Se transfirió a un trabajador web.
- Codificado en formato de video H.264
- se vuelve a decodificar en una secuencia de fotogramas de video
- y se renderiza en el segundo lienzo con
transferControlToOffscreen()
Otras demostraciones
También puedes consultar nuestras otras demostraciones:
- Cómo decodificar GIFs con ImageDecoder
- Cómo capturar la entrada de la cámara en un archivo
- Reproducción de MP4
- Otras muestras
Cómo usar la API de WebCodecs
Detección de características
Para verificar la compatibilidad con WebCodecs, haz lo siguiente:
if ('VideoEncoder' in window) {
// WebCodecs API is supported.
}
Ten en cuenta que la API de WebCodecs solo está disponible en contextos seguros, por lo que la detección fallará si self.isSecureContext
es falso.
Comentarios
El equipo de Chrome quiere conocer tu experiencia con la API de WebCodecs.
Cuéntanos sobre el diseño de la API
¿Hay algo sobre la API que no funciona como esperabas? ¿O faltan métodos o propiedades que necesitas para implementar tu idea? ¿Tienes alguna pregunta o comentario sobre el modelo de seguridad? Informa un problema de especificación en el repositorio de GitHub correspondiente o agrega tus ideas a un problema existente.
Informa un problema con la implementación
¿Encontraste un error en la implementación de Chrome? ¿O la implementación es diferente de las especificaciones? Presenta un error en new.crbug.com. Asegúrate de incluir tantos detalles como sea posible, instrucciones sencillas para reproducir el error y, luego, ingresa Blink>Media>WebCodecs
en el cuadro Components.
Cómo mostrar compatibilidad con la API
¿Piensas usar la API de WebCodecs? Tu apoyo público ayuda al equipo de Chrome a priorizar funciones y muestra a otros proveedores de navegadores lo importante que es admitirlas.
Envía correos electrónicos a media-dev@chromium.org o un tweet a @ChromiumDev con el hashtag #WebCodecs
y cuéntanos dónde y cómo lo usas.