Aller au contenu principal

RFC 8854 - Exigences de codec et de traitement audio WebRTC

  • Statut: Proposed Standard
  • Publié: January 2021
  • Stream: IETF
  • Errata: Pas d'errata

Informations de base

  • RFC-Nummer: 8854
  • Titel: WebRTC Audio Codec and Processing Requirements
  • Titre français: Exigences de codec et de traitement audio WebRTC
  • Date de publication: Janvier 2021
  • Status: PROPOSED STANDARD (Norme proposée)
  • Autoren: J.-M. Valin, C. Bran

Résumé (Abstract)

Ce document définit les exigences de traitement audio et de codec pour les points de terminaison WebRTC. Il spécifie les codecs et les fonctionnalités de traitement audio qui doivent être implémentés pour garantir l'interopérabilité entre différentes implémentations WebRTC.

Aperçu des exigences audio WebRTC

Codecs requis

Opus (requis):

Status: MUST (doit être implémenté)
Standard: RFC 6716
Caractéristiques:
✅ Faible latence (5-60ms)
✅ Débit variable
✅ Support large bande/super large bande/pleine bande
✅ Optimisation voix et musique
✅ In-Band-FEC (Forward Error Correction)
✅ Sans redevance

Taux d'échantillonnage: 48000 Hz
Canaux: 1-2 (mono/stéréo)
Bitrate: 6-510 kbps

G.711 (requis):

Status: MUST (doit être implémenté)
Objectif: Interopérabilité avec les systèmes téléphoniques traditionnels
Variantes:
- PCMU (μ-law): Nordamerika, Japan
- PCMA (A-law): Europa, Rest der Welt

Caractéristiques:
- 64 kbps fixe
- 8 kHz Abtastrate
- Qualité audio bande étroite
- Complexité extrêmement faible

Exigences de traitement audio

Annulation d'écho acoustique (AEC - Acoustic Echo Cancellation):

Status: MUST (doit être implémenté)
Objectif: Éliminer l'écho haut-parleur vers microphone

Scénario:
🔊 Lautsprecher → Mikrofon → L'autre partie entend sa propre voix (écho)

Traitement AEC:
Entrée microphone - Signal de référence haut-parleur = Voix pure

Exigences:
✓ Au moins 45dB de suppression d'écho
✓ Adaptation aux changements temporels
✓ Gestion des distorsions non linéaires

Suppression du bruit (NS - Noise Suppression):

Status: SHOULD (devrait être implémenté)
Objectif: Réduire le bruit de fond

Bruits courants:
- Frappes clavier
- Bruit de vent
- Bruit de circulation
- Bruit de climatisation

Traitement:
✓ Analyse spectrale
✓ Estimation du bruit
✓ Suppression sélective

Contrôle automatique de gain (AGC - Automatic Gain Control):

Status: SHOULD (devrait être implémenté)
Objectif: Normaliser le volume audio

Problèmes:
- Distance variable au microphone
- Sensibilités différentes des microphones
- Volumes de parole différents

AGC-Traitement:
✓ Détection du niveau audio
✓ Ajustement dynamique du gain
✓ Maintien d'un volume cohérent

Détails du codec Opus

Paramètres de configuration Opus

RTP Payload Type:

Standard: Dynamische Zuweisung (normalerweise 111)
SDP-Beispiel:
m=audio 54321 RTP/SAVPF 111
a=rtpmap:111 opus/48000/2
a=fmtp:111 minptime=10;useinbandfec=1

Abtastrate:

Intern: 48000 Hz (fest)
Eingabe: Variabel (8-48 kHz)
Traitement: Internes Resampling auf 48kHz

Hinweis: In RTP immer als 48000 gekennzeichnet

Kanalanzahl:

Mono: 1 Kanal
Stereo: 2 Kanäle

In SDP:
a=rtpmap:111 opus/48000/2 ← /2 bedeutet Stereo
a=rtpmap:111 opus/48000/1 ← /1 bedeutet Mono (kann auch weggelassen werden)

fmtp-Parameter:

ParameterBedeutungStandardwertBeschreibung
maxplaybackrateMaximale Wiedergabe-Abtastrate48000Vom Decoder unterstützte maximale Abtastrate
sprop-maxcapturerateAufnahme-Abtastrate des Senders48000Vom Encoder verwendete Abtastrate
maxaveragebitrateMaximale durchschnittliche BitrateUnbegrenztbps
stereoStereo-Präferenz01=Stereo bevorzugt
sprop-stereoStereo-Fähigkeit des Senders01=Kann Stereo senden
cbrKonstante Bitrate01=CBR, 0=VBR
useinbandfecIn-Band-FEC verwenden01=FEC aktiviert
usedtxDTX verwenden01=DTX aktiviert
maxptimeMaximale Paketdauer120Millisekunden
ptimePaketdauer20Millisekunden

