demo · v139
Audio Mix Normalizer
Each RTCEncodedAudioFrame carries an audioLevel field on the 0–127 dBov scale (0 = maximum loudness, 127 = silence). A transform pipeline reads this per-frame without decoding and computes the gain correction needed to hit a target loudness.
Checking RTCEncodedAudioFrame support…
dBov scale: 0 = maximum possible amplitude (full scale), 127 = effectively silence. Lower numbers are louder. The gain factor for a participant is computed as
gain_dB = target_dBov − participant_dBov. A positive result means boost; negative means attenuate.
RTCEncodedTransform pattern
const sender = pc.getSenders().find(s => s.track.kind === 'audio');
const { readable, writable } = sender.createEncodedStreams();
const targetLevel = -20; // dBov
readable.pipeThrough(new TransformStream({
transform(frame, controller) {
const meta = frame.getMetadata();
// meta.audioLevel: 0..127 dBov (Chrome 139+)
const gainDb = targetLevel - meta.audioLevel;
// gainDb > 0 → boost participant (they are quieter than target)
// gainDb < 0 → cut participant (they are louder than target)
// Apply gain to the raw PCM in frame.data (Int16Array)
const pcm = new Int16Array(frame.data);
const factor = Math.pow(10, gainDb / 20);
for (let i = 0; i < pcm.length; i++) {
pcm[i] = Math.max(-32768, Math.min(32767, Math.round(pcm[i] * factor)));
}
controller.enqueue(frame);
}
})).pipeTo(writable);