RFC 8854 - Requisiti di codec e elaborazione audio WebRTC
- Stato: Proposed Standard
- Pubblicato: January 2021
- Stream: IETF
- Errata: Nessun errata
Informazioni di base
- RFC-Nummer: 8854
- Titel: WebRTC Audio Codec and Processing Requirements
- Titolo italiano: Requisiti di codec e elaborazione audio WebRTC
- Data di pubblicazione: Gennaio 2021
- Status: PROPOSED STANDARD (Standard proposto)
- Autoren: J.-M. Valin, C. Bran
Sommario (Abstract)
Questo documento definisce i requisiti di elaborazione audio e codec per gli endpoint WebRTC. Specifica i codec e le funzionalità di elaborazione audio che devono essere implementati per garantire l'interoperabilità tra diverse implementazioni WebRTC.
Panoramica dei requisiti audio WebRTC
Codec richiesti
Opus (richiesto):
Status: MUST (deve essere implementato)
Standard: RFC 6716
Caratteristiche:
✅ Bassa latenza (5-60ms)
✅ Bitrate variabile
✅ Supporto banda larga/super banda larga/banda completa
✅ Ottimizzazione voce e musica
✅ In-Band-FEC (Forward Error Correction)
✅ Senza royalty
Frequenza di campionamento: 48000 Hz
Canali: 1-2 (mono/stereo)
Bitrate: 6-510 kbps
G.711 (richiesto):
Status: MUST (deve essere implementato)
Scopo: Interoperabilità con sistemi telefonici tradizionali
Varianti:
- PCMU (μ-law): Nordamerika, Japan
- PCMA (A-law): Europa, Rest der Welt
Caratteristiche:
- 64 kbps fisso
- 8 kHz Abtastrate
- Qualità audio a banda stretta
- Complessità estremamente bassa
Requisiti di elaborazione audio
Cancellazione dell'eco acustica (AEC - Acoustic Echo Cancellation):
Status: MUST (deve essere implementato)
Scopo: Eliminare l'eco dall'altoparlante al microfono
Scenario:
🔊 Lautsprecher → Mikrofon → L'altra parte sente la propria voce (eco)
Elaborazione AEC:
Ingresso microfono - Segnale di riferimento altoparlante = Voce pura
Requisiti:
✓ Almeno 45dB di soppressione dell'eco
✓ Adattamento ai cambiamenti temporali
✓ Gestione delle distorsioni non lineari
Soppressione del rumore (NS - Noise Suppression):
Status: SHOULD (dovrebbe essere implementato)
Scopo: Ridurre il rumore di fondo
Rumori comuni:
- Digitazione sulla tastiera
- Rumore del vento
- Rumore del traffico
- Rumore del condizionatore
Elaborazione:
✓ Analisi spettrale
✓ Stima del rumore
✓ Soppressione selettiva
Controllo automatico del guadagno (AGC - Automatic Gain Control):
Status: SHOULD (dovrebbe essere implementato)
Scopo: Normalizzare il volume audio
Problemi:
- Distanza variabile dal microfono
- Sensibilità diverse dei microfoni
- Volumi di voce diversi
AGC-Elaborazione:
✓ Rilevamento del livello audio
✓ Regolazione dinamica del guadagno
✓ Mantenimento di un volume coerente
Dettagli del codec Opus
Parametri di configurazione 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)
Elaborazione: 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
Scopo: 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, // Cancellazione dell'eco acustica
noiseSuppression: true, // Soppressione del rumore
autoGainControl: true, // Controllo automatico del guadagno
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
}
});
// Bassa latenza 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
RFC audio WebRTC:
- [RFC 8854] WebRTC Audio Codec and Processing Requirements ← Dieses Dokument
- [RFC 6716] Opus Interactive Audio Codec
- [RFC 7874] WebRTC Audio Codec and Processing Requirements (durch RFC 8854 ersetzt)
Standard correlati:
- [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 Cancellazione dell'eco acustica, Soppressione del rumore 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.