mirror of
https://github.com/suitenumerique/meet.git
synced 2026-07-26 11:58:53 +00:00
✨(frontend) enhance noise reduction with BBBA audio processing pipeline
Replace the basic RNN noise processor with a more advanced audio pipeline powered by Big Blue Better Audio (BBBA), a project supported by the Prototype Fund. The new WASM-based pipeline introduced by @falkTX and @trummerschlunk adds: - voice isolation - high-pass filtering - spectral balancing - multiband compression - low-pass filtering This significantly improves overall audio quality and speech clarity. More information about the pipeline is available on the trummerschlunk/BigBlueBetterAudio repo. Voice isolation still relies on RNNNoise, a widely used deep learning-based denoising model. Audio quality could be further improved in the future if DeepFilterNet becomes usable directly in the browser. Their code has been released in an NPM package under GPL license.
This commit is contained in:
@@ -8,6 +8,10 @@ and this project adheres to
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Changed
|
||||
|
||||
- ✨(frontend) enhance noise reduction with BBBA audio processing pipeline
|
||||
|
||||
## [1.20.0] - 2026-06-12
|
||||
|
||||
### Changed
|
||||
|
||||
Generated
+7
@@ -11,6 +11,7 @@
|
||||
"@fontsource-variable/atkinson-hyperlegible-next": "5.2.6",
|
||||
"@fontsource-variable/lexend": "5.2.11",
|
||||
"@fontsource/opendyslexic": "5.2.5",
|
||||
"@libreaudio/la-call": "0.1.4",
|
||||
"@livekit/components-react": "2.9.21",
|
||||
"@livekit/components-styles": "1.2.0",
|
||||
"@livekit/track-processors": "0.7.2",
|
||||
@@ -1446,6 +1447,12 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@libreaudio/la-call": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@libreaudio/la-call/-/la-call-0.1.4.tgz",
|
||||
"integrity": "sha512-yaqlYGpvZbfpr1h2p1jSGOKPUy7NsfpoJN9gXvHmJu5MB8nN/qfx6LQ2a0hbua3H8rargFKROOv4VZ5WuTKNsA==",
|
||||
"license": "GPL-3.0+"
|
||||
},
|
||||
"node_modules/@livekit/components-core": {
|
||||
"version": "0.12.13",
|
||||
"resolved": "https://registry.npmjs.org/@livekit/components-core/-/components-core-0.12.13.tgz",
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"dependencies": {
|
||||
"@fontsource-variable/atkinson-hyperlegible-next": "5.2.6",
|
||||
"@fontsource-variable/lexend": "5.2.11",
|
||||
"@libreaudio/la-call": "0.1.4",
|
||||
"@fontsource/opendyslexic": "5.2.5",
|
||||
"@livekit/components-react": "2.9.21",
|
||||
"@livekit/components-styles": "1.2.0",
|
||||
|
||||
@@ -216,6 +216,14 @@ export const Join = ({
|
||||
try {
|
||||
const track = await createLocalAudioTrack({
|
||||
deviceId: { exact: audioDeviceId },
|
||||
noiseSuppression: true,
|
||||
echoCancellation: true,
|
||||
autoGainControl: true,
|
||||
voiceIsolation: false,
|
||||
// Audio quality optimized for voice
|
||||
sampleRate: 48000, // High quality sample rate
|
||||
channelCount: 1, // Mono for voice calls (saves bandwidth)
|
||||
sampleSize: 16, // 16-bit audio
|
||||
})
|
||||
setDynamicAudioTrack(track)
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import type { Track, TrackProcessor, ProcessorOptions } from 'livekit-client'
|
||||
import { NoiseSuppressorWorklet_Name } from '@timephy/rnnoise-wasm'
|
||||
|
||||
// This is an example how to get the script path using Vite, may be different when using other build tools
|
||||
// NOTE: `?worker&url` is important (`worker` to generate a working script, `url` to get its url to load it)
|
||||
import NoiseSuppressorWorklet from '@timephy/rnnoise-wasm/NoiseSuppressorWorklet?worker&url'
|
||||
import type { Track, TrackProcessor, ProcessorOptions } from 'livekit-client'
|
||||
import { createWasmProcessor } from '@libreaudio/la-call'
|
||||
|
||||
// Use Jitsi's approach: maintain a global AudioContext variable
|
||||
// and suspend/resume it as needed to manage audio state
|
||||
@@ -20,7 +17,7 @@ export class RnnNoiseProcessor implements AudioProcessorInterface {
|
||||
private source?: MediaStreamTrack
|
||||
private sourceNode?: MediaStreamAudioSourceNode
|
||||
private destinationNode?: MediaStreamAudioDestinationNode
|
||||
private noiseSuppressionNode?: AudioWorkletNode
|
||||
private noiseSuppressionNode?: AudioNode
|
||||
|
||||
async init(opts: ProcessorOptions<Track.Kind.Audio>) {
|
||||
if (!opts.track) {
|
||||
@@ -35,25 +32,16 @@ export class RnnNoiseProcessor implements AudioProcessorInterface {
|
||||
await audioContext.resume()
|
||||
}
|
||||
|
||||
await audioContext.audioWorklet.addModule(NoiseSuppressorWorklet)
|
||||
|
||||
this.sourceNode = audioContext.createMediaStreamSource(
|
||||
new MediaStream([this.source])
|
||||
)
|
||||
|
||||
this.noiseSuppressionNode = new AudioWorkletNode(
|
||||
audioContext,
|
||||
NoiseSuppressorWorklet_Name,
|
||||
{
|
||||
// RNNoise is a mono algorithm. Its worklet only denoises and writes
|
||||
// channel 0. Force any (possibly stereo) input to down-mix to a single
|
||||
// channel and emit a single channel, so we don't produce a stereo frame
|
||||
// whose right channel is left silent.
|
||||
channelCount: 1,
|
||||
channelCountMode: 'explicit',
|
||||
outputChannelCount: [1],
|
||||
}
|
||||
)
|
||||
this.noiseSuppressionNode = await createWasmProcessor(audioContext, {
|
||||
intensity: 90,
|
||||
})
|
||||
if (!this.noiseSuppressionNode) {
|
||||
throw new Error('Failed to create Wasm processor')
|
||||
}
|
||||
|
||||
this.destinationNode = audioContext.createMediaStreamDestination()
|
||||
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
"disabled": "Microphone disabled"
|
||||
},
|
||||
"noiseReduction": {
|
||||
"label": "Noise reduction",
|
||||
"heading": "Noise reduction",
|
||||
"label": "Advanced voice optimization",
|
||||
"heading": "Audio filters",
|
||||
"ariaLabel": {
|
||||
"enable": "Enable noise reduction",
|
||||
"disable": "Disable noise reduction"
|
||||
|
||||
Reference in New Issue
Block a user