Signatur
Beschreibung
Die schreibgeschützte Eigenschaft concealmentEvents des Wörterbuchs RTCInboundRtpStreamStats ist ein Zähler vom Typ number, der nur bei Audio-Streams (kind === "audio") vorhanden ist. Er gibt an, wie oft der Jitter-Buffer des Empfängers fehlende oder zu spät eingetroffene Pakete durch künstlich erzeugte Samples ersetzen musste. Dieses Verfahren heißt Packet Loss Concealment (PLC), also das Verschleiern von Paketverlusten.
Wichtig: Gezählt wird ein Ereignis, nicht jedes einzelne Sample. Folgen mehrere verschleierte Samples direkt aufeinander, zählt das nur als ein Ereignis. Erst wenn nach echt empfangenen Samples wieder verschleiert werden muss, erhöht sich der Zähler. Wie viele Samples insgesamt betroffen waren, zeigt die Eigenschaft concealedSamples. Beide Werte zusammen sagen etwas über die Art der Störung aus: Viele Ereignisse mit wenigen Samples deuten auf häufige kurze Aussetzer hin, wenige Ereignisse mit vielen Samples auf längere Ausfälle.
Den Wert erhält man über RTCPeerConnection.getStats() oder RTCRtpReceiver.getStats(). Man sucht darin die Einträge mit type === "inbound-rtp". Da der Zähler über die gesamte Lebensdauer des Statistikobjekts nur wächst, bildet man für eine Echtzeit-Überwachung am besten die Differenz zwischen zwei Messungen.
Rückgabewert
undefined.Beispiele
Concealment-Ereignisse eines Audio-Empfängers auslesen
// pc ist eine bestehende, verbundene RTCPeerConnection
const logConcealment = async (pc) => {
const stats = await pc.getStats();
stats.forEach((report) => {
if (report.type === 'inbound-rtp' && report.kind === 'audio') {
console.log(`Concealment-Ereignisse: ${report.concealmentEvents}`);
console.log(`Verschleierte Samples: ${report.concealedSamples}`);
}
});
};
await logConcealment(pc);
Neue Aussetzer pro Intervall überwachen
// Differenz zwischen zwei Messungen = Ereignisse im letzten Intervall
const watchAudioGlitches = (receiver, intervalMs = 2000) => {
let previous = 0;
return setInterval(async () => {
const stats = await receiver.getStats();
for (const report of stats.values()) {
if (report.type !== 'inbound-rtp' || report.kind !== 'audio') continue;
const current = report.concealmentEvents ?? 0;
const delta = current - previous;
previous = current;
if (delta > 3) {
console.warn(`Audioqualität schlecht: ${delta} Aussetzer in ${intervalMs} ms`);
}
}
}, intervalMs);
};
const [audioReceiver] = pc.getReceivers().filter((r) => r.track.kind === 'audio');
const timerId = watchAudioGlitches(audioReceiver);
// später: clearInterval(timerId);
Durchschnittliche Länge eines Aussetzers berechnen
const averageConcealmentMs = async (receiver) => {
const stats = await receiver.getStats();
for (const r of stats.values()) {
if (r.type === 'inbound-rtp' && r.kind === 'audio' && r.concealmentEvents > 0) {
// Wie viele verschleierte Samples im Schnitt auf ein Ereignis kommen
const samplesPerEvent = r.concealedSamples / r.concealmentEvents;
// Die Abtastrate bei Opus beträgt typischerweise 48000 Hz
return (samplesPerEvent / 48000) * 1000;
}
}
return 0;
};
console.log(`Ø ${(await averageConcealmentMs(audioReceiver)).toFixed(1)} ms pro Aussetzer`);
// Wichtig · Fallstricke
- Nur bei Audio: Bei Video-Streams (
kind === "video") fehlt die Eigenschaft. Mit?? 0oder einer Prüfung vonkindvermeidet manNaNin Berechnungen. - Zähler statt Rate: Der Wert steigt nur an. Wie gut die Qualität gerade ist, zeigt erst die Differenz zwischen zwei Messungen.
- Ereignis ≠ Sample: Ein langer Ausfall zählt nur als ein Ereignis. Um die Schwere eines Ausfalls zu beurteilen, sollte man immer auch
concealedSamplesundsilentConcealedSamplesheranziehen. - Browser-Kompatibilität: Chromium-Browser unterstützen die Eigenschaft seit Version 83, Firefox erst seit neueren Versionen (etwa ab 113). Safari liefert sie bislang nicht. Bei der Auswertung sollte man deshalb immer mit
undefinedrechnen. - Ereignisse, die aus Stille entstehen (etwa wegen DTX), können trotzdem mitgezählt werden. Bei sehr ruhigen Sprechern kann der Wert daher höher sein, ohne dass man eine Störung hört.