Vollständiges SDP-Beispiel:

m=audio 54321 RTP/SAVPF 111
a=rtpmap:111 opus/48000/2
a=fmtp:111 minptime=10;useinbandfec=1;stereo=1;maxplaybackrate=48000;maxaveragebitrate=510000
a=ptime:20
a=maxptime:60

Opus In-Band-FEC (Forward Error Correction)

Funktionsweise:

Normaler Fall:
Packet 1: Frame 1
Packet 2: Frame 2
Packet 3: Frame 3

Mit aktiviertem FEC:
Packet 1: Frame 1 + FEC(Frame 0)
Packet 2: Frame 2 + FEC(Frame 1)
Packet 3: Frame 3 + FEC(Frame 2)

Paketverlust-Wiederherstellung:
Packet 1: [Verloren]
Packet 2: Empfangen → Dekodiere Frame 2 + Stelle Frame 1 wieder her
Ergebnis: Frame 1 und 2 sind beide verfügbar!

FEC aktivieren:

// JavaScript (über SDP)
const offer = await pc.createOffer();
offer.sdp = offer.sdp.replace(
/a=fmtp:111 /,
'a=fmtp:111 useinbandfec=1;'
);
await pc.setLocalDescription(offer);

FEC-Overhead:

Bitrate-Erhöhung: Etwa 15-25%
Latenz-Erhöhung: Minimal (1 Paketintervall)
Paketverlust-Wiederherstellungsrate: Einzelpaket-Verlust nahe 100%

Opus DTX (Discontinuous Transmission)

Definition:

DTX = Diskontinuierliche Übertragung
Objectif: Keine Datenpakete während Stilleperioden senden
Vorteile:
✅ Reduzierung der Bandbreitennutzung
✅ Reduzierung des Verarbeitungsaufwands
✅ Batteriesparend (mobile Geräte)

Stille-Erkennung:
Voice Activity Detection (VAD) → Stille → Übertragung stoppen

DTX aktivieren:

SDP:
a=fmtp:111 usedtx=1

Effekt:
Beim Sprechen: Normale Paketübertragung
Bei Stille: Übertragung stoppen oder stark reduzieren

JavaScript-API-Nutzung

Audio-Track abrufen

// Grundlegender Abruf
const stream = await navigator.mediaDevices.getUserMedia({
audio: true
});

// Detaillierte Konfiguration
const stream = await navigator.mediaDevices.getUserMedia({
audio: {
echoCancellation: true, // Annulation d'écho acoustique
noiseSuppression: true, // Suppression du bruit
autoGainControl: true, // Contrôle automatique de gain
sampleRate: 48000, // Abtastrate
channelCount: 1, // Mono
latency: 0.01, // 10ms Latenz
sampleSize: 16 // 16-Bit-Abtastung
}
});

const audioTrack = stream.getAudioTracks()[0];
console.log('Audio settings:', audioTrack.getSettings());

Audio-Einschränkungen konfigurieren

// Hochwertige Musik
const musicStream = await navigator.mediaDevices.getUserMedia({
audio: {
echoCancellation: false, // Musik benötigt kein AEC
noiseSuppression: false, // Originalton beibehalten
autoGainControl: false, // Dynamikbereich nicht komprimieren
sampleRate: 48000,
channelCount: 2, // Stereo
latency: 0.02 // Etwas höhere Latenz für bessere Qualität
}
});

// Faible latence für Sprache
const voiceStream = await navigator.mediaDevices.getUserMedia({
audio: {
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true,
sampleRate: 16000, // Breitband ausreichend
channelCount: 1,
latency: 0.005 // 5ms extrem niedrige Latenz
}
});

Audio-Qualität überwachen

// Statistikinformationen abrufen
setInterval(async () => {
const stats = await pc.getStats();

stats.forEach(report => {
if (report.type === 'inbound-rtp' && report.kind === 'audio') {
console.log('Audio stats:', {
packetsLost: report.packetsLost,
packetsReceived: report.packetsReceived,
jitter: report.jitter,
audioLevel: report.audioLevel,
totalAudioEnergy: report.totalAudioEnergy
});

// Paketverlustrate berechnen
const lossRate = report.packetsLost /
(report.packetsReceived + report.packetsLost);
console.log('Packet loss:', (lossRate * 100).toFixed(2) + '%');
}

if (report.type === 'outbound-rtp' && report.kind === 'audio') {
console.log('Outbound audio:', {
packetsSent: report.packetsSent,
bytesSent: report.bytesSent,
targetBitrate: report.targetBitrate
});
}
});
}, 1000);

