[ Web Proxy ]
URL:
Viewing: https://developer.mozilla.org/de/docs/Web/API/WebCodecs_API/Using_the_WebCodecs_API [Back]  [Original]

Verwendung der WebCodecs API - Web-APIs | MDN

Dieser Inhalt wurde automatisch aus dem Englischen bersetzt, und kann Fehler enthalten. Erfahre mehr ber dieses Experiment.

View in English Always switch to English

Verwendung der WebCodecs API

Dieser Leitfaden behandelt die grundlegenden Nutzungsmuster der WebCodecs API, einschlielich der Codierung und Dekodierung von Video und Audio sowie der Verwendung von VideoFrame und AudioData.

In diesem Artikel

Videokodierung

Das grundlegende Nutzungsmuster fr VideoEncoder beginnt mit der Instanziierung, bei der Sie die output- und error-Callback-Funktionen definieren. Das output-Callback erhlt ein EncodedVideoChunk und einen metadata-Parameter ein EncodedVideoChunkMetadata-Wrterbuch, das eine optionale decoderConfig-Eigenschaft enthlt. Diese Metadaten werden von Multiplexing-Bibliotheken bentigt, wenn sie in eine Videodatei gemuxt werden sollen.

js
const encoder = new VideoEncoder({
  output(chunk, meta) {
    // Do something with chunk, typically send to muxing library
  },
  error(e) {
    // Handle the error
  },
});

Anschlieend mssen Sie den Encoder mit dem Codec-Parameter und verschiedenen anderen Kodierungsparametern wie Breite, Hhe, Bitrate und Bildrate konfigurieren. Siehe den Leitfaden zur Codec-Auswahl fr Hinweise zur Auswahl eines Codecs.

js
encoder.configure({
  codec: "vp09.00.40.08.00", // See codec selection guide
  width: 1280,
  height: 720,
  bitrate: 1_000_000, // 1 Mbps
  framerate: 30,
});

Dann wrden Sie damit beginnen, VideoFrame-Objekte zu kodieren, wobei Sie nicht nur das zu kodierende VideoFrame angeben, sondern auch den keyFrame-Parameter, der angibt, ob das Frame als Keyframe kodiert werden soll oder nicht.

js
for (let i = 0; i < 60; i++) {
  const timestamp = (i * 1e6) / 30; // 30 fps, in microseconds
  const frame = new VideoFrame(canvas, { timestamp });
  encoder.encode(frame, { keyFrame: i % 60 === 0 });
  frame.close();
}

Das erste kodierte Frame sollte ein Keyframe sein whrend VideoEncoder das erste Frame automatisch als Keyframe erzwingen wird, selbst wenn es nicht explizit gekennzeichnet ist, ist es eine gute Praxis, es explizit festzulegen. Typische Keyframe-Intervalle liegen bei ein Mal alle 30 oder 60 Frames. Die Verwendung von mehr Keyframes erhht die Dateigre des Videos, whrend die Verwendung von weniger Keyframes zu instabiler Videowiedergabe bei einigen Videoplayern fhren kann.

Es ist wichtig, VideoFrame-Objekte sofort nach dem Senden zur Kodierung zu schlieen, um Speicherlecks zu vermeiden. VideoFrame-Objekte sind gro genug, dass Anwendungen abstrzen knnen, wenn weniger als 100 aktive Frames im Speicher sind.

Beachten Sie, dass VideoEncoder auch eine Warteschlange fr zu kodierende Frames hat, die encodeQueue genannt wird. Wenn Sie eine Animation mit 30 fps rendern, fhren Sie encoder.encode(frame) bei jedem Rendern aus, aber der Encoder kann nur mit 10 fps kodieren, dann wird die Encoder-Warteschlange schlielich so lange wachsen, bis sie keinen Videospeicher mehr hat und der Prozess abstrzt.

Sie mssen daher verwalten, wie und wann Sie Frames an den Encoder senden, indem Sie innerhalb Ihrer Render-Schleife die VideoEncoder.encodeQueueSize berprfen und sicherstellen, dass sie nicht ungebunden wchst.

Es ist mglich, das dequeue-Ereignis zu verwenden, um zu erkennen, wann die Encode-Warteschlange reduziert wird, um das Abfragen der encodeQueueSize zu vermeiden.

js
encoder.addEventListener("dequeue", (event) => {
  // Queue up more encoding work
});

Sobald Sie alle Frames zur Kodierung gesendet haben, sollten Sie die flush()-Methode aufrufen.

js
await encoder.flush();

