Manipulieren von Video-Stream-Komponenten
Moderne Webtechnologien bieten viele Möglichkeiten, mit Videos zu arbeiten. Media Stream API Media Recording API Media Source API und WebRTC API addiert ein umfangreiches Tool zum Aufzeichnen, Übertragen und Wiedergeben von Videostreams. Diese APIs verhindern bei der Lösung bestimmter übergeordneter Aufgaben Programmierer arbeiten mit einzelnen Komponenten eines Videostreams, z. B. Frames. und nicht gemischt codierte Video- oder Audio-Blöcke. Um Low-Level-Zugriff auf diese grundlegenden Komponenten zu erhalten, haben Entwickler WebAssembly, um Video- und Audio-Codecs in den Browser zu integrieren. Aber in Anbetracht dass moderne Browser bereits mit einer Vielzahl von Codecs ausgeliefert werden (die häufig durch Hardware beschleunigt werden), sie als WebAssembly neu zu verpacken, Personal- und Computerressourcen.
WebCodecs API beseitigt diese Ineffizienz. indem sie Programmierern die Möglichkeit geben, Medienkomponenten zu verwenden, die bereits in im Browser. Im Detail:
- Video- und Audiodecoder
- Video- und Audio-Encoder
- Rohvideoframes
- Bilddecoder
Das WebCodecs-API eignet sich für Webanwendungen, die vollständige Kontrolle über den wie Medieninhalte verarbeitet werden, z. B. Videoeditoren, Videokonferenzen, Streaming usw.
Videoverarbeitungs-Workflow
Frames sind das Herzstück der Videoverarbeitung. Daher werden in WebCodecs die meisten Klassen entweder Frames verbrauchen oder produzieren. Video-Encoder wandeln Frames in codierte Blöcke. Bei Videodecoder ist das Gegenteil der Fall.
VideoFrame
ist außerdem gut mit anderen Web-APIs kompatibel, da es ein CanvasImageSource
ist und einen Konstruktor hat, der CanvasImageSource
akzeptiert.
Daher kann es in Funktionen wie drawImage()
und texImage2D()
verwendet werden. Es kann auch aus Canvases, Bitmaps, Videoelementen und anderen Videoframes erstellt werden.
Die WebCodecs API funktioniert gut zusammen mit den Klassen der Insertable Streams API. die WebCodecs mit Medienstream-Tracks verbinden.
MediaStreamTrackProcessor
unterteilt die Media-Tracks in einzelne Frames.MediaStreamTrackGenerator
erstellt einen Medien-Track aus einem Frame-Stream.
WebCodecs und Web Worker
Die WebCodecs API erledigt die schwierigen Aufgaben asynchron und außerhalb des Hauptthreads. Da Frame- und Chunk-Callbacks jedoch häufig mehrmals pro Sekunde aufgerufen werden können, könnten sie den Hauptthread überladen und die Website dadurch weniger responsiv machen. Daher ist es besser, die Verarbeitung einzelner Frames und codierte Blöcke Web Worker.
ReadableStream
ermöglicht die bequeme Übertragung aller Frames, die von einem Medium stammen,
an den Worker senden. Mit MediaStreamTrackProcessor
kann beispielsweise ein Wert für
ReadableStream
für einen Mediastream-Track, der von der Webcam stammt. Danach
Der Stream wird an einen Web Worker übertragen, wo Frames einzeln gelesen und in die Warteschlange gestellt werden.
in VideoEncoder
.
Mit HTMLCanvasElement.transferControlToOffscreen
kann sogar außerhalb des Hauptthreads gerendert werden. Aber wenn sich all die High-Level-Tools
nicht praktikabel ist, ist VideoFrame
selbst übertragbar und unter Umständen
zwischen Workern verschoben.
WebCodecs in Aktion
Codierung
<ph type="x-smartling-placeholder">Alles beginnt mit einem VideoFrame
.
Es gibt drei Möglichkeiten, Videoframes zu erstellen.
Aus einer Bildquelle wie einem Canvas, einer Bild-Bitmap oder einem Videoelement.
const canvas = document.createElement("canvas"); // Draw something on the canvas... const frameFromCanvas = new VideoFrame(canvas, { timestamp: 0 });
Mit
MediaStreamTrackProcessor
Frames aus einemMediaStreamTrack
abrufenconst 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; }
Frame aus seiner binären Pixeldarstellung in einem
BufferSource
erstellenconst 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);
Ganz gleich, woher sie kommen, Frames können in
EncodedVideoChunk
-Objekte mit einem VideoEncoder
.
Vor der Codierung müssen VideoEncoder
zwei JavaScript-Objekte erhalten:
- Init-Wörterbuch mit zwei Funktionen zur Verarbeitung codierter Blöcke und
Fehler. Diese Funktionen sind vom Entwickler definiert und können später nicht mehr geändert werden.
werden sie an den
VideoEncoder
-Konstruktor übergeben. - Encoder-Konfigurationsobjekt, das Parameter für die Ausgabe enthält
des Videostreams. Sie können diese Parameter später durch Aufrufen von
configure()
ändern.
Die Methode configure()
gibt NotSupportedError
aus, wenn die Konfiguration nicht
die vom Browser unterstützt werden. Es empfiehlt sich, die statische Methode aufzurufen,
VideoEncoder.isConfigSupported()
durch die Konfiguration, um vorab zu prüfen,
die Konfiguration unterstützt wird,
und warten auf ihr Promise.
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.
}
Nachdem der Encoder eingerichtet wurde, kann er Frames über die Methode encode()
annehmen.
Sowohl configure()
als auch encode()
werden sofort zurückgegeben, ohne auf den
zu erledigen. Es ermöglicht, dass mehrere Frames an der
während encodeQueueSize
anzeigt, wie viele Anfragen in der Warteschlange
bis die vorherigen Codierungen abgeschlossen sind.
Fehler werden gemeldet, indem entweder sofort eine Ausnahme ausgelöst wird, falls die Argumente
oder die Reihenfolge der Methodenaufrufe verstößt gegen den API-Vertrag oder das Aufrufen der error()
Callback für Probleme bei der Codec-Implementierung.
Wenn die Codierung erfolgreich abgeschlossen wurde, hat output()
-Callback mit einem neuen codierten Chunk als Argument aufgerufen.
Ein weiteres wichtiges Detail ist, dass Frames informiert werden müssen, wenn sie
mehr benötigt wird, indem Sie close()
aufrufen.
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();
}
}
Schließlich ist es an der Zeit, den Codierungscode abzuschließen, indem Sie eine Funktion schreiben, die codierten Videoblöcken, die über den Encoder ausgegeben werden. Normalerweise werden mit dieser Funktion Datenblöcke über das Netzwerk gesendet oder in ein Medium mux. für die Speicherung.
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,
});
}
Solltest du irgendwann einmal sicherstellen,
dass alle ausstehenden Codierungsanfragen
abgeschlossen wurde, können Sie flush()
aufrufen und auf das Versprechen warten.
await encoder.flush();
Decodierung
<ph type="x-smartling-placeholder">Die Einrichtung eines VideoDecoder
ähnelt dem Vorgang für den
VideoEncoder
: Beim Erstellen des Decoders werden zwei Funktionen übergeben und der Codec
configure()
übergeben werden.
Die Codec-Parameter sind von Codec zu Codec unterschiedlich. Beispiel: H.264-Codec
benötigt möglicherweise ein binäres Blob
von AVCC, es sei denn, sie sind im so genannten Anhang B-Format (encoderConfig.avc = { format: "annexb" }
) codiert.
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.
}
Sobald der Decoder initialisiert ist, kannst du EncodedVideoChunk
-Objekte in ihn übergeben.
Zum Erstellen eines Blocks benötigen Sie Folgendes:
BufferSource
codierter Videodaten- Startzeitstempel des Blocks in Mikrosekunden (Medienzeit des ersten codierten Frames im Block)
- den Chunk-Typ, einer der folgenden:
<ph type="x-smartling-placeholder">
- </ph>
key
, wenn der Chunk unabhängig von vorherigen Blöcken decodiert werden kanndelta
, wenn der Block erst decodiert werden kann, nachdem mindestens ein vorheriger Block decodiert wurde
Außerdem sind alle vom Encoder ausgegebenen Blöcke bereit für den Decoder. Alle oben Gesagten über Error Reporting und die asynchrone Natur der Encoder-Methoden gelten auch für Decodierer.
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();
Jetzt ist es an der Zeit zu zeigen, wie ein neu decodierter Frame auf der Seite angezeigt werden kann. Es ist
um sicherzustellen, dass der Decoder-Callback (handleFrame()
) ausgegeben wird
kehrt schnell zurück. Im Beispiel unten wird nur ein Frame
die zum Rendern bereit sind.
Das Rendering erfolgt separat und umfasst zwei Schritte:
- Es wird auf den richtigen Zeitpunkt gewartet, um den Frame anzuzeigen.
- Zeichnen des Rahmens auf dem Canvas
Sobald ein Frame nicht mehr benötigt wird, rufen Sie close()
auf, um den zugrunde liegenden Speicher freizugeben
bevor die automatische Speicherbereinigung an die
automatische Speicherbereinigung gelangt,
von der Webanwendung verwendet wird.
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);
}
Entwicklertipps
Den Medienbereich verwenden in den Chrome-Entwicklertools, um Medienprotokolle aufzurufen und WebCodecs zu debuggen.
<ph type="x-smartling-placeholder">Demo
In der folgenden Demo sehen Sie, wie Animationsframes aus einem Canvas aussehen:
- aufgenommen mit 25 fps in eine
ReadableStream
vonMediaStreamTrackProcessor
- an einen Web Worker übertragen
- im H.264-Videoformat codiert
- wieder in eine Folge von Videoframes decodiert wird
- und auf dem zweiten Canvas mit
transferControlToOffscreen()
gerendert
Andere Demos
Sehen Sie sich auch unsere anderen Demos an:
- GIFs mit ImageDecoder decodieren
- Kameraeingabe in einer Datei erfassen
- MP4-Wiedergabe
- Weitere Beispiele
WebCodecs API verwenden
Funktionserkennung
So prüfen Sie, ob WebCodecs unterstützt werden:
if ('VideoEncoder' in window) {
// WebCodecs API is supported.
}
Die WebCodecs API ist nur in sicheren Kontexten verfügbar.
sodass die Erkennung fehlschlägt, wenn self.isSecureContext
auf „false“ gesetzt ist.
Feedback
Das Chrome-Team möchte mehr über Ihre Erfahrungen mit der WebCodecs API erfahren.
Informationen zum API-Design
Funktioniert die API nicht wie erwartet? Oder sind fehlende Methoden oder Eigenschaften, die Sie zur Umsetzung Ihrer Idee benötigen? Ein Fragen oder Anmerkungen zum Sicherheitsmodell haben? Spezifikationsproblem melden auf der entsprechendes GitHub-Repository oder fügen Sie Ihre Gedanken zu einem bestehenden Problem.
Problem mit der Implementierung melden
Haben Sie bei der Implementierung von Chrome einen Fehler gefunden? Oder ist die Implementierung
von der Spezifikation abweichen? Melde einen Fehler unter new.crbug.com.
Geben Sie so viele Details wie möglich an.
und geben Sie Blink>Media>WebCodecs
in das Feld Components (Komponenten) ein.
Glitch eignet sich hervorragend, um schnelle und einfache Reproduktionen zu teilen.
Unterstützung für die API anzeigen
Möchten Sie die WebCodecs API verwenden? Ihre öffentliche Unterstützung hilft Chrome-Team, um Funktionen zu priorisieren, und zeigt anderen Browseranbietern, wie wichtig sie zu unterstützen.
Senden Sie E-Mails an media-dev@chromium.org oder einen Tweet.
an @ChromiumDev mit dem Hashtag
#WebCodecs
und teilen Sie uns mit, wo und wie Sie sie nutzen.
Hero-Image von Denise Jans bei Unsplash