Manipolare i componenti dello stream video.
Le moderne tecnologie web offrono molti modi per lavorare con i video. API Media Stream, l'API Media Recording, API Media Source, e l'API WebRTC si sommano a un ricco set di strumenti per la registrazione, il trasferimento e la riproduzione di stream video. Queste API, pur risolvendo alcune attività di alto livello, i programmatori lavorano con i singoli componenti di uno stream video, come i fotogrammi frammenti di video o audio codificati e non associati. Per ottenere un accesso di basso livello a questi componenti di base, gli sviluppatori hanno usato WebAssembly per integrare codec video e audio nel browser. Ma dato i browser moderni dispongono già di una varietà di codec (spesso accelerata dall'hardware), ripacchettizzandole come WebAssembly sembra uno spreco di risorse umane e informatiche.
L'API WebCodecs elimina questa inefficienza dando ai programmatori un modo per usare i componenti multimediali già presenti il browser. In particolare:
- Decodificatori video e audio
- Codificatori video e audio
- Fotogrammi video non elaborati
- Decodificatori di immagini
L'API WebCodecs è utile per le applicazioni web che richiedono il controllo completo del modalità di elaborazione dei contenuti multimediali, ad esempio editor video, videoconferenze, streaming e così via.
Flusso di lavoro di elaborazione dei video
I fotogrammi sono il fulcro dell'elaborazione video. Di conseguenza, in WebCodec gran parte delle classi consumare o produrre frame. I codificatori video convertono i fotogrammi in o blocchi di testo. I decoder video fanno il contrario.
Inoltre, VideoFrame
funziona perfettamente con altre API web in quanto è un CanvasImageSource
e ha un costruttore che accetta CanvasImageSource
.
Pertanto, può essere utilizzata in funzioni come drawImage()
e texImage2D()
. Può anche essere creato da canvas, bitmap, elementi video e altri fotogrammi.
L'API WebCodecs funziona bene insieme alle classi dell'API Insertable Streams che collegano i WebCodec alle tracce degli stream multimediali.
MediaStreamTrackProcessor
suddivide le tracce multimediali in singoli frame.MediaStreamTrackGenerator
crea una traccia multimediale da uno stream di frame.
WebCodec e web worker
L'API WebCodecs si occupa di tutte le operazioni più gravose in modo asincrono e al di fuori del thread principale. Ma poiché i callback frame e chunk possono essere chiamati più volte al secondo, potrebbero ingombrare il thread principale e rendere il sito web meno reattivo. Perciò è preferibile spostare la gestione dei singoli frame e di blocchi codificati in un di Google.
Per aiutarti, ReadableStream
offre un modo conveniente per trasferire automaticamente tutti i frame provenienti da un contenuto multimediale
il tracciamento per il worker. Ad esempio, MediaStreamTrackProcessor
può essere utilizzato per ottenere un
ReadableStream
per una traccia di stream multimediale proveniente dalla webcam. In seguito
lo stream viene trasferito a un worker web dove i frame vengono letti uno alla volta e messi in coda
in un VideoEncoder
.
Con HTMLCanvasElement.transferControlToOffscreen
è possibile eseguire il rendering anche dal thread principale. Ma se tutti gli strumenti di alto livello
non sia conveniente, pertanto l'entità VideoFrame
stessa è trasferibile e potrebbe essere
si spostano da un worker all'altro.
WebCodec in azione
Codifica
Tutto inizia con un VideoFrame
.
Esistono tre modi per creare fotogrammi video.
Da un'origine immagine, ad esempio un canvas, una bitmap dell'immagine o un elemento video.
const canvas = document.createElement("canvas"); // Draw something on the canvas... const frameFromCanvas = new VideoFrame(canvas, { timestamp: 0 });
Usa
MediaStreamTrackProcessor
per eseguire il pull dei 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; }
Consente di creare un frame dalla relativa rappresentazione in pixel binari in un file
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
EncodedVideoChunk
oggetti con VideoEncoder
.
Prima della codifica, a VideoEncoder
devono essere assegnati due oggetti JavaScript:
- Inizia 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
vengono passate al costruttore
VideoEncoder
. - Oggetto di configurazione encoder, che contiene i parametri per l'output
stream video. Puoi modificare questi parametri in un secondo momento richiamando
configure()
.
Il metodo configure()
restituisce NotSupportedError
se la configurazione non è
supportate dal browser. Ti invitiamo a chiamare il metodo statico
VideoEncoder.isConfigSupported()
con la configurazione per verificare in anticipo se
la configurazione è supportata e attendere la 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.
}
Dopo la configurazione, il codificatore è pronto per accettare frame con il metodo encode()
.
Sia configure()
che encode()
ritornano immediatamente senza attendere il
il lavoro effettivo da completare. Consente a diversi frame di inserire i dati in coda per la codifica
contemporaneamente, mentre encodeQueueSize
mostra quante richieste sono in attesa in coda
per le codifica precedenti.
Gli errori vengono segnalati generando immediatamente un'eccezione, se gli argomenti
o l'ordine delle chiamate ai metodi viola il contratto API oppure chiamando il error()
per problemi riscontrati nell'implementazione del codec.
Se la codifica ha esito positivo, output()
viene chiamato con un nuovo blocco codificato come argomento.
Un altro dettaglio importante è che i frame devono essere informati quando non sono
più necessario chiamando il numero 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 frammenti di video codificati quando escono dal codificatore. Di solito questa funzione invia blocchi di dati sulla rete o li mux in un supporto container 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
è stata completata, puoi chiamare flush()
e attendere la promessa.
await encoder.flush();
Decodifica
La configurazione di VideoDecoder
è simile a quella che è stata eseguita per
VideoEncoder
: vengono passate due funzioni al momento della creazione del decoder e il codec
vengono assegnati a configure()
.
L'insieme di parametri del codec varia da codec a codec. Ad esempio codec H.264
potrebbe aver bisogno di un blob binario
di AVCC, a meno che non siano codificati nel cosiddetto formato Allegato 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 decoder, puoi iniziare a alimentarlo con oggetti EncodedVideoChunk
.
Per creare un blocco, avrai bisogno di:
- Un
BufferSource
di dati video codificati - il timestamp di inizio del blocco in microsecondi (tempo dei media del primo frame codificato nel blocco)
- il tipo di chunk, uno dei seguenti:
key
se il blocco può essere decodificato indipendentemente dai blocchi precedentidelta
se il blocco può essere decodificato solo dopo che uno o più blocchi precedenti sono stati decodificati
Inoltre, tutti i blocchi emessi dall'encoder sono pronti per il decoder così come sono. Tutte le considerazioni precedenti relative alla segnalazione degli errori e alla natura asincrona dei metodi dell'encoder sono ugualmente veri anche per i decoder.
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 può essere mostrato nella pagina un frame appena decodificato. È
è meglio assicurarsi che il callback di output del decoder (handleFrame()
)
che ritorna rapidamente. Nell'esempio riportato di seguito, viene aggiunto solo un frame alla coda di
pronti per il rendering.
Il rendering viene eseguito separatamente ed è costituito da due passaggi:
- In attesa del momento giusto per mostrare il frame.
- Disegno della cornice sull'area di lavoro.
Quando un frame non è più necessario, chiama close()
per rilasciare la memoria sottostante
prima che il garbage collector vi acceda, si ridurrà la quantità media
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 Contenuti multimediali in Chrome DevTools per visualizzare i log multimediali ed eseguire il debug dei WebCodec.
Demo
La demo riportata di seguito mostra come sono i frame di animazione da un canvas:
- acquisito a 25 f/s in
ReadableStream
diMediaStreamTrackProcessor
- trasferito a un lavoratore web
- con codifica nel formato video H.264
- sono nuovamente decodificati in una sequenza di fotogrammi
- e visualizzato sul secondo canvas utilizzando
transferControlToOffscreen()
Altre demo
Guarda anche le altre nostre demo:
- Decodificare le GIF con ImageDecoder
- Acquisire l'input della fotocamera in un file
- Riproduzione di MP4
- Altri campioni
Utilizzo dell'API WebCodecs
Rilevamento delle caratteristiche
Per verificare il supporto dei WebCodec:
if ('VideoEncoder' in window) {
// WebCodecs API is supported.
}
Tieni presente che l'API WebCodecs è disponibile solo in contesti sicuri,
pertanto 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.
Parlaci della progettazione dell'API
C'è qualcosa nell'API che non funziona come previsto? Oppure sono mancano metodi o proprietà per implementare la tua idea? Avere un domanda o commento sul modello di sicurezza? Segnala un problema con le specifiche sul un repository GitHub corrispondente oppure aggiungi le tue opinioni su un problema esistente.
Segnalare un problema con l'implementazione
Hai trovato un bug nell'implementazione di Chrome? Oppure l'implementazione
rispetto alle specifiche? Segnala un bug all'indirizzo new.crbug.com.
Includi il maggior numero di dettagli possibile, istruzioni semplici per
viene riprodotto e inserisci Blink>Media>WebCodecs
nella casella Componenti.
Glitch è la soluzione perfetta per condividere riproduzioni in modo facile e veloce.
Mostra il supporto per l'API
Intendi utilizzare l'API WebCodecs? Il tuo supporto pubblico aiuta Il team di Chrome assegna la priorità alle funzionalità e mostra ad altri fornitori di browser quanto sono importanti è sostenerli.
Invia email a media-dev@chromium.org o invia un tweet
a @ChromiumDev utilizzando l'hashtag
#WebCodecs
e facci sapere dove e come lo utilizzi.
Immagine hero di Denise Jans su Unsplash.