Start · Sprachen · JavaScript · Referenz · RTCInboundRtpStreamStats.concealedSamples

RTCInboundRtpStreamStats.concealedSamples

Eigenschaft

Gesamtzahl der Audio-Samples eines empfangenen Tracks, die der Empfänger lokal erzeugt hat (Packet Loss Concealment), weil die Originaldaten verloren gingen oder zu spät ankamen.

seit JavaScript WebRTC Statistics API – Chromium-Browser und Safar Kategorie: webapi

Signatur

RTCInboundRtpStreamStats.concealedSamples

Beschreibung

Die Eigenschaft concealedSamples gehört zu einem Statistikobjekt vom Typ "inbound-rtp", das du über RTCPeerConnection.getStats() oder RTCRtpReceiver.getStats() erhältst. Sie zählt über die gesamte Lebensdauer des Statistikobjekts, wie viele Audio-Samples verdeckt (englisch concealed) wurden. Verdeckt heißt: Der Decoder hat das Sample nicht aus empfangenen Daten gewonnen, sondern lokal synthetisiert und so abgespielt, etwa durch Interpolation oder Komfortrauschen.

Verdeckung wird nötig, wenn RTP-Pakete verloren gehen (siehe packetsLost) oder zu spät ankommen und vom Jitter-Buffer verworfen werden (siehe packetsDiscarded). Der Wert ist deshalb ein guter Indikator dafür, wie der Nutzer die Audioqualität erlebt. Setzt du ihn ins Verhältnis zu totalSamplesReceived, erhältst du den Anteil verdeckten Audios, also eine gut vergleichbare Kennzahl für Dashboards oder eine adaptive Qualitätssteuerung.

Die Eigenschaft gibt es nur bei Audio-Streams (kind === "audio"). Bei Video-Streams fehlt sie. Der Zähler wächst monoton. Für eine Aussage über die aktuelle Qualität bildest du daher die Differenz zwischen zwei aufeinanderfolgenden Messungen, statt den absoluten Wert zu verwenden.

Rückgabewert

Typ
number
Beschreibung
Nicht-negative Ganzzahl (WebIDL unsigned long long): Summe aller verdeckten Samples seit Beginn des Statistikobjekts. Bei Video-Streams oder in Browsern ohne Unterstützung ist der Wert undefined.

Beispiele

Verdeckte Samples aller Audio-Empfänger ausgeben

// pc ist eine bestehende, verbundene RTCPeerConnection
const logConcealedSamples = async (pc) => {
  const report = await pc.getStats();
  report.forEach((stats) => {
    if (stats.type === 'inbound-rtp' && stats.kind === 'audio') {
      console.log(`SSRC ${stats.ssrc}: ${stats.concealedSamples ?? 'n/a'} verdeckte Samples`);
    }
  });
};

await logConcealedSamples(pc);
SSRC 3735928559: 4320 verdeckte Samples

Anteil verdeckten Audios berechnen

const getConcealmentRatio = async (receiver) => {
  const report = await receiver.getStats();
  for (const stats of report.values()) {
    if (stats.type === 'inbound-rtp' && stats.kind === 'audio') {
      const { concealedSamples = 0, totalSamplesReceived = 0 } = stats;
      if (totalSamplesReceived === 0) return 0;
      return concealedSamples / totalSamplesReceived;
    }
  }
  return null; // kein Audio-Empfang
};

const [audioReceiver] = pc.getReceivers().filter((r) => r.track.kind === 'audio');
const ratio = await getConcealmentRatio(audioReceiver);
console.log(`Verdeckt: ${(ratio * 100).toFixed(2)} %`);
Verdeckt: 0.87 %

Aktuelle Audioqualität per Intervall-Differenz überwachen

const monitorAudioQuality = (pc, intervalMs = 2000) => {
  let previous = null;

  const timer = setInterval(async () => {
    const report = await pc.getStats();
    const stats = [...report.values()].find(
      (s) => s.type === 'inbound-rtp' && s.kind === 'audio'
    );
    if (!stats || stats.concealedSamples === undefined) return;

    if (previous) {
      const concealed = stats.concealedSamples - previous.concealedSamples;
      const received = stats.totalSamplesReceived - previous.totalSamplesReceived;
      const ratio = received > 0 ? concealed / received : 0;
      if (ratio > 0.05) {
        console.warn(`Schlechte Audioqualität: ${(ratio * 100).toFixed(1)} % verdeckt`);
      }
    }
    previous = stats;
  }, intervalMs);

  return () => clearInterval(timer); // Funktion zum Beenden
};

const stopMonitoring = monitorAudioQuality(pc);
// später: stopMonitoring();
Schlechte Audioqualität: 7.3 % verdeckt

// Wichtig · Fallstricke

  • Nur Audio: Bei inbound-rtp-Statistiken mit kind === "video" ist die Eigenschaft nicht vorhanden. Filtere immer nach kind.
  • Kumulativer Zähler: Der Wert sinkt nie. Für die aktuelle Qualität brauchst du Differenzen zwischen zwei Messungen. Wird der Stream neu ausgehandelt oder eine neue SSRC verwendet, kann ein neues Statistikobjekt mit einem Zähler ab 0 entstehen. Prüfe deshalb id bzw. ssrc, bevor du Differenzen bildest.
  • Stille wird mitgezählt: concealedSamples enthält auch verdeckte Samples, die als Stille bzw. Komfortrauschen abgespielt wurden. Diese Teilmenge steht separat in silentConcealedSamples. Wie oft verdeckt wurde (die Anzahl der Ereignisse, nicht der Samples), liefert concealmentEvents.
  • Einheit: Gezählt werden Samples, keine Pakete und keine Millisekunden. Umrechnen in Zeit kannst du nur mit der Abtastrate des Codecs (z. B. 48 000 Hz bei Opus). Bei Mehrkanal-Audio kann die Zählweise je nach Implementierung abweichen.
  • Browser-Kompatibilität: Chromium-basierte Browser und Safari liefern den Wert. Firefox unterstützt die Audio-Empfangsstatistiken nicht oder nur teilweise. Verwende daher Fallbacks wie stats.concealedSamples ?? 0 und prüfe auf undefined.