@@ -18,20 +18,30 @@ use windows::Win32::System::Com::{
1818} ;
1919
2020/// Windows WASAPI audio capture implementation
21+ ///
22+ /// Captures system audio output using WASAPI loopback mode.
23+ /// The audio format (sample rate, channels, bit depth) is auto-detected
24+ /// from the system's default audio device during `start_capture()`.
25+ ///
26+ /// Typical Windows audio format: 48000 Hz, 2 channels, 32-bit float
2127pub struct WasapiAudioCapture {
2228 is_capturing : Arc < Mutex < bool > > ,
2329 audio_buffer : Arc < Mutex < Vec < f32 > > > ,
30+ /// Audio format - placeholder until capture starts, then auto-detected
2431 format : AudioFormat ,
2532 capture_handle : Option < tokio:: task:: JoinHandle < ( ) > > ,
2633}
2734
2835impl WasapiAudioCapture {
2936 /// Creates a new WASAPI audio capture instance
37+ ///
38+ /// The format field is initialized to a default placeholder.
39+ /// Actual format is detected when `start_capture()` is called.
3040 pub fn new ( ) -> Self {
3141 Self {
3242 is_capturing : Arc :: new ( Mutex :: new ( false ) ) ,
3343 audio_buffer : Arc :: new ( Mutex :: new ( Vec :: new ( ) ) ) ,
34- format : AudioFormat :: default ( ) ,
44+ format : AudioFormat :: default ( ) , // Placeholder, updated during start_capture()
3545 capture_handle : None ,
3646 }
3747 }
@@ -61,9 +71,15 @@ impl WasapiAudioCapture {
6171 }
6272
6373 /// Initialize the audio client with the desired format
64- fn initialize_audio_client ( audio_client : & IAudioClient ) -> Result < ( WAVEFORMATEX , u16 , u16 ) > {
74+ ///
75+ /// Queries the WASAPI device for its mix format and initializes the audio client
76+ /// for loopback capture. Returns the detected format parameters which are used
77+ /// to update the WasapiAudioCapture.format field.
78+ ///
79+ /// Returns: (WAVEFORMATEX, sample_rate, bits_per_sample)
80+ fn initialize_audio_client ( audio_client : & IAudioClient ) -> Result < ( WAVEFORMATEX , u32 , u16 ) > {
6581 unsafe {
66- // Get the device's mix format
82+ // Get the device's mix format (auto-detected from system)
6783 let mix_format_ptr = audio_client
6884 . GetMixFormat ( )
6985 . map_err ( |e| AppError :: AudioCapture ( format ! ( "Failed to get mix format: {}" , e) ) ) ?;
@@ -73,8 +89,8 @@ impl WasapiAudioCapture {
7389 }
7490
7591 let mix_format = * mix_format_ptr;
76- let sample_rate = mix_format. nSamplesPerSec as u16 ;
77- let bits_per_sample = mix_format. wBitsPerSample ;
92+ let sample_rate = mix_format. nSamplesPerSec ; // Actual system sample rate
93+ let bits_per_sample = mix_format. wBitsPerSample ; // Actual bit depth
7894
7995 // Initialize the audio client for loopback capture
8096 let buffer_duration = 10_000_000 ; // 1 second in 100-nanosecond units
@@ -236,19 +252,24 @@ impl AudioCapturePort for WasapiAudioCapture {
236252 }
237253
238254 async fn start_capture ( & mut self , _device_name : Option < String > ) -> Result < ( ) > {
239- let mut is_capturing = self . is_capturing . lock ( ) . unwrap ( ) ;
240- if * is_capturing {
241- return Err ( AppError :: AudioCapture (
242- "Capture already in progress" . to_string ( ) ,
243- ) ) ;
244- }
255+ {
256+ let mut is_capturing = self . is_capturing . lock ( ) . unwrap ( ) ;
257+ if * is_capturing {
258+ return Err ( AppError :: AudioCapture (
259+ "Capture already in progress" . to_string ( ) ,
260+ ) ) ;
261+ }
245262
246- * is_capturing = true ;
247- drop ( is_capturing) ;
263+ * is_capturing = true ;
264+ } // Drop is_capturing guard here
248265
249266 let is_capturing_clone = Arc :: clone ( & self . is_capturing ) ;
250267 let audio_buffer_clone = Arc :: clone ( & self . audio_buffer ) ;
251268
269+ // Store format info to be updated after detection
270+ let format_info = Arc :: new ( Mutex :: new ( AudioFormat :: default ( ) ) ) ;
271+ let format_info_clone = Arc :: clone ( & format_info) ;
272+
252273 // Spawn background task for audio capture
253274 let handle = tokio:: task:: spawn_blocking ( move || {
254275 // Initialize COM for this thread
@@ -282,7 +303,8 @@ impl AudioCapturePort for WasapiAudioCapture {
282303 }
283304 } ;
284305
285- // Initialize the audio client
306+ // Initialize the audio client and get the actual device format
307+ // This is where the format is detected from the WASAPI device
286308 let ( format, sample_rate, bits_per_sample) = match Self :: initialize_audio_client ( & audio_client) {
287309 Ok ( f) => f,
288310 Err ( e) => {
@@ -293,6 +315,15 @@ impl AudioCapturePort for WasapiAudioCapture {
293315 }
294316 } ;
295317
318+ // IMPORTANT: Update format with actual detected values from the device
319+ // This replaces the default placeholder values with the real audio format
320+ let channels = format. nChannels ;
321+ * format_info_clone. lock ( ) . unwrap ( ) = AudioFormat {
322+ sample_rate, // e.g., 48000 Hz (detected from device)
323+ channels, // e.g., 2 (stereo, detected from device)
324+ bits_per_sample, // e.g., 32 bits (float, detected from device)
325+ } ;
326+
296327 // Get the capture client
297328 let capture_client: IAudioCaptureClient = match unsafe {
298329 audio_client. GetService :: < IAudioCaptureClient > ( )
@@ -307,7 +338,7 @@ impl AudioCapturePort for WasapiAudioCapture {
307338 } ;
308339
309340 log:: info!( "WASAPI audio capture initialized successfully" ) ;
310- log:: info!( "Format: {} Hz, {} bits" , sample_rate, bits_per_sample) ;
341+ log:: info!( "Format: {} Hz, {} channels, {} bits" , sample_rate, channels , bits_per_sample) ;
311342
312343 // Run the capture loop
313344 Self :: capture_loop (
@@ -324,7 +355,17 @@ impl AudioCapturePort for WasapiAudioCapture {
324355 } ) ;
325356
326357 self . capture_handle = Some ( handle) ;
327- log:: info!( "Audio capture started" ) ;
358+
359+ // Wait for format detection to complete
360+ // The background thread detects the system's audio format and stores it in format_info
361+ // Typical Windows audio: 48000 Hz, stereo, 32-bit float
362+ tokio:: time:: sleep ( tokio:: time:: Duration :: from_millis ( 100 ) ) . await ;
363+
364+ // Update our format from the auto-detected format
365+ self . format = format_info. lock ( ) . unwrap ( ) . clone ( ) ;
366+
367+ log:: info!( "Audio capture started with format: {} Hz, {} channels, {} bits" ,
368+ self . format. sample_rate, self . format. channels, self . format. bits_per_sample) ;
328369 Ok ( ( ) )
329370 }
330371
@@ -384,9 +425,12 @@ mod tests {
384425 fn test_default_format ( ) {
385426 let capture = WasapiAudioCapture :: new ( ) ;
386427 let format = capture. get_format ( ) ;
387- assert_eq ! ( format. sample_rate, 16000 ) ;
388- assert_eq ! ( format. channels, 1 ) ;
389- assert_eq ! ( format. bits_per_sample, 16 ) ;
428+ // Before capture starts, format is the default placeholder
429+ // Actual format is detected during start_capture() and varies by system
430+ // Typical Windows audio: 48000 Hz, 2 channels, 32 bits (float)
431+ assert_eq ! ( format. sample_rate, 16000 ) ; // Placeholder before capture
432+ assert_eq ! ( format. channels, 1 ) ; // Placeholder before capture
433+ assert_eq ! ( format. bits_per_sample, 16 ) ; // Placeholder before capture
390434 }
391435
392436 #[ tokio:: test]
0 commit comments