Signatur
Beschreibung
Die Eigenschaft timestamp eines RTCEncodedAudioFrame gibt an, wann die Abtastung (Sampling) des Audio-Frames begann. Der Wert entspricht dem RTP-Zeitstempel nach RFC 3550, also dem Abtastzeitpunkt des ersten Oktetts im RTP-Paket. Er wird in Einheiten der Medien-Taktrate gezählt (bei Opus typischerweise 48 000 Hz) und nicht in Millisekunden der Wanduhr.
Verwenden kannst du die Eigenschaft innerhalb einer WebRTC Encoded Transform, also überall dort, wo du kodierte Frames zwischen Encoder und Paketierer (Sender) bzw. zwischen Depaketierer und Decoder (Empfänger) abfängst, z. B. über RTCRtpScriptTransform in einem Worker. Typische Einsatzfälle sind das Sortieren oder Synchronisieren von Frames, das Erkennen von Lücken (verlorene Pakete), das Messen der Frame-Dauer sowie das Protokollieren oder Debuggen von Audio-Pipelines, etwa bei Ende-zu-Ende-Verschlüsselung.
Die Eigenschaft ist schreibgeschützt. Die Spezifikation hat den Wert inzwischen in die Metadaten verlagert: Neuer Code sollte frame.getMetadata().rtpTimestamp verwenden und timestamp höchstens als Rückfalllösung für ältere Browser lesen.
Rückgabewert
Beispiele
Zeitstempel im Worker mit RTCRtpScriptTransform protokollieren
// worker.js
onrtctransform = (event) => {
const { readable, writable } = event.transformer;
const logger = new TransformStream({
transform(frame, controller) {
// Neuere Browser: Metadaten bevorzugen, sonst auf timestamp zurückfallen
const ts = frame.getMetadata?.().rtpTimestamp ?? frame.timestamp;
console.log(`Audio-Frame: RTP-Zeitstempel ${ts}, ${frame.data.byteLength} Byte`);
controller.enqueue(frame);
}
});
readable.pipeThrough(logger).pipeTo(writable);
};
// main.js
const worker = new Worker('worker.js');
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
const pc = new RTCPeerConnection();
const sender = pc.addTrack(stream.getAudioTracks()[0], stream);
sender.transform = new RTCRtpScriptTransform(worker, { side: 'send' });
Frame-Dauer und Lücken auf der Empfangsseite erkennen
// worker.js – Empfangsseite
const CLOCK_RATE = 48000; // Opus
let lastTs = null;
onrtctransform = (event) => {
const { readable, writable } = event.transformer;
readable.pipeThrough(new TransformStream({
transform(frame, controller) {
const ts = frame.timestamp;
if (lastTs !== null) {
// Differenz modulo 2^32 berechnen (Überlauf berücksichtigen)
const delta = (ts - lastTs + 2 ** 32) % 2 ** 32;
const ms = (delta / CLOCK_RATE) * 1000;
if (ms > 20) console.warn(`Lücke erkannt: ${ms.toFixed(1)} ms`);
}
lastTs = ts;
controller.enqueue(frame);
}
})).pipeTo(writable);
};
// main.js
pc.ontrack = ({ receiver }) => {
receiver.transform = new RTCRtpScriptTransform(worker, { side: 'receive' });
};
Legacy-API in Chromium (createEncodedStreams)
// Nur Chromium, PeerConnection mit encodedInsertableStreams erzeugen
const pc = new RTCPeerConnection({ encodedInsertableStreams: true });
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
const sender = pc.addTrack(stream.getAudioTracks()[0], stream);
const { readable, writable } = sender.createEncodedStreams();
readable.pipeThrough(new TransformStream({
transform(frame, controller) {
console.log('timestamp:', frame.timestamp);
controller.enqueue(frame);
}
})).pipeTo(writable);
// Wichtig · Fallstricke
- Veraltet: Die Spezifikation führt den Wert inzwischen als
rtpTimestampinRTCEncodedAudioFrame.getMetadata(). Verwendetimestampnur als Rückfall und prüfe per Feature-Erkennung. - Einheit: Der Wert ist kein Wanduhr- oder
performance.now()-Zeitstempel, sondern zählt in Takten der Codec-Taktrate. Zum Umrechnen in Millisekunden musst du durch die Taktrate teilen (z. B. 48 000 bei Opus). - Überlauf: RTP-Zeitstempel sind 32-Bit-Werte, die überlaufen und bei einem zufälligen Wert starten. Differenzen deshalb immer modulo 232 berechnen.
- Kompatibilität: Chromium unterstützt die ältere API
createEncodedStreams()(mitencodedInsertableStreams: true) und neuere Versionen auchRTCRtpScriptTransform; Firefox und Safari nurRTCRtpScriptTransform. Die Frames sind ausschließlich innerhalb dieser Transform-Pipelines verfügbar, nicht über andere Media-APIs. - Die Frames müssen mit
controller.enqueue()weitergereicht werden, sonst bricht der Audiostrom ab.