Start · Sprachen · JavaScript · Referenz · VideoFrame

VideoFrame

Schnittstelle

Das <code>VideoFrame</code>-Interface der Web Codecs API steht für ein einzelnes Videobild mit Pixeldaten, Format, Abmessungen, Zeitstempel und Farbraum.

seit JavaScript Chrome/Edge 94+, Firefox 130+, Safari 16.4+ (nur s Kategorie: webapi

Signatur

interface VideoFrame

Beschreibung

VideoFrame ist der zentrale Datentyp der Web Codecs API. Ein Objekt enthält die Pixeldaten eines einzelnen Videobilds und dazu Metadaten: format (z. B. "I420", "NV12", "RGBA", "BGRA"), codedWidth/codedHeight, visibleRect, displayWidth/displayHeight, timestamp und duration (beide in Mikrosekunden) sowie colorSpace.

Frames bekommst du vom output-Callback eines VideoDecoder oder aus einem MediaStreamTrackProcessor. Du kannst sie auch selbst erzeugen, und zwar auf zwei Wegen:

  • new VideoFrame(image, init) aus einer Bildquelle wie HTMLVideoElement, HTMLCanvasElement, HTMLImageElement, OffscreenCanvas, ImageBitmap, SVGImageElement oder einem anderen VideoFrame.
  • new VideoFrame(data, init) aus rohen Pixeldaten (BufferSource). Dabei sind format, codedWidth, codedHeight und timestamp Pflichtangaben.

Danach kannst du den Frame an VideoEncoder.encode() übergeben, mit drawImage(), WebGL (texImage2D) oder WebGPU (importExternalTexture) zeichnen oder mit copyTo() in einen eigenen Puffer kopieren. Die wichtigsten Methoden: allocationSize(options) gibt an, wie viele Bytes copyTo() braucht. copyTo(destination, options) kopiert die Pixeldaten und liefert ein Promise mit dem Ebenen-Layout (offset, stride). clone() erzeugt eine weitere Referenz auf dieselben Daten, close() gibt die Ressourcen sofort frei.

Frames sind übertragbar (Transferable). Du kannst sie also ohne Kopie per postMessage() an einen Worker schicken. Das eignet sich gut, um rechenintensive Bildbearbeitung aus dem Haupt-Thread auszulagern.

Parameter

Name Typ Default Beschreibung
$image | data Pflicht CanvasImageSource | VideoFrame | BufferSource Entweder eine Bildquelle (HTMLVideoElement, HTMLCanvasElement, OffscreenCanvas, ImageBitmap, VideoFrame …) oder rohe Pixeldaten als ArrayBuffer/TypedArray/DataView.
$init.timestamp number Zeitstempel in Mikrosekunden. Pflicht bei Rohdaten und bei Bildquellen, die keinen eigenen Zeitstempel haben (z. B. Canvas).
$init.duration number Dauer des Frames in Mikrosekunden.
$init.format string Pixelformat, nur bei Rohdaten und dort Pflicht: "I420", "I420A", "I422", "I444", "NV12", "RGBA", "RGBX", "BGRA", "BGRX".
$init.codedWidth / init.codedHeight number Breite und Höhe der Pixeldaten in Pixeln. Bei Rohdaten Pflicht.
$init.visibleRect DOMRectInit ganzer Frame Sichtbarer Ausschnitt, z. B. { x: 0, y: 0, width: 640, height: 360 }.
$init.displayWidth / init.displayHeight number Größe von visibleRect Anzeigegröße, zum Beispiel für nicht-quadratische Pixel.
$init.layout PlaneLayout[] dicht gepackt Nur bei Rohdaten: offset und stride für jede Ebene.
$init.colorSpace VideoColorSpaceInit Farbraum-Angaben (primaries, transfer, matrix, fullRange).
$init.alpha "keep" | "discard" "keep" Bei Bildquellen: Alphakanal behalten oder verwerfen.
$init.transfer ArrayBuffer[] [] Nur bei Rohdaten: Puffer, die der Frame ohne Kopie übernimmt. Danach sind sie beim Aufrufer detached.

Rückgabewert

Typ
VideoFrame
Beschreibung
Ein neues VideoFrame-Objekt. Es belegt Ressourcen (oft GPU-Speicher), bis du close() aufrufst.

Beispiele

Frame aus einem Canvas erzeugen und kodieren

const canvas = document.querySelector('canvas');

const encoder = new VideoEncoder({
  output: (chunk) => console.log('Chunk:', chunk.type, chunk.byteLength),
  error: (e) => console.error(e)
});
encoder.configure({ codec: 'vp8', width: canvas.width, height: canvas.height });

let frameIndex = 0;
const captureFrame = () => {
  // Zeitstempel in Mikrosekunden (30 fps)
  const frame = new VideoFrame(canvas, {
    timestamp: frameIndex * (1_000_000 / 30),
    duration: 1_000_000 / 30
  });
  encoder.encode(frame, { keyFrame: frameIndex % 60 === 0 });
  frame.close(); // sofort freigeben, der Encoder hat eine eigene Referenz
  frameIndex++;
};

captureFrame();
Chunk: key 1834

Frame aus rohen RGBA-Pixeldaten erzeugen und zeichnen

const width = 4;
const height = 2;
const pixels = new Uint8Array(width * height * 4);
for (let i = 0; i < pixels.length; i += 4) {
  pixels.set([255, 0, 0, 255], i); // rot, deckend
}

const frame = new VideoFrame(pixels, {
  format: 'RGBA',
  codedWidth: width,
  codedHeight: height,
  timestamp: 0
});

console.log(frame.format, frame.codedWidth, frame.codedHeight);

const ctx = document.querySelector('canvas').getContext('2d');
ctx.drawImage(frame, 0, 0);
frame.close();
RGBA 4 2

Pixeldaten mit allocationSize() und copyTo() auslesen

const readPixels = async (frame) => {
  const options = { format: 'RGBA' }; // Umwandlung nach RGBA (in neueren Browsern)
  const size = frame.allocationSize(options);
  const buffer = new Uint8Array(size);
  const layout = await frame.copyTo(buffer, options);
  console.log('Bytes:', size, 'Layout:', layout);
  return buffer;
};

const canvas = new OffscreenCanvas(320, 240);
const frame = new VideoFrame(canvas, { timestamp: 0 });
const data = await readPixels(frame);
frame.close();
Bytes: 307200 Layout: [{ offset: 0, stride: 1280 }]

Frames aus einer Kamera an einen Worker übertragen

// Haupt-Thread
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
const [track] = stream.getVideoTracks();
const processor = new MediaStreamTrackProcessor({ track });
const reader = processor.readable.getReader();
const worker = new Worker('frame-worker.js');

while (true) {
  const { value: frame, done } = await reader.read();
  if (done) break;
  // Übertragen statt kopieren: Danach ist frame hier geschlossen
  worker.postMessage({ frame }, [frame]);
}

// frame-worker.js
// self.onmessage = ({ data: { frame } }) => {
//   console.log(frame.timestamp, frame.displayWidth);
//   frame.close();
// };
12345678 1280