Skip to content

Commit c2aa114

Browse files
committed
[+] feat: support real-time noise reduction
1 parent abf0dae commit c2aa114

File tree

10 files changed

+136
-34
lines changed

10 files changed

+136
-34
lines changed

lib/recorder/examples/input_recording_demo.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
55
println!("Input Audio Recording Demo");
66
println!("==========================");
77

8-
let recorder = AudioRecorder::new(None)?;
8+
let recorder = AudioRecorder::new(None)?.with_real_time_denoise(true);
99

1010
println!("\nAvailable Input Devices:");
1111
println!("------------------------");

lib/recorder/src/record_audio.rs

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
use crate::{apply_gain, calc_rms_level};
1+
use crate::{RealTimeDenoise, apply_gain, calc_rms_level, denoise_model};
22
use cpal::{
33
Device, Host, InputCallbackInfo, SampleFormat, Stream, StreamConfig,
44
traits::{DeviceTrait, HostTrait, StreamTrait},
55
};
66
use crossbeam::channel::{Receiver, Sender, bounded};
77
use hound::{WavSpec, WavWriter};
8+
use nnnoiseless::RnnModel;
9+
use once_cell::sync::Lazy;
810
use std::{
911
fs::File,
1012
io::BufWriter,
@@ -16,6 +18,8 @@ use std::{
1618
};
1719
use thiserror::Error;
1820

21+
static DENOISE_MODEL: Lazy<RnnModel> = Lazy::new(|| denoise_model());
22+
1923
/// Audio recording error types.
2024
///
2125
/// This enum represents all possible errors that can occur during
@@ -54,6 +58,9 @@ pub enum AudioError {
5458
/// Audio sample encoding or format conversion failed
5559
#[error("Audio encoding error: {0}")]
5660
EncodingError(String),
61+
/// Audio sample denoise failed
62+
#[error("Audio denoise error: {0}")]
63+
DenoiseError(String),
5764
}
5865

5966
/// Information about an audio device for recording.
@@ -135,6 +142,8 @@ pub struct AudioRecorder {
135142

136143
// [0, infinity]
137144
amplification: Option<Arc<AtomicI32>>,
145+
146+
real_time_denoise: bool,
138147
}
139148

140149
impl AudioRecorder {
@@ -176,6 +185,7 @@ impl AudioRecorder {
176185
audio_level_sender,
177186
audio_level_receiver,
178187
amplification: None,
188+
real_time_denoise: false,
179189
})
180190
}
181191

@@ -184,6 +194,11 @@ impl AudioRecorder {
184194
self
185195
}
186196

197+
pub fn with_real_time_denoise(mut self, v: bool) -> Self {
198+
self.real_time_denoise = v;
199+
self
200+
}
201+
187202
/// Get the audio level receiver for real-time monitoring.
188203
///
189204
/// This method returns the receiver end of the audio level channel,
@@ -407,12 +422,48 @@ impl StreamingAudioRecorder {
407422
)?)))
408423
};
409424