Je nach Gert/Browser gibt der Encoder mglicherweise die letzten EncodedVideoChunk-Objekte nicht zurck, bis flush() aufgerufen wird. Sobald Sie die VideoEncoder-Nutzung vollstndig abgeschlossen haben, sollten Sie die close()-Methode aufrufen, um Systemressourcen freizugeben.

js
encoder.close();

Ein VideoEncoder kann whrend der Kodierung aus verschiedenen Grnden einen Fehler auslsen, wie etwa wenn der Benutzer den Tab wechselt und der Browser die Ressourcen wiedererlangt. Wenn ein Fehler auftritt, wechselt der Encoder dauerhaft in den Zustand "closed". Es ist nicht mglich, einen geschlossenen Encoder neu zu konfigurieren eine neue VideoEncoder-Instanz muss erstellt werden. Das erste von dem neuen Encoder kodierte Frame muss ein Keyframe sein.

js
if (encoder.state === "closed") {
  // Close the old encoder, instantiate and configure a new encoder
}

encoder.encode(frame, { keyFrame: true });

Videodekodierung

Ebenso beginnen Sie bei der Dekodierung von Videos mit der Instanziierung des VideoDecoder mit den output- und error-Callback-Funktionen, wobei das output-Callback VideoFrame-Objekte erhlt, die vom Decoder zurckgegeben werden.

js
const decoder = new VideoDecoder({
  output(frame) {
    // Do something with the VideoFrame
  },
  error(e) {
    /** Handle the error */
  },
});

Anschlieend mssen Sie den Decoder konfigurieren. Wenn Sie eine Videodatei dekodieren, kann eine Demultiplex-Bibliothek die richtige Decoder-Konfiguration bereitstellen (siehe Muxing und Demuxing). Beim Streaming von Video zwischen einem WebCodecs-Sender und -Empfnger wrde die Decoder-Konfiguration identisch mit den Metadaten sein, die von dem VideoEncoder zurckgegeben werden, der die kodierten Chunks erzeugt hat.

js
decoder.configure(/* config */);

Wenn Sie eine Videodatei dekodieren, bentigen Sie eine Demultiplex-Bibliothek, um Video-Chunks zu extrahieren. Sie knnen dann die Chunks zur Dekodierung einreichen. Denken Sie daran, dass Sie nicht nur einen Chunk zur Dekodierung senden und darauf warten sollten, dass das Frame ausgegeben wird, bevor Sie den nchsten Chunk senden. Abhngig vom Browser/Gert und dem Video selbst mssen Sie mglicherweise mehrere Chunks senden, bevor der Decoder beginnt, Frames zurckzugeben, und die Mindestanzahl von Chunks hngt vom jeweiligen Gert ab.

js
let chunkIndex = 0;
// Process chunks in batches, not one at a time nor all at once
for (let i = 0; i < BATCH_LENGTH; i++) {
  decoder.decode(chunks[chunkIndex]);
  chunkIndex++;
}

hnlich wie VideoEncoder unterhlt VideoDecoder eine Dekodier-Warteschlange, die verwaltet werden muss. Wenn Sie Tausende von Chunks auf einmal an den VideoDecoder senden, knnte der Decoder schlieen oder fehlschlagen, sodass Ihre Anwendung sicherstellen muss, dass die VideoDecoder.decodeQueueSize nicht ungebunden wchst. Wie beim Encoder knnen Sie auch das dequeue-Ereignis verwenden, um bei der Verwaltung der Dekodier-Warteschlange zu helfen.

js
decoder.addEventListener("dequeue", (event) => {
  // Queue up more decoding work
});

Sobald Sie alle Frames zur Dekodierung gesendet haben, knnen Sie flush ausfhren.

js
await decoder.flush();

Je nach Gert/Browser gibt der Decoder die letzten VideoFrame-Objekte mglicherweise nicht zurck, bis flush() aufgerufen wird. Sobald Sie die VideoDecoder-Nutzung vollstndig abgeschlossen haben, sollten Sie die close()-Methode aufrufen, um Systemressourcen freizugeben.

js
decoder.close();

Ein VideoDecoder kann aus verschiedenen Grnden einen Fehler beim Dekodieren auslsen, wie z.B. beschdigte oder fehlende Daten in einem Quell-EncodedVideoChunk. Wenn ein Decoder fehlschlgt, wechselt er dauerhaft in den Zustand "closed", und es muss eine neue VideoDecoder-Instanz erstellt werden. Der erste von dem neuen Decoder dekodierte Chunk muss ein Keyframe sein, daher muss von der aktuellen Position bis zum nchsten Keyframe gesucht werden, bevor fortgefahren wird.

