1
1
use cpal:: traits:: { DeviceTrait , HostTrait , StreamTrait } ;
2
- use std:: sync:: { Arc , Mutex } ;
3
- use std:: sync:: atomic:: { AtomicBool , Ordering } ;
4
2
use once_cell:: sync:: OnceCell ;
5
- use std:: sync:: mpsc;
6
3
use rubato:: {
7
- SincInterpolationParameters , SincInterpolationType , Resampler ,
8
- SincFixedIn , WindowFunction
4
+ Resampler , SincFixedIn , SincInterpolationParameters , SincInterpolationType , WindowFunction ,
9
5
} ;
6
+ use std:: sync:: atomic:: { AtomicBool , Ordering } ;
7
+ use std:: sync:: mpsc;
8
+ use std:: sync:: { Arc , Mutex } ;
10
9
11
10
static RECORDER : OnceCell < AudioRecorder > = OnceCell :: new ( ) ;
12
11
13
12
// Standard sample rate for voice recording with good quality-to-size ratio
14
- const TARGET_SAMPLE_RATE : u32 = 16000 ;
13
+ const TARGET_SAMPLE_RATE : u32 = 22000 ;
15
14
16
15
pub struct AudioRecorder {
17
16
recording : Arc < AtomicBool > ,
@@ -43,34 +42,42 @@ impl AudioRecorder {
43
42
* self . stop_tx . lock ( ) . unwrap ( ) = Some ( tx) ;
44
43
45
44
let host = cpal:: default_host ( ) ;
46
- let device = host. default_input_device ( )
47
- . ok_or ( "No input device found" ) ?;
45
+ let device = host. default_input_device ( ) . ok_or ( "No input device found" ) ?;
46
+
47
+ let supported_config = device. default_input_config ( ) . map_err ( |e| e. to_string ( ) ) ?;
48
+
49
+ * self . device_sample_rate . lock ( ) . unwrap ( ) = supported_config. sample_rate ( ) . 0 ;
50
+
51
+ let config: cpal:: StreamConfig = supported_config. into ( ) ;
52
+ let channels = config. channels as usize ;
48
53
49
- let config = device. default_input_config ( )
50
- . map_err ( |e| e. to_string ( ) ) ?;
51
-
52
- * self . device_sample_rate . lock ( ) . unwrap ( ) = config. sample_rate ( ) . 0 ;
53
54
let samples = Arc :: clone ( & self . samples ) ;
54
55
let recording = Arc :: clone ( & self . recording ) ;
55
-
56
+
56
57
self . recording . store ( true , Ordering :: SeqCst ) ;
57
-
58
+
58
59
std:: thread:: spawn ( move || {
59
- let stream = device. build_input_stream (
60
- & config. into ( ) ,
61
- move |data : & [ f32 ] , _: & _ | {
62
- if recording. load ( Ordering :: SeqCst ) {
63
- if let Ok ( mut guard) = samples. lock ( ) {
64
- guard. extend ( data. iter ( ) . map ( |& x| ( x * 32768.0 ) as i16 ) ) ;
60
+ let stream = device
61
+ . build_input_stream (
62
+ & config,
63
+ move |data : & [ f32 ] , _: & _ | {
64
+ if recording. load ( Ordering :: SeqCst ) {
65
+ if let Ok ( mut guard) = samples. lock ( ) {
66
+ guard. extend ( data. chunks ( channels) . map ( |chunk| {
67
+ let sum: f32 = chunk. iter ( ) . sum ( ) ;
68
+ let avg = sum / channels as f32 ;
69
+ ( avg. clamp ( -1.0 , 1.0 ) * 32767.0 ) as i16
70
+ } ) ) ;
71
+ }
65
72
}
66
- }
67
- } ,
68
- |err| eprintln ! ( "Error: {}" , err ) ,
69
- None
70
- ) . unwrap ( ) ;
73
+ } ,
74
+ |err| eprintln ! ( "Error: {}" , err ) ,
75
+ None ,
76
+ )
77
+ . unwrap ( ) ;
71
78
72
79
stream. play ( ) . unwrap ( ) ;
73
-
80
+
74
81
// Wait for stop signal
75
82
rx. recv ( ) . unwrap_or ( ( ) ) ;
76
83
} ) ;
@@ -86,32 +93,29 @@ impl AudioRecorder {
86
93
self . recording . store ( false , Ordering :: SeqCst ) ;
87
94
88
95
let wav_buffer = {
89
- let samples = self . samples . lock ( )
90
- . map_err ( |_| "Failed to get samples" ) ?;
96
+ let samples = self . samples . lock ( ) . map_err ( |_| "Failed to get samples" ) ?;
91
97
92
98
if samples. is_empty ( ) {
93
99
return Err ( "No audio data recorded" . to_string ( ) ) ;
94
100
}
95
101
96
102
let device_sample_rate = * self . device_sample_rate . lock ( ) . unwrap ( ) ;
97
-
103
+
98
104
// Resample audio to target sample rate to ensure consistent quality
99
105
let resampled_samples = self . resample_audio ( & samples, device_sample_rate) ?;
100
-
106
+
101
107
let spec = hound:: WavSpec {
102
108
channels : 1 ,
103
109
sample_rate : TARGET_SAMPLE_RATE ,
104
110
bits_per_sample : 16 ,
105
111
sample_format : hound:: SampleFormat :: Int ,
106
112
} ;
107
-
113
+
108
114
let mut buffer: Vec < u8 > = Vec :: new ( ) ;
109
115
{
110
- let mut writer = hound:: WavWriter :: new (
111
- std:: io:: Cursor :: new ( & mut buffer) ,
112
- spec
113
- ) . map_err ( |e| e. to_string ( ) ) ?;
114
-
116
+ let mut writer = hound:: WavWriter :: new ( std:: io:: Cursor :: new ( & mut buffer) , spec)
117
+ . map_err ( |e| e. to_string ( ) ) ?;
118
+
115
119
for & sample in resampled_samples. iter ( ) {
116
120
writer. write_sample ( sample) . map_err ( |e| e. to_string ( ) ) ?;
117
121
}
@@ -124,22 +128,20 @@ impl AudioRecorder {
124
128
125
129
Ok ( wav_buffer)
126
130
}
127
-
131
+
128
132
/// Resample audio using Rubato's high-quality resampling
129
133
fn resample_audio ( & self , samples : & [ i16 ] , source_rate : u32 ) -> Result < Vec < i16 > , String > {
130
134
// If sample rates are already the same, return the original samples
131
135
if source_rate == TARGET_SAMPLE_RATE {
132
136
return Ok ( samples. to_vec ( ) ) ;
133
137
}
134
-
138
+
135
139
// Convert i16 samples to f32 for Rubato
136
- let samples_f32: Vec < f32 > = samples. iter ( )
137
- . map ( |& s| ( s as f32 ) / 32768.0 )
138
- . collect ( ) ;
139
-
140
+ let samples_f32: Vec < f32 > = samples. iter ( ) . map ( |& s| ( s as f32 ) / 32768.0 ) . collect ( ) ;
141
+
140
142
// Since Rubato works with separate channels, wrap our mono audio in a Vec of Vecs
141
143
let input_frames = vec ! [ samples_f32] ;
142
-
144
+
143
145
// Create a Sinc resampler with good quality settings for voice
144
146
let params = SincInterpolationParameters {
145
147
sinc_len : 256 ,
@@ -148,27 +150,27 @@ impl AudioRecorder {
148
150
oversampling_factor : 256 ,
149
151
window : WindowFunction :: BlackmanHarris2 ,
150
152
} ;
151
-
153
+
152
154
let mut resampler = SincFixedIn :: < f32 > :: new (
153
155
TARGET_SAMPLE_RATE as f64 / source_rate as f64 ,
154
156
1.0 ,
155
157
params,
156
158
samples. len ( ) ,
157
159
1 , // mono audio (1 channel)
158
- ) . map_err ( |e| format ! ( "Failed to create resampler: {}" , e) ) ?;
159
-
160
+ )
161
+ . map_err ( |e| format ! ( "Failed to create resampler: {}" , e) ) ?;
162
+
160
163
// Process the audio
161
- let output_frames = resampler. process (
162
- & input_frames,
163
- None
164
- ) . map_err ( |e| format ! ( "Failed to resample audio: {}" , e) ) ?;
165
-
164
+ let output_frames = resampler
165
+ . process ( & input_frames, None )
166
+ . map_err ( |e| format ! ( "Failed to resample audio: {}" , e) ) ?;
167
+
166
168
// Convert back to i16 from f32 (first channel only since we're using mono)
167
169
let resampled_samples = output_frames[ 0 ]
168
170
. iter ( )
169
171
. map ( |& s| ( s * 32767.0 ) as i16 )
170
172
. collect ( ) ;
171
-
173
+
172
174
Ok ( resampled_samples)
173
175
}
174
176
}
0 commit comments