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:
| Parameter | Bedeutung | Standardwert | Beschreibung |
|---|---|---|---|
maxplaybackrate | Maximale Wiedergabe-Abtastrate | 48000 | Vom Decoder unterstützte maximale Abtastrate |
sprop-maxcapturerate | Aufnahme-Abtastrate des Senders | 48000 | Vom Encoder verwendete Abtastrate |
maxaveragebitrate | Maximale durchschnittliche Bitrate | Unbegrenzt | bps |
stereo | Stereo-Präferenz | 0 | 1=Stereo bevorzugt |
sprop-stereo | Stereo-Fähigkeit des Senders | 0 | 1=Kann Stereo senden |
cbr | Konstante Bitrate | 0 | 1=CBR, 0=VBR |
useinbandfec | In-Band-FEC verwenden | 0 | 1=FEC aktiviert |
usedtx | DTX verwenden | 0 | 1=DTX aktiviert |
maxptime | Maximale Paketdauer | 120 | Millisekunden |
ptime | Paketdauer | 20 | Millisekunden |
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.