425+
// Note:
426+
// Without calling `denoise.flush` is not a problem.
427+
// Just losing the last frame of real-time samples.
428+
let mut denoiser = if recorder.real_time_denoise && !disable_save_file {
429+
let spec = WavSpec {
430+
channels: stream_config.channels,
431+
sample_rate: stream_config.sample_rate.0,
432+
bits_per_sample: 32,
433+
sample_format: hound::SampleFormat::Float,
434+
};
435+
436+
Some(
437+
RealTimeDenoise::new(&DENOISE_MODEL, spec)
438+
.map_err(|e| AudioError::DenoiseError(e.to_string()))?,
439+
)
440+
} else {
441+
None
442+
};
443+
410444
let file_writer_clone = file_writer.clone();
411445
let audio_level_sender = recorder.audio_level_sender.clone();
412446
let amplification = recorder.amplification.clone();
447+
413448
let recording_session = recorder.start_input_recording(
414449
device_name,
415450
move |f32_samples: &[f32], _info: &_| {
451+
let mut denoise_samples = None;
452+
let f32_samples = if let Some(ref mut denoiser) = denoiser {
453+
match denoiser.process(f32_samples) {
454+
Ok(v) => denoise_samples = v,
455+
Err(e) => log::warn!("denoise audio samples failed: {e}"),
456+
};
457+
458+
if denoise_samples.is_some() {
459+
&denoise_samples.unwrap()
460+
} else {
461+
f32_samples
462+
}
463+
} else {
464+
f32_samples
465+
};
466+
416467
let mut f32_sample_amplification = Vec::with_capacity(f32_samples.len());
417468
let data = if let Some(ref amplification) = amplification {
418469
f32_sample_amplification.extend_from_slice(f32_samples);

lib/recorder/src/recorder.rs

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -428,7 +428,9 @@ impl RecordingSession {
428428
.map_err(|e: AudioError| RecorderError::AudioError(e.to_string()))?;
429429

430430
let audio_recorder = if self.config.audio_amplification.is_some() {
431-
audio_recorder.with_amplification(self.config.audio_amplification.clone().unwrap())
431+
audio_recorder
432+
.with_amplification(self.config.audio_amplification.clone().unwrap())
433+
.with_real_time_denoise(self.config.enable_denoise && self.config.real_time_denoise)
432434
} else {
433435
audio_recorder
434436
};
@@ -825,7 +827,10 @@ impl RecordingSession {
825827
}
826828

827829
if !self.config.disable_save_file {
828-
if self.config.enable_denoise && self.config.audio_device_name.is_some() {
830+
if self.config.enable_denoise
831+
&& !self.config.real_time_denoise
832+
&& self.config.audio_device_name.is_some()
833+
{
829834
let input_file = self
830835
.config
831836
.output_path
@@ -855,15 +860,17 @@ impl RecordingSession {
855860
let combine_config = MergeTracksConfig {
856861
h264_path: self.config.output_path.with_extension(RAW_VIDEO_EXTENSION),
857862
input_wav_path: if self.config.audio_device_name.is_some() {
858-
Some(if self.config.enable_denoise {
859-
self.config
860-
.output_path
861-
.with_extension(INPUT_AUDIO_DENOISE_EXTENSION)
862-
} else {
863-
self.config
864-
.output_path
865-
.with_extension(INPUT_AUDIO_EXTENSION)
866-
})
863+
Some(
864+
if self.config.enable_denoise && !self.config.real_time_denoise {
865+
self.config
866+
.output_path
867+
.with_extension(INPUT_AUDIO_DENOISE_EXTENSION)
868+
} else {
869+
self.config
870+
.output_path
871+
.with_extension(INPUT_AUDIO_EXTENSION)
872+
},
873+
)
867874
} else {
868875
None
869876
},
@@ -918,7 +925,7 @@ impl RecordingSession {
918925
.with_extension(INPUT_AUDIO_EXTENSION),
919926
);
920927

921-
if self.config.enable_denoise {
928+
if self.config.enable_denoise && !self.config.real_time_denoise {
922929
_ = fs::remove_file(
923930
self.config
924931
.output_path

lib/recorder/src/recorder_config.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,8 @@ pub struct RecorderConfig {
135135

136136
pub enable_denoise: bool,
137137

138+
pub real_time_denoise: bool,
139+
138140
pub disable_save_file: bool,
139141

140142
#[setters(strip_option)]
@@ -197,6 +199,7 @@ impl RecorderConfig {
197199
enable_speaker_channel_user: false,
198200
enable_preview_mode: false,
199201
enable_denoise: false,
202+
real_time_denoise: true,
200203
disable_save_file: false,
201204
audio_amplification: None,
202205
speaker_amplification: None,

tr-helper/src/tr.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -239,8 +239,10 @@ fn cn() -> &'static HashMap<&'static str, &'static str> {
239239
("Welcome to use wayshot", "欢迎使用Wayshot"),
240240
("screen recording tool for wayland", "Wayland录屏工具"),
241241
("Finished Mergeing Tracks", "完成合并轨道"),
242-
("Disabled microphone denoise", "禁用麦克风降噪"),
243-
("Enabled microphone denoise", "启用麦克风降噪"),
242+
("Disabled microphone noise reduction", "已禁用麦克风降噪"),
243+
("Enabled microphone noise reduction", "已启用麦克风降噪"),
244+
("Post-processing noise reduction", "后处理降噪"),
245+
("Real-time noise reduction", "实时降噪"),
244246
("create desktop speaker recorder failed", "创建桌面扬声器录制器失败"),
245247
("start desktop speaker recorder failed", "启动桌面扬声器录制器失败"),
246248
("Convert the microphone audio to mono", "将麦克风音频转换为单声道"),

wayshot/src/config.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,9 @@ pub struct Recorder {
130130

131131
pub enable_denoise: bool,
132132

133+
#[derivative(Default(value = "true"))]
134+
pub real_time_denoise: bool,
135+
133136
pub convert_input_wav_to_mono: bool,
134137

135138
#[derivative(Default(value = "fps_default()"))]

wayshot/src/logic/recorder.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -578,6 +578,7 @@ fn inner_start_recording(ui_weak: Weak<AppWindow>) -> Result<()> {
578578
.with_enable_frame_channel_user(true)
579579
.with_enable_preview_mode(all_config.recorder.enable_preview)
580580
.with_enable_denoise(all_config.recorder.enable_denoise)
581+
.with_real_time_denoise(all_config.recorder.real_time_denoise)
581582
.with_convert_input_wav_to_mono(all_config.recorder.convert_input_wav_to_mono)
582583
.with_enable_recording_speaker(all_config.control.enable_desktop_speaker)
583584
.with_include_cursor(all_config.recorder.include_cursor)
@@ -675,8 +676,10 @@ fn inner_start_recording(ui_weak: Weak<AppWindow>) -> Result<()> {
675676

676677
_ = ui_weak.upgrade_in_event_loop(move |ui| {
677678
global_store!(ui).set_record_status(UIRecordStatus::Stopped);
679+
let all_config = config::all();
678680

679-
if config::all().recorder.enable_denoise
681+
if all_config.recorder.enable_denoise
682+
&& !all_config.recorder.real_time_denoise
680683
&& global_store!(ui).get_denoise_status() != UIDenoiseStatus::Cancelled
681684
{
682685
global_store!(ui).set_denoise_status(UIDenoiseStatus::Finished);
@@ -703,7 +706,8 @@ fn stop_recording(ui: &AppWindow) {
703706
log::warn!("recorder_stop_sig is None");
704707
}
705708

706-
if config::all().recorder.enable_denoise {
709+
let all_config = config::all();
710+
if all_config.recorder.enable_denoise && !all_config.recorder.real_time_denoise {
707711
global_store!(ui).set_record_status(UIRecordStatus::Denoising);
708712
} else {
709713
global_store!(ui).set_record_status(UIRecordStatus::Mergeing);

wayshot/src/logic/tr.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -241,8 +241,10 @@ fn cn() -> &'static HashMap<&'static str, &'static str> {
241241
("Welcome to use wayshot", "欢迎使用Wayshot"),
242242
("screen recording tool for wayland", "Wayland录屏工具"),
243243
("Finished Mergeing Tracks", "完成合并轨道"),
244-
("Disabled microphone denoise", "禁用麦克风降噪"),
245-
("Enabled microphone denoise", "启用麦克风降噪"),
244+
("Disabled microphone noise reduction", "已禁用麦克风降噪"),
245+
("Enabled microphone noise reduction", "已启用麦克风降噪"),
246+
("Post-processing noise reduction", "后处理降噪"),
247+
("Real-time noise reduction", "实时降噪"),
246248
("create desktop speaker recorder failed", "创建桌面扬声器录制器失败"),
247249
("start desktop speaker recorder failed", "启动桌面扬声器录制器失败"),
248250
("Convert the microphone audio to mono", "将麦克风音频转换为单声道"),

wayshot/ui/panel/setting/components/recorder.slint

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Store, Logic, Theme, DeviceType, Icons, SettingRecorder } from "../../def.slint";
22
import { Fps, Resolution } from "../../../store.slint";
3-
import { SettingDetail, SettingDetailInner, SettingDetailInnerVbox, SettingDetailLabel, LineInput, ConfirmBtn, Label, SettingDetailSwitch, Select } from "../../../base/widgets.slint";
3+
import { SettingDetail, SettingDetailInner, RadioBtn, SettingDetailInnerVbox, SettingDetailLabel, LineInput, ConfirmBtn, Label, SettingDetailSwitch, Select } from "../../../base/widgets.slint";
44

55
export component Recorder inherits SettingDetail {
66
callback confirmed();
@@ -10,6 +10,7 @@ export component Recorder inherits SettingDetail {
1010
private property <bool> include-cursor;
1111
private property <bool> enable-preview;
1212
private property <bool> enable-denoise;
13+
private property <bool> real-time-denoise;
1314
private property <bool> convert-input-wav-to-mono;
1415
private property <Fps> fps;
1516
private property <Resolution> resolution;
@@ -25,6 +26,7 @@ export component Recorder inherits SettingDetail {
2526
include-cursor: root.include-cursor,
2627
enable-preview: root.enable-preview,
2728
enable-denoise: root.enable-denoise,
29+
real-time-denoise: root.real-time-denoise,
2830
convert-input-wav-to-mono: root.convert-input-wav-to-mono,
2931
fps: root.fps,
3032
resolution: root.resolution,
@@ -37,6 +39,7 @@ export component Recorder inherits SettingDetail {
3739
root.include-cursor = setting.include-cursor;
3840
root.enable-preview = setting.enable-preview;
3941
root.enable-denoise = setting.enable-denoise;
42+
root.real-time-denoise = setting.real-time-denoise;
4043
root.convert-input-wav-to-mono = setting.convert-input-wav-to-mono;
4144
root.fps = setting.fps;
4245
root.resolution = setting.resolution;
@@ -102,19 +105,6 @@ export component Recorder inherits SettingDetail {
102105
}
103106
}
104107

105-
SettingDetailInnerVbox {
106-
SettingDetailSwitch {
107-
icon: Icons.denoise-light;
108-
text: self.checked ? Logic.tr("Enabled microphone denoise") : Logic.tr("Disabled microphone denoise");
109-
checked: root.enable-denoise;
110-
111-
toggled => {
112-
root.enable-denoise = self.checked;
113-
Logic.set-setting-recorder(root.get());
114-
}
115-
}
116-
}
117-
118108
SettingDetailInnerVbox {
119109
include-cursor-swicth := SettingDetailSwitch {
120110
icon: Icons.cursor-light;
@@ -154,5 +144,44 @@ export component Recorder inherits SettingDetail {
154144
}
155145
}
156146
}
147+
148+
SettingDetailInnerVbox {
149+
spacing: Theme.spacing * 2;
150+
151+
SettingDetailSwitch {
152+
icon: Icons.denoise-light;
153+
text: self.checked ? Logic.tr("Enabled microphone noise reduction") : Logic.tr("Disabled microphone noise reduction");
154+
checked: root.enable-denoise;
155+
156+
toggled => {
157+
root.enable-denoise = self.checked;
158+
Logic.set-setting-recorder(root.get());
159+
}
160+
}
161+
162+
if root.enable-denoise: HorizontalLayout {
163+
HorizontalLayout {
164+
width: 50%;
165+
166+
RadioBtn {
167+
text: Logic.tr("Real-time noise reduction");
168+
checked: root.real-time-denoise;
169+
check => {
170+
root.real-time-denoise = true;
171+
Logic.set-setting-recorder(root.get());
172+
}
173+
}
174+
}
175+
176+
RadioBtn {
177+
text: Logic.tr("Post-processing noise reduction");
178+
checked: !root.real-time-denoise;
179+
check => {
180+
root.real-time-denoise = false;
181+
Logic.set-setting-recorder(root.get());
182+
}
183+
}
184+
}
185+
}
157186
}
158187
}

wayshot/ui/store.slint

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ export struct SettingRecorder {
7979
include-cursor: bool,
8080
enable-preview: bool,
8181
enable-denoise: bool,
82+
real-time-denoise: bool,
8283
convert-input-wav-to-mono: bool,
8384
fps: Fps,
8485
resolution: Resolution,

0 commit comments

Comments
 (0)