Bitrate dynamisch anpassen

// Audio-Sender abrufen
const audioSender = pc.getSenders()
.find(s => s.track && s.track.kind === 'audio');

if (audioSender) {
const params = audioSender.getParameters();

if (!params.encodings) {
params.encodings = [{}];
}

// Maximale Bitrate festlegen
params.encodings[0].maxBitrate = 128000; // 128 kbps

await audioSender.setParameters(params);
}

Audio-Verarbeitung in der Praxis

Stille-Erkennung

class VoiceActivityDetector {
constructor(stream) {
this.audioContext = new AudioContext();
this.source = this.audioContext.createMediaStreamSource(stream);
this.analyser = this.audioContext.createAnalyser();
this.analyser.fftSize = 2048;

this.source.connect(this.analyser);

this.dataArray = new Uint8Array(this.analyser.frequencyBinCount);
this.threshold = 30; // Lautstärkeschwelle
this.isSpeaking = false;
}

start(callback) {
const check = () => {
this.analyser.getByteFrequencyData(this.dataArray);

// Durchschnittliche Lautstärke berechnen
const average = this.dataArray.reduce((a, b) => a + b) / this.dataArray.length;

const speaking = average > this.threshold;

if (speaking !== this.isSpeaking) {
this.isSpeaking = speaking;
callback(speaking);
}

requestAnimationFrame(check);
};

check();
}
}

// Verwendung
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
const vad = new VoiceActivityDetector(stream);

vad.start((speaking) => {
console.log(speaking ? 'Spricht' : 'Stille');

// UI basierend auf Status anpassen
if (speaking) {
micIcon.classList.add('active');
} else {
micIcon.classList.remove('active');
}
});

Audio-Visualisierung

class AudioVisualizer {
constructor(stream, canvas) {
this.canvas = canvas;
this.ctx = canvas.getContext('2d');
this.audioContext = new AudioContext();
this.source = this.audioContext.createMediaStreamSource(stream);
this.analyser = this.audioContext.createAnalyser();
this.analyser.fftSize = 256;

this.source.connect(this.analyser);

this.bufferLength = this.analyser.frequencyBinCount;
this.dataArray = new Uint8Array(this.bufferLength);
}

start() {
const draw = () => {
requestAnimationFrame(draw);

this.analyser.getByteFrequencyData(this.dataArray);

this.ctx.fillStyle = 'rgb(0, 0, 0)';
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);

const barWidth = (this.canvas.width / this.bufferLength) * 2.5;
let x = 0;

for (let i = 0; i < this.bufferLength; i++) {
const barHeight = this.dataArray[i] / 2;

this.ctx.fillStyle = `rgb(50, ${barHeight + 100}, 50)`;
this.ctx.fillRect(
x,
this.canvas.height - barHeight,
barWidth,
barHeight
);

x += barWidth + 1;
}
};

draw();
}
}

// Verwendung
const canvas = document.getElementById('visualizer');
const visualizer = new AudioVisualizer(stream, canvas);
visualizer.start();

Benutzerdefinierte Audio-Verarbeitung

class CustomAudioProcessor {
constructor(stream) {
this.audioContext = new AudioContext();
this.source = this.audioContext.createMediaStreamSource(stream);
this.destination = this.audioContext.createMediaStreamDestination();

// Verstärkungsknoten erstellen
this.gainNode = this.audioContext.createGain();
this.gainNode.gain.value = 1.0;

// Filter erstellen
this.filter = this.audioContext.createBiquadFilter();
this.filter.type = 'lowpass';
this.filter.frequency.value = 5000; // Tiefpassfilter

// Audio-Graph verbinden
this.source
.connect(this.gainNode)
.connect(this.filter)
.connect(this.destination);
}

getProcessedStream() {
return this.destination.stream;
}

setVolume(value) {
this.gainNode.gain.value = value;
}

setFilterFrequency(freq) {
this.filter.frequency.value = freq;
}
}

// Verwendung
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
const processor = new CustomAudioProcessor(stream);

// Verarbeiteten Stream verwenden
const processedStream = processor.getProcessedStream();
processedStream.getTracks().forEach(track => {
pc.addTrack(track, processedStream);
});

// Dynamisch anpassen
processor.setVolume(1.5); // Lautstärke erhöhen
processor.setFilterFrequency(3000); // Filter anpassen

