Passa al contenuto principale

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:

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
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.