Signatur
Beschreibung
Die Eigenschaft framesDecoded des Wörterbuchs RTCInboundRtpStreamStats ist ein Zähler, der angibt, wie viele Videoframes der Empfänger für diese Medienquelle bereits korrekt decodiert hat. Frames, die verworfen wurden oder nicht decodiert werden konnten, zählen nicht dazu. Der Wert steigt monoton an, solange der Strom läuft.
Du bekommst die Statistiken über RTCPeerConnection.getStats() oder RTCRtpReceiver.getStats(). Im zurückgegebenen RTCStatsReport suchst du nach Einträgen mit type === "inbound-rtp" und kind === "video". Bei Audio-Strömen ist die Eigenschaft nicht vorhanden.
In der Praxis vergleichst du zwei Messungen: Aus der Differenz von framesDecoded und der Differenz der timestamp-Werte ergibt sich die tatsächliche Decodier-Bildrate. Zusammen mit framesReceived, framesDropped und totalDecodeTime kannst du erkennen, ob Paketverlust, Überlastung der CPU oder ein zu langsamer Decoder die Videoqualität beeinträchtigt.
Beispiele
Decodierte Frames eines Video-Empfängers auslesen
// pc ist eine bestehende, verbundene RTCPeerConnection
const logDecodedFrames = async (pc) => {
const report = await pc.getStats();
report.forEach((stats) => {
if (stats.type === 'inbound-rtp' && stats.kind === 'video') {
console.log(`SSRC ${stats.ssrc}: ${stats.framesDecoded} Frames decodiert`);
}
});
};
await logDecodedFrames(pc);
Tatsächliche Decodier-Bildrate berechnen
const getVideoInbound = async (receiver) => {
const report = await receiver.getStats();
return [...report.values()].find((s) => s.type === 'inbound-rtp' && s.kind === 'video');
};
const receiver = pc.getReceivers().find((r) => r.track?.kind === 'video');
let previous = await getVideoInbound(receiver);
setInterval(async () => {
const current = await getVideoInbound(receiver);
if (!current || !previous) return;
const frames = current.framesDecoded - previous.framesDecoded;
const seconds = (current.timestamp - previous.timestamp) / 1000;
console.log(`Decodier-FPS: ${(frames / seconds).toFixed(1)}`);
previous = current;
}, 2000);
Decoder-Probleme erkennen
const checkDecoderHealth = async (pc) => {
const report = await pc.getStats();
for (const s of report.values()) {
if (s.type !== 'inbound-rtp' || s.kind !== 'video') continue;
const received = s.framesReceived ?? 0;
const decoded = s.framesDecoded ?? 0;
const ratio = received > 0 ? decoded / received : 1;
const avgDecodeMs = decoded > 0 ? (s.totalDecodeTime / decoded) * 1000 : 0;
console.log(`Decodiert: ${(ratio * 100).toFixed(1)} % der empfangenen Frames`);
console.log(`Ø Decodierzeit: ${avgDecodeMs.toFixed(2)} ms`);
if (ratio < 0.95) console.warn('Viele Frames konnten nicht decodiert werden!');
}
};
await checkDecoderHealth(pc);
// Wichtig · Fallstricke
- Nur für Video: Bei
kind === "audio"fehlt die Eigenschaft. Prüfe vor Berechnungen immer aufundefined, z. B. mit?? 0. - Kumulativer Zähler: Der Wert ist eine Gesamtsumme seit Beginn des Stroms. Für Raten brauchst du zwei Messungen und die Differenz der
timestamp-Werte (Millisekunden). - Neustart/SSRC-Wechsel: Ändert sich die SSRC (z. B. durch Neuverhandlung oder Simulcast-Wechsel), taucht ein neues Statistik-Objekt auf und der Zähler beginnt wieder bei 0. Ordne die Messungen daher über
idoderssrczu. - Decodiert ≠ angezeigt: Decodierte Frames können trotzdem verworfen werden, bevor sie dargestellt werden. Für angezeigte Frames ist
framesRenderedbzw.framesDroppedaussagekräftiger. - Kompatibilität: Wird von allen modernen Browsern unterstützt. Ältere Browserversionen lieferten den Wert teils nur im veralteten Statistik-Typ
"track", der aus der Spezifikation entfernt wurde. - Performance:
getStats()ist asynchron und nicht ganz kostenlos. Fragen im Abstand von 1–2 Sekunden reicht für Monitoring meistens aus.