Start · Sprachen · JavaScript · Referenz · RTCEncodedAudioFrame

RTCEncodedAudioFrame

Schnittstelle

Stellt einen kodierten Audio-Frame in der WebRTC-Sende- oder Empfangs-Pipeline dar, den man in einem WebRTC Encoded Transform lesen und verändern kann.

seit JavaScript Baseline 2025 (Firefox 117+, Safari 15.4+, Chrome Kategorie: webapi

Signatur

interface RTCEncodedAudioFrame

Beschreibung

RTCEncodedAudioFrame ist ein einzelner, bereits kodierter Audio-Frame (z. B. Opus) in einer WebRTC-Verbindung. Du bekommst solche Frames nicht direkt im Hauptthread. Sie laufen durch einen WebRTC Encoded Transform: Auf der Senderseite liegen sie zwischen Encoder und Paketierer, auf der Empfängerseite zwischen Depaketierer und Decoder.

Typische Einsatzfälle:

  • Ende-zu-Ende-Verschlüsselung (E2EE), zum Beispiel über einen SFU-Server, der die Inhalte nicht lesen soll
  • Zusätzliche Metadaten an die Nutzdaten anhängen
  • Analyse und Protokollierung, etwa von Frame-Größen oder RTP-Zeitstempeln

Wichtige Mitglieder:

  • data ist ein ArrayBuffer mit den kodierten Nutzdaten und lässt sich beschreiben.
  • timestamp ist der RTP-Zeitstempel. Diese Eigenschaft ist veraltet, stattdessen gilt rtpTimestamp in den Metadaten.
  • getMetadata() gibt ein Objekt mit synchronizationSource, payloadType, contributingSources, sequenceNumber, rtpTimestamp und mimeType zurück.

Nach dem Standard verarbeitest du die Frames in einem Worker. Dort empfängt der Handler des rtctransform-Events einen RTCRtpScriptTransformer mit einem readable- und einem writable-Stream. Jeden Frame, den du liest, schreibst du nach der Bearbeitung wieder in writable. Frames, die du nicht zurückschreibst, werden verworfen.

Der Konstruktor new RTCEncodedAudioFrame(originalFrame, options) kommt erst in neueren Browsern vor. Er kopiert einen Frame, auf Wunsch mit geänderten Metadaten.

Parameter

Name Typ Default Beschreibung
$originalFrame Pflicht RTCEncodedAudioFrame Nur für den Konstruktor: der Frame, dessen Daten und Metadaten kopiert werden.
$options { metadata?: RTCEncodedAudioFrameMetadata } {} Nur für den Konstruktor: Metadaten, die im neuen Frame die Werte des Original-Frames ersetzen, z. B. rtpTimestamp.

Rückgabewert

Typ
RTCEncodedAudioFrame
Beschreibung
Ein Frame-Objekt mit data, timestamp und getMetadata(). Der Konstruktor gibt eine unabhängige Kopie zurück.

Beispiele

Hauptthread: Transform an Sender und Empfänger hängen

const worker = new Worker('audio-transform.js');
const pc = new RTCPeerConnection();

const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
const [track] = stream.getAudioTracks();
const sender = pc.addTrack(track, stream);

// Ausgehende Frames im Worker bearbeiten
sender.transform = new RTCRtpScriptTransform(worker, { side: 'send' });

// Eingehende Frames ebenfalls über den Worker leiten
pc.addEventListener('track', ({ receiver }) => {
  receiver.transform = new RTCRtpScriptTransform(worker, { side: 'receive' });
});

Worker: einfache XOR-Verschleierung der Nutzdaten

// audio-transform.js
const KEY = 0x5a;

const xorFrame = (frame, controller) => {
  const bytes = new Uint8Array(frame.data);
  for (let i = 0; i < bytes.length; i++) bytes[i] ^= KEY;
  frame.data = bytes.buffer; // geänderte Daten zurückschreiben
  controller.enqueue(frame);
};

addEventListener('rtctransform', ({ transformer }) => {
  // Senden und Empfangen nutzen dieselbe (symmetrische) Operation
  transformer.readable
    .pipeThrough(new TransformStream({ transform: xorFrame }))
    .pipeTo(transformer.writable);
});

Worker: Metadaten der Frames protokollieren

addEventListener('rtctransform', ({ transformer }) => {
  const { side } = transformer.options;
  transformer.readable
    .pipeThrough(new TransformStream({
      transform(frame, controller) {
        const { synchronizationSource, payloadType, rtpTimestamp, mimeType } = frame.getMetadata();
        console.log(side, mimeType, `SSRC=${synchronizationSource}`,
          `PT=${payloadType}`, `TS=${rtpTimestamp}`, `${frame.data.byteLength} Bytes`);
        controller.enqueue(frame);
      }
    }))
    .pipeTo(transformer.writable);
});
send audio/opus SSRC=1234567890 PT=111 TS=960 87 Bytes

Chromium-Fallback mit createEncodedStreams()

// Ältere Chrome-Versionen: nicht standardisierte API
const pc = new RTCPeerConnection({ encodedInsertableStreams: true });
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
const sender = pc.addTrack(stream.getAudioTracks()[0], stream);

if (!('RTCRtpScriptTransform' in window) && sender.createEncodedStreams) {
  const { readable, writable } = sender.createEncodedStreams();
  const worker = new Worker('audio-transform-legacy.js');
  // Streams sind übertragbar (Transferable)
  worker.postMessage({ readable, writable }, [readable, writable]);
}

// Wichtig · Fallstricke

  • Browser-Unterstützung: Firefox (ab 117) und Safari (ab 15.4) unterstützen den Standard mit RTCRtpScriptTransform. Chromium hatte lange nur createEncodedStreams(), für das encodedInsertableStreams: true in der RTCPeerConnection-Konfiguration nötig ist. Neuere Chrome-Versionen unterstützen auch RTCRtpScriptTransform. Prüfe beide Wege mit Feature-Detection.
  • Jeden Frame zurückschreiben: Frames, die du nicht per enqueue() oder in writable weitergibst, kommen beim Empfänger nicht an und erzeugen hörbare Aussetzer.
  • timestamp ist veraltet. Verwende stattdessen getMetadata().rtpTimestamp.
  • Performance: Die Transform-Funktion läuft für jeden Frame, bei Opus meist alle 20 ms. Vermeide darin teure Speicherbelegungen und synchrone Blockaden.
  • Sicherheit: XOR ist keine Verschlüsselung. Für echte Ende-zu-Ende-Verschlüsselung brauchst du z. B. AES-GCM über crypto.subtle, eine eindeutige IV pro Frame und einen sicheren Schlüsselaustausch, etwa nach dem SFrame-Konzept. Sender und Empfänger müssen dasselbe Schema verwenden, sonst kann der Decoder die Daten nicht lesen.
  • Der Konstruktor und manche Metadaten-Felder, z. B. sequenceNumber oder mimeType, sind noch nicht überall verfügbar.