Best Practices

1. Geeignete Konfiguration wählen

// Konfiguration basierend auf Anwendungsfall wählen
const configs = {
// Hochwertige Musik
music: {
audio: {
echoCancellation: false,
noiseSuppression: false,
autoGainControl: false,
sampleRate: 48000,
channelCount: 2
},
sdp: {
maxaveragebitrate: 510000, // 510 kbps
stereo: 1,
useinbandfec: 1,
usedtx: 0
}
},

// Standard-Sprachanruf
voice: {
audio: {
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true,
sampleRate: 48000,
channelCount: 1
},
sdp: {
maxaveragebitrate: 40000, // 40 kbps
useinbandfec: 1,
usedtx: 1
}
},

// Niedrige Bandbreite
lowBandwidth: {
audio: {
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true,
sampleRate: 16000,
channelCount: 1
},
sdp: {
maxaveragebitrate: 16000, // 16 kbps
maxplaybackrate: 16000,
useinbandfec: 0,
usedtx: 1
}
}
};

// Konfiguration anwenden
async function setupAudio(useCase) {
const config = configs[useCase];
const stream = await navigator.mediaDevices.getUserMedia(config);

// SDP modifizieren, um Codec-Parameter anzuwenden
const offer = await pc.createOffer();
offer.sdp = applyOpusParams(offer.sdp, config.sdp);
await pc.setLocalDescription(offer);
}

2. Netzwerkanpassung

class AdaptiveAudioManager {
constructor(pc) {
this.pc = pc;
this.currentBitrate = 64000;
this.monitor();
}

async monitor() {
setInterval(async () => {
const stats = await this.pc.getStats();
let packetsLost = 0;
let packetsReceived = 0;

stats.forEach(report => {
if (report.type === 'inbound-rtp' && report.kind === 'audio') {
packetsLost = report.packetsLost;
packetsReceived = report.packetsReceived;
}
});

const lossRate = packetsLost / (packetsReceived + packetsLost);

if (lossRate > 0.05) {
// Paketverlust >5%, Bitrate reduzieren
this.adjustBitrate(this.currentBitrate * 0.8);
} else if (lossRate < 0.01) {
// Paketverlust <1%, Bitrate kann erhöht werden
this.adjustBitrate(Math.min(this.currentBitrate * 1.2, 128000));
}
}, 2000);
}

async adjustBitrate(newBitrate) {
this.currentBitrate = newBitrate;

const sender = this.pc.getSenders()
.find(s => s.track && s.track.kind === 'audio');

if (sender) {
const params = sender.getParameters();
if (!params.encodings) params.encodings = [{}];
params.encodings[0].maxBitrate = newBitrate;
await sender.setParameters(params);

console.log(`Adjusted audio bitrate to ${(newBitrate/1000).toFixed(0)} kbps`);
}
}
}

3. Fehlerbehandlung

async function setupRobustAudio() {
try {
// Zuerst ideale Konfiguration versuchen
const stream = await navigator.mediaDevices.getUserMedia({
audio: {
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true,
sampleRate: 48000
}
});

return stream;

} catch (err) {
console.warn('Ideal config failed, trying fallback:', err);

try {
// Auf Basiskonfiguration herabstufen
const stream = await navigator.mediaDevices.getUserMedia({
audio: true
});

return stream;

} catch (err2) {
console.error('Audio access denied:', err2);

// Fehler dem Benutzer anzeigen
showError('Mikrofonzugriff nicht möglich, bitte Berechtigungseinstellungen prüfen');
throw err2;
}
}
}

Referenzen

RFCs audio WebRTC:

  • [RFC 8854] WebRTC Audio Codec and Processing Requirements ← Ce document
  • [RFC 6716] Opus Interactive Audio Codec
  • [RFC 7874] WebRTC Audio Codec and Processing Requirements (durch RFC 8854 ersetzt)

Standards associés:

  • [RFC 8834] WebRTC Media Transport
  • [RFC 7587] RTP Payload Format for Opus

Zusammenfassung: Opus ist der Kern-Audio-Codec von WebRTC und bietet exzellente Audioqualität, niedrige Latenz und Bandbreitenanpassungsfähigkeit. In Kombination mit Annulation d'écho acoustique, Suppression du bruit und anderen Audio-Verarbeitungsfunktionen kann WebRTC hochwertige Echtzeit-Sprachkommunikation bereitstellen. Das Verständnis dieser Technologien und Konfigurationsoptionen ist entscheidend für den Aufbau exzellenter WebRTC-Anwendungen.