Signatur
Beschreibung
Die Eigenschaft keyFramesDecoded gehört zum Wörterbuch RTCInboundRtpStreamStats. Dieses Objekt steht in dem RTCStatsReport, den RTCPeerConnection.getStats() liefert, und zwar in Einträgen vom Typ "inbound-rtp". Der Wert ist ein fortlaufender Zähler (unsigned long). Er zählt die Schlüsselbilder, die der Decoder für diesen Medienstrom bisher erfolgreich verarbeitet hat. Bei VP8 sind das Keyframes, bei H.264 IDR-Frames.
Ein Schlüsselbild ist ein vollständiges Bild, das nicht von früheren Bildern abhängt. Nach einem Paketverlust, einem Beitritt zu einer laufenden Sitzung oder einem Auflösungswechsel muss der Empfänger ein neues Keyframe erhalten, bevor er wieder sauber dekodieren kann. Deshalb eignet sich keyFramesDecoded gut für die Diagnose der Videoqualität. Steigt der Zähler oft, fordert der Empfänger häufig neue Keyframes an (per PLI/FIR). Das deutet meist auf Paketverlust oder eine instabile Verbindung hin.
Aussagekräftig wird der Wert vor allem im Vergleich mit anderen Kennzahlen. Das Verhältnis zu framesDecoded zeigt den Anteil der Schlüsselbilder an allen dekodierten Bildern. pliCount und firCount zeigen, wie viele Keyframe-Anfragen der Empfänger gesendet hat. Die Eigenschaft gibt es nur bei Video-Strömen (kind === "video").
Rückgabewert
undefined.Beispiele
Anzahl dekodierter Keyframes auslesen
// pc ist eine bestehende, verbundene RTCPeerConnection
const logKeyFrames = async (pc) => {
const report = await pc.getStats();
report.forEach((stats) => {
if (stats.type === 'inbound-rtp' && stats.kind === 'video') {
console.log(`SSRC ${stats.ssrc}: ${stats.keyFramesDecoded ?? 'n/a'} Keyframes dekodiert`);
}
});
};
await logKeyFrames(pc);
Keyframe-Rate pro Intervall überwachen
// Misst alle 5 Sekunden, wie viele neue Keyframes dazugekommen sind
const monitorKeyFrames = (pc, intervalMs = 5000) => {
let last = new Map();
const timer = setInterval(async () => {
const report = await pc.getStats();
for (const stats of report.values()) {
if (stats.type !== 'inbound-rtp' || stats.kind !== 'video') continue;
if (typeof stats.keyFramesDecoded !== 'number') continue;
const prev = last.get(stats.id) ?? stats.keyFramesDecoded;
const delta = stats.keyFramesDecoded - prev;
last.set(stats.id, stats.keyFramesDecoded);
if (delta > 2) {
console.warn(`Viele Keyframes (${delta}) in ${intervalMs / 1000}s – mögliche Paketverluste`);
}
}
}, intervalMs);
return () => clearInterval(timer);
};
const stopMonitoring = monitorKeyFrames(pc);
// später: stopMonitoring();
Keyframe-Anteil und PLI-Anfragen vergleichen
const analyzeVideo = async (receiver) => {
// getStats() direkt am RTCRtpReceiver liefert nur die Statistik dieses Tracks
const report = await receiver.getStats();
for (const s of report.values()) {
if (s.type === 'inbound-rtp' && s.kind === 'video' && s.framesDecoded > 0) {
const ratio = ((s.keyFramesDecoded / s.framesDecoded) * 100).toFixed(2);
console.log({
framesDecoded: s.framesDecoded,
keyFramesDecoded: s.keyFramesDecoded,
keyFrameAnteil: `${ratio} %`,
pliCount: s.pliCount,
});
}
}
};
const [videoReceiver] = pc.getReceivers().filter((r) => r.track.kind === 'video');
await analyzeVideo(videoReceiver);
// Wichtig · Fallstricke
- Nur Video: Bei Audio-Einträgen vom Typ
inbound-rtpfehlt die Eigenschaft. Filtern Sie daher immer nachkind === "video". - Kumulativer Zähler: Der Wert steigt über die ganze Lebensdauer des Stroms nur an. Um Raten zu berechnen, bilden Sie die Differenz zwischen zwei Messungen und beziehen sie auf die Zeit (
timestamp). - Browser-Kompatibilität: Chromium-basierte Browser und neuere Safari-Versionen unterstützen die Eigenschaft. Firefox liefert sie derzeit nicht. Prüfen Sie deshalb mit
typeof stats.keyFramesDecoded === "number", bevor Sie rechnen. - Simulcast/SVC: Wechselt der SFU die Ebene oder die Auflösung, kommen oft zusätzliche Keyframes. Ein Anstieg bedeutet also nicht zwingend Paketverlust.
- Performance:
getStats()ist asynchron und nicht kostenlos. Fragen Sie die Statistik höchstens etwa einmal pro Sekunde ab.