js
let chunkIndex = 0;

for (let i = 0; i < BATCH_LENGTH; i++) {
  // Check if decoder failed
  if (decoder.state === "closed") {
    // Seek forward to the next key frame from the current position
    for (let j = chunkIndex; j < chunks.length; j++) {
      if (chunks[j].type === "key") {
        chunkIndex = j;
        break;
      }
    }
    // Close the old decoder, instantiate and configure a new decoder
  }
  decoder.decode(chunks[chunkIndex]);
  chunkIndex++;
}

VideoFrame

Ein VideoFrame reprsentiert ein einzelnes unkomprimiertes Video-Frame, einschlielich seiner Pixeldaten und Metadaten wie seinem Zeitstempel. Es wird sowohl vom VideoDecoder beim Dekodieren codierter Videos zurckgegeben als auch aus einer Vielzahl von Bildquellen erzeugt.

Erstellung von Videoframes

Ein VideoFrame kann aus jeder Bildquelle konstruiert werden. Beachten Sie, dass Zeitstempel in Mikrosekunden angegeben werden.

js
const bitmapFrame = new VideoFrame(imgBitmap, { timestamp: 0 });
const imageFrame = new VideoFrame(htmlImageEl, { timestamp: 0 });
const videoFrame = new VideoFrame(htmlVideoEl, { timestamp: 0 });
const canvasFrame = new VideoFrame(canvasEl, { timestamp: 0 });

Das Erstellen eines VideoFrame aus einem Canvas ist typischerweise der Weg, wie Sie Video in einer Videobearbeitungsanwendung kodieren wrden, wobei Quellvideos und Bilder im Rahmen eines Canvas-Kontextes verwendet werden, um Effekte und Transformationen anzuwenden, und das Canvas kann sowohl vom Benutzer vorab betrachtet als auch als Bildquelle fr ein zu kodierendes VideoFrame verwendet werden.

Sie knnen auch direkt ein VideoFrame aus Binrdaten erstellen, wie z. B. einem ArrayBuffer; Sie mssen jedoch das format und die Metadaten angeben und sicherstellen, dass die zum Erstellen des Frames verwendeten Daten dem angegebenen format entsprechen.

js
const rgbaFrame = new VideoFrame(rgbaData, {
  timestamp: 0,
  format: "RGBA",
  codedWidth: 1920,
  codedHeight: 1080,
});

VideoFrame-Objekte sind an Daten im Grafikspeicher gebunden. Beim Erstellen eines VideoFrame aus einem Canvas, Bitmap, Video oder Image werden Daten vom Grafikspeicher zum Grafikspeicher kopiert, was relativ effizienter ist.

Ein aus Binrdaten (z.B. ArrayBuffer oder Uint8ClampedArray) konstruiertes VideoFrame wird eine CPUGrafikspeicher-Kopieroperation erfordern, was, wenn es wiederholt durchgefhrt wird, zu Leistungseinbuen fhren kann.

Schlielich knnen VideoFrame-Objekte auch durch das Dekodieren von EncodedVideoChunk-Objekten ber einen VideoDecoder erzeugt werden, wie im Abschnitt Videodekodierung oben gezeigt.

Konsum von Videoframes

Dekodiertes Video kann auch im Browser abgespielt werden, indem VideoFrame-Objekte ber eine der Canvas-Rendering-Methoden in ein Canvas gerendert werden. Verschiedene Rendering-Methoden haben unterschiedliche Leistungsmerkmale, die relevant sein knnten, wenn rechenintensive Videoverarbeitungsoperationen ausgefhrt werden.

Canvas2D

Frames knnen mit der drawImage-Methode auf ein CanvasRenderingContext2D gezeichnet werden:

js
const canvas = new OffscreenCanvas(width, height);
const ctx = canvas.getContext("2d");
ctx.drawImage(frame, 0, 0);

Whrend der 2D-Canvas-Kontext eine einfache, aber flexible API hat, verwenden Browser unter der Haube unterschiedliche Implementierungen, was zu inkonsistenter und im Allgemeinen schlechterer Leistung ber verschiedene Browser hinweg fhrt.

BitmapRenderer

Frames knnen auch ber den ImageBitmapRenderingContext auf ein Canvas gerendert werden, indem ein ImageBitmap aus dem Frame erstellt und es ber die Methode transferFromImageBitmap auf das Canvas gerendert wird.

js
const canvas = new OffscreenCanvas(width, height);
const ctx = canvas.getContext("bitmaprenderer");

const bitmap = await createImageBitmap(frame);
ctx.transferFromImageBitmap(bitmap);
frame.close();

Diese Methode beinhaltet das Erstellen einer einzigen Kopie des Frames im Grafikspeicher, was zu konsistenterer und im Allgemeinen besserer Leistung ber Browser hinweg als die Canvas2D-API fhrt und gleichzeitig relativ einfach ist.

WebGPU

Der effizienteste Weg, ein VideoFrame auf ein Canvas zu rendern, ist ber die Methode importExternalTexture in WebGPU.

js
const externalTexture = device.importExternalTexture({ source: frame });

importExternalTexture ist effizient, da es einen null kopierenden Vorgang verursacht und genau dasselbe VideoFrame-Objekt im Speicher innerhalb einer WebGPU-Pipeline verwendet. Es ist die leistungsstrkste Methode zum Rendern eines VideoFrame, aber auch die komplexeste beim Einrichten.

Speicher

Da VideoFrame-Objekte signifikanten GPU-Speicher konsumieren knnen und die Videobearbeitung das Manipulieren vieler Frames pro Sekunde beinhaltet, sollte besonderer Wert darauf gelegt werden, Speicher zu verwalten und Speicherlecks zu vermeiden, um Abstrze der Anwendung zu vermeiden.

In erster Linie mssen Frames explizit freigegeben werden, wenn sie nicht mehr bentigt werden.

js
frame.close();

Beim Kodieren knnen Sie das Frame schlieen, sobald Sie es zur Kodierung senden.

js
encoder.encode(frame, { keyFrame: true });
frame.close();

Sie sollten die Frames auch unmittelbar nach dem Rendern schlieen.

js
ctx.drawImage(frame, 0, 0);
frame.close();

Beim bertragen eines VideoFrame zwischen Threads (z.B. einem Worker) sollte es als Transferable Object bertragen werden.

js
worker.postMessage(frame, [frame]);

Audio

WebCodecs untersttzt die Kodierung und Dekodierung von Audio ber AudioEncoder und AudioDecoder, unter Verwendung der Codecs Opus und AAC. Bevor Sie mit Audio arbeiten, gibt es einige wichtige Hinweise:

  • Durchleitung: Wenn Sie Video transkodieren und das Audio nicht verndern mssen, brauchen Sie das Audio berhaupt nicht zu dekodieren und neu zu kodieren. EncodedAudioChunk-Objekte knnen direkt von einer Demuxing-Bibliothek zu einer Muxing-Bibliothek bergeben werden, was erheblich effizienter ist.
  • Wiedergabe: Die WebCodecs API hat keine eingebaute Audiowiedergabe. Fr die Wiedergabe verwenden Sie die Web Audio API.
  • Formatuntersttzung: WebCodecs untersttzt nur die Kodierung von Opus und AAC. Fr MP3 oder andere Formate ist eine Drittanbieter-Bibliothek erforderlich.

Wiedergabe

Es gibt keine direkte Verbindung zwischen WebCodecs und der Web Audio API. AudioData-Objekte knnen nicht direkt an die Web Audio API bergeben werden, die AudioBuffer zur Darstellung roher Audiodaten verwendet.

Der empfohlene Ansatz zur Wiedergabe ist, EncodedAudioChunk-Objekte mit einer Muxing-Bibliothek in einen Speicherpuffer zu muxen und dann diesen Puffer ber AudioContext.decodeAudioData() zu dekodieren:

js
// mux encoded chunks to an ArrayBuffer using a muxing library
const buffer = await muxAudioToBuffer(encodedChunks);
const audioBuffer = await audioContext.decodeAudioData(buffer);
const source = audioContext.createBufferSource();
source.buffer = audioBuffer;
source.connect(audioContext.destination);
source.start();

Alternativ knnen Sie rohe Samples aus AudioData ber copyTo() extrahieren und manuell einen AudioBuffer konstruieren, aber dies erfordert eine CPU-seitige Datenkopie fr jeden Chunk und ist langsamer.

Kodierung

Die Audiokodierung ist einfacher als die Videokodierung es gibt keine Keyframes, keine Hardware-Beschleunigungsprobleme, und jedes AudioData erzeugt genau einen EncodedAudioChunk. Der Encoder kann als einfacher asynchroner Pipeline behandelt werden.

js
const encoder = new AudioEncoder({
  output(chunk) {
    // send to muxer
  },
  error(e) {
    console.error(e);
  },
});

encoder.configure({
  codec: "opus",
  sampleRate: 48000,
  numberOfChannels: 2,
});

for (const audioData of rawAudio) {
  encoder.encode(audioData);
  audioData.close();
}

await encoder.flush();

Siehe den Leitfaden zur Codec-Auswahl fr Hinweise zur Auswahl zwischen Opus und AAC.

Dekodierung

Die Audiodekodierung folgt demselben Muster wie die Kodierung. Die Decoder-Konfiguration wird typischerweise von der Demuxing-Bibliothek bereitgestellt und nicht vom Entwickler ausgewhlt.

js
const decoder = new AudioDecoder({
  output(audioData) {
    // process AudioData
    audioData.close();
  },
  error(e) {
    console.error(e);
  },
});

// config comes from demuxer library
decoder.configure(decoderConfig);

for (const chunk of encodedChunks) {
  decoder.decode(chunk);
}

await decoder.flush();

AudioData

Ein AudioData-Objekt reprsentiert ein Segment von Rohaudio, typischerweise ber einen Zeitraum von 0,20,5 Sekunden. Rohe Samples werden als Float32Array-Daten mithilfe der Methode AudioData.copyTo() extrahiert. Das Extraktionsmuster hngt von der format-Eigenschaft des AudioData-Objekts ab.

Das hufigste Format ist f32-planar, bei dem jeder Kanal in einer separaten Ebene gespeichert wird. Verwenden Sie planeIndex, um jeden Kanal unabhngig zu kopieren:

js
// f32-planar: each channel stored separately
const leftChannel = new Float32Array(audioData.numberOfFrames);
audioData.copyTo(leftChannel, { planeIndex: 0 });

const rightChannel = new Float32Array(audioData.numberOfFrames);
audioData.copyTo(rightChannel, { planeIndex: 1 });

Das weniger hufige f32-Format speichert alle Kanle ineinander gestaffelt in einem einzigen Array ([L, R, L, R, ...]). In diesem Fall kopieren Sie den gesamten ineinander verschachtelten Puffer und de-multiplexieren ihn manuell:

js
// f32: channels interleaved in a single array
const interleaved = new Float32Array(
  audioData.numberOfFrames * audioData.numberOfChannels,
);
audioData.copyTo(interleaved, { planeIndex: 0 });

const leftChannel = new Float32Array(audioData.numberOfFrames);
const rightChannel = new Float32Array(audioData.numberOfFrames);

for (let i = 0; i < audioData.numberOfFrames; i++) {
  leftChannel[i] = interleaved[i * 2];
  rightChannel[i] = interleaved[i * 2 + 1];
}

Um beide Formate zu verarbeiten:

js
if (audioData.format.includes("planar")) {
  // f32-planar: copy each channel by planeIndex
} else {
  // f32: copy interleaved, then de-interleave
}

Um ein AudioData aus rohen Samples zu konstruieren, mssen die Daten fr alle Kanle in einem einzelnen Float32Array verkettet werden, wobei die Samples jedes Kanals sequentiell angeordnet werden (entspricht dem f32-planar-Layout), und die numberOfFrames muss auf die Anzahl der Samples pro Kanal eingestellt werden:

js
const framesPerChunk = 1024;
const data = new Float32Array(framesPerChunk * 2); // 2 channels
data.set(leftChannel, 0);
data.set(rightChannel, framesPerChunk);

const audioData = new AudioData({
  format: "f32-planar",
  sampleRate: 48000,
  numberOfFrames: framesPerChunk,
  numberOfChannels: 2,
  timestamp: sourceAudioData.timestamp,
  data,
});

Beachten Sie, dass bestimmte AAC-Codec-Zeichenfolgen (mp4a.40.5, mp4a.40.05 und mp4a.40.29) Konfigurationen entsprechen, die eine Technik namens Spectral Band Replication (SBR) verwenden, die dazu fhrt, dass der Decoder Audio mit der doppelten Samplerate ausgibt, die in der Decoder-Konfiguration angegeben ist. Lesen Sie immer audioData.sampleRate direkt ab, anstatt anzunehmen, dass es mit dem konfigurierten Wert bereinstimmt.

Wie VideoFrame-Objekte mssen AudioData-Objekte explizit geschlossen werden, um Speicher freizugeben:

js
audioData.close();

Whrend AudioData viel weniger Speicher als ein VideoFrame bentigt, hat rohes Audio immer noch einen erheblichen Speicherbedarf eine Stunde Stereo-Audio bei 48 kHz liegt bei ungefhr 1,4 GB. Fr groe Dateien sollte Audio in Chargen dekodiert und verarbeitet werden, anstatt alles auf einmal.

Siehe auch


Web Proxy Viewer  |  New URL  |  Original Page