Manipolazione dei componenti dello stream video.
Le moderne tecnologie web offrono molti modi per lavorare con i video. L'API Media Stream, l'API Media Recording, l'API Media Source e l'API WebRTC costituiscono un ricco set di strumenti per registrare, trasferire e riprodurre flussi video. Durante la risoluzione di determinate attività di alto livello, queste API non consentono ai programmatori web di lavorare con singoli componenti di un flusso video, come frame e blocchi demuxed di video o audio codificati. Per ottenere l'accesso di basso livello a questi componenti di base, gli sviluppatori hanno utilizzato WebAssembly per portare i codec video e audio nel browser. Tuttavia, dato che i browser moderni vengono già forniti con una serie di codec (spesso accelerati dall'hardware), il loro riconfezionamento come WebAssembly sembra uno spreco di risorse umane e informatiche.
L'API WebCodecs elimina questa inefficienza offrendo ai programmatori un modo per utilizzare i componenti multimediali già presenti nel browser. In particolare:
- Decoder video e audio
- Encoder video e audio
- Fotogrammi video non elaborati
- Decoder di immagini
L'API WebCodecs è utile per le applicazioni web che richiedono il controllo completo sul modo in cui vengono elaborati i contenuti multimediali, ad esempio editor video, videoconferenze, streaming video e così via.
Flusso di lavoro di elaborazione video
I fotogrammi sono l'elemento centrale dell'elaborazione video. Pertanto, in WebCodecs la maggior parte delle classi consuma o produce frame. I codificatori video convertono i frame in blocchi codificati. I decoder video fanno il contrario.
Inoltre, VideoFrame
funziona bene con altre API web perché è un CanvasImageSource
e ha un costruttore che accetta CanvasImageSource
. Pertanto, può essere utilizzato in funzioni come drawImage()
etexImage2D()
. Inoltre, può essere costruito a partire da canvas, bitmap, elementi video e altri fotogrammi video.
L'API WebCodecs funziona bene in tandem con le classi dell'API Insertable Streams, che collega WebCodecs alle tracce di stream multimediali.
MediaStreamTrackProcessor
suddivide le tracce multimediali in singoli fotogrammi.MediaStreamTrackGenerator
crea una traccia multimediale da un flusso di frame.
WebCodecs e web worker
Per progettazione, l'API WebCodecs esegue tutte le operazioni pesanti in modo asincrono e al di fuori del thread principale. Tuttavia, poiché i callback di frame e chunk possono essere chiamati più volte al secondo, potrebbero ingombrare il thread principale e quindi rendere il sito web meno reattivo. Pertanto, è preferibile spostare la gestione dei singoli frame e dei blocchi codificati in un web worker.
Per facilitare questa operazione, ReadableStream offre un modo pratico per trasferire automaticamente tutti i frame provenienti da una traccia multimediale al worker. Ad esempio, MediaStreamTrackProcessor
può essere utilizzato per ottenere un ReadableStream
per una traccia di flusso multimediale proveniente dalla webcam. Dopodiché il flusso viene trasferito a un web worker in cui i frame vengono letti uno alla volta e accodati in un VideoEncoder
.
Con HTMLCanvasElement.transferControlToOffscreen
, anche il rendering può essere eseguito al di fuori del thread principale. Ma se tutti gli strumenti di alto livello si rivelano scomodi, VideoFrame
stesso è trasferibile e può essere spostato tra i lavoratori.
WebCodecs in azione
Codifica

Canvas
o da un ImageBitmap
alla rete o allo spazio di archiviazioneTutto inizia con un VideoFrame
. Esistono tre modi per costruire i fotogrammi video.
Da un'origine immagine come un canvas, un bitmap di immagine o un elemento video.
const canvas = document.createElement("canvas"); // Draw something on the canvas... const frameFromCanvas = new VideoFrame(canvas, { timestamp: 0 });
Utilizzare
MediaStreamTrackProcessor
per estrarre i frame da 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 frame dalla sua rappresentazione binaria dei pixel in 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);
Indipendentemente dalla loro provenienza, i frame possono essere codificati in oggetti EncodedVideoChunk
con un VideoEncoder
.
Prima della codifica, a VideoEncoder
devono essere forniti due oggetti JavaScript:
- Inizializza il dizionario con due funzioni per la gestione di blocchi codificati e errori. Queste funzioni sono definite dallo sviluppatore e non possono essere modificate dopo essere state passate al costruttore
VideoEncoder
. - Oggetto di configurazione del codificatore, che contiene i parametri per lo stream video di output. Puoi modificare questi parametri in un secondo momento chiamando il numero
configure()
.
Il metodo configure()
genererà NotSupportedError
se la configurazione non è supportata dal browser. Ti consigliamo di chiamare il metodo statico VideoEncoder.isConfigSupported()
con la configurazione per verificare in anticipo se la configurazione è supportata e attendere la relativa promessa.
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 volta configurato, il codificatore è pronto per accettare i frame tramite il metodo encode()
. Sia configure()
che encode()
vengono restituiti immediatamente senza attendere il completamento dell'effettivo lavoro. Consente di mettere in coda più frame per la codifica contemporaneamente, mentre encodeQueueSize
mostra quante richieste sono in attesa nella coda per il completamento delle codifiche precedenti. Gli errori vengono segnalati generando immediatamente un'eccezione, nel caso in cui gli argomenti o l'ordine delle chiamate ai metodi violino il contratto API, oppure chiamando il callback error()
per i problemi riscontrati nell'implementazione del codec. Se la codifica viene completata correttamente, viene chiamato il callback output()
con un nuovo blocco codificato come argomento. Un altro dettaglio importante è che ai frame deve essere comunicato quando non sono più necessari chiamando 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(); } }
Infine, è il momento di completare il codice di codifica scrivendo una funzione che gestisca i blocchi di video codificati man mano che vengono generati dal codificatore. In genere, questa funzione invia blocchi di dati sulla rete o li multiplexa in un contenitore multimediale per l'archiviazione.
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, }); }
Se a un certo punto devi assicurarti che tutte le richieste di codifica in attesa siano state completate, puoi chiamare flush()
e attendere la sua promessa.
await encoder.flush();
Decodifica

Canvas
o a un ImageBitmap
.La configurazione di un VideoDecoder
è simile a quella eseguita per VideoEncoder
: vengono passate due funzioni quando viene creato il decodificatore e i parametri del codec vengono forniti a configure()
.
Il set di parametri del codec varia da codec a codec. Ad esempio, il codec H.264 potrebbe richiedere un blob binario di AVCC, a meno che non sia codificato nel cosiddetto 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 volta inizializzato il decodificatore, puoi iniziare a inserire gli oggetti EncodedVideoChunk
. Per creare un segmento, ti serviranno:
- Un
BufferSource
di dati video codificati - il timestamp di inizio del chunk in microsecondi (tempo multimediale del primo frame codificato nel chunk)
- il tipo di blocco, uno dei seguenti:
key
se il blocco può essere decodificato indipendentemente dai blocchi precedentidelta
se il blocco può essere decodificato solo dopo che sono stati decodificati uno o più blocchi precedenti
Inoltre, tutti i blocchi emessi dal codificatore sono pronti per il decodificatore così come sono. Tutto ciò che è stato detto in precedenza sulla segnalazione degli errori e sulla natura asincrona dei metodi del codificatore vale anche per i decodificatori.
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();
Ora è il momento di mostrare come un frame appena decodificato può essere visualizzato nella pagina. È meglio assicurarsi che il callback dell'output del decoder (handleFrame()
) venga restituito rapidamente. Nell'esempio riportato di seguito, viene aggiunto solo un frame alla coda di frame pronti per il rendering. Il rendering viene eseguito separatamente ed è costituito da due passaggi:
- In attesa del momento giusto per mostrare il frame.
- Disegno del frame sul canvas.
Quando un frame non è più necessario, chiama close()
per rilasciare la memoria sottostante prima che venga eseguita la garbage collection, in modo da ridurre la quantità media di memoria utilizzata dall'applicazione 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); }
Suggerimenti per gli sviluppatori
Utilizza il riquadro Media in Chrome DevTools per visualizzare i log multimediali ed eseguire il debug di WebCodecs.

Demo
La demo mostra come i frame di animazione di un canvas vengono:
- acquisito a 25 fps in un
ReadableStream
daMediaStreamTrackProcessor
- trasferito a un web worker
- codificati nel formato video H.264
- decodificata di nuovo in una sequenza di fotogrammi video
- e visualizzato sulla seconda tela utilizzando
transferControlToOffscreen()
Altre demo
Dai un'occhiata anche alle altre nostre demo:
- Decodifica delle GIF con ImageDecoder
- Acquisire l'input della videocamera in un file
- Riproduzione MP4
- Altri esempi
Utilizzo dell'API WebCodecs
Rilevamento delle funzionalità
Per verificare il supporto di WebCodecs:
if ('VideoEncoder' in window) { // WebCodecs API is supported. }
Tieni presente che l'API WebCodecs è disponibile solo in contesti sicuri, quindi il rilevamento non andrà a buon fine se self.isSecureContext
è false.
Feedback
Il team di Chrome vuole conoscere la tua esperienza con l'API WebCodecs.
Descrivi la progettazione dell'API
C'è qualcosa nell'API che non funziona come previsto? Oppure mancano metodi o proprietà che ti servono per implementare la tua idea? Hai una domanda o un commento sul modello di sicurezza? Invia una segnalazione relativa alle specifiche nel repository GitHub corrispondente o aggiungi i tuoi commenti a una segnalazione esistente.
Segnalare un problema relativo all'implementazione
Hai trovato un bug nell'implementazione di Chrome? O l'implementazione è diversa dalla specifica? Segnala un bug all'indirizzo new.crbug.com. Assicurati di includere il maggior numero possibile di dettagli, istruzioni semplici per la riproduzione e inserisci Blink>Media>WebCodecs
nella casella Componenti.
Mostra il tuo sostegno all'API
Intendi utilizzare l'API WebCodecs? Il tuo supporto pubblico aiuta il team di Chrome a dare la priorità alle funzionalità e mostra ad altri fornitori di browser quanto sia fondamentale supportarle.
Invia email all'indirizzo [email protected] o invia un tweet a @ChromiumDev utilizzando l'hashtag #WebCodecs
e facci sapere dove e come lo utilizzi.
Hero image di Denise Jans su Unsplash.