@@ -27,6 +27,25 @@ pub struct NativeAudioSource {
2727}
2828
2929impl NativeAudioSource {
30+ /// Creates a new [`NativeAudioSource`].
31+ ///
32+ /// # Arguments
33+ /// * `options` – Configuration options for the source (e.g. echo cancellation, noise suppression).
34+ /// * `sample_rate` – Sampling rate in Hz (for example, `48000`).
35+ /// * `num_channels` – Number of audio channels (`1` for mono, `2` for stereo, etc.).
36+ /// * `queue_size_ms` – Size of the internal buffering queue, in milliseconds.
37+ ///
38+ /// # Behavior
39+ /// - If `queue_size_ms` is **zero**, buffering is **disabled** and audio frames are
40+ /// delivered directly to webrtc sinks. In this mode, the caller **must provide 10 ms frames**
41+ /// (i.e., `sample_rate / 100` samples per channel) when calling [`capture_frame`].
42+ /// - If `queue_size_ms` is **non-zero**, buffering is enabled. The value must be a
43+ /// **multiple of 10**, representing the total buffering duration in milliseconds.
44+ /// Frames will be queued and flushed to sinks asynchronously once the buffer
45+ /// reaches the configured threshold.
46+ ///
47+ /// # Panics
48+ /// assert if `queue_size_ms` is not a multiple of 10.
3049 pub fn new (
3150 options : AudioSourceOptions ,
3251 sample_rate : u32 ,
@@ -78,6 +97,49 @@ impl NativeAudioSource {
7897 } ) ;
7998 }
8099
100+ // Fast path: no buffering
101+ if self . queue_size_samples == 0 {
102+ // frame size must be 10ms for fast path
103+ let expected_frames_per_ch = ( self . sample_rate / 100 ) as usize ;
104+ if frame. data . len ( ) % ( self . num_channels as usize ) != 0 {
105+ return Err ( RtcError {
106+ error_type : RtcErrorType :: InvalidState ,
107+ message : "frame.data length not divisible by channel count" . to_owned ( ) ,
108+ } ) ;
109+ }
110+ let nb_frames = frame. data . len ( ) / ( self . num_channels as usize ) ;
111+ if nb_frames != expected_frames_per_ch {
112+ return Err ( RtcError {
113+ error_type : RtcErrorType :: InvalidState ,
114+ message : format ! (
115+ "direct capture requires 10ms frames: got {} frames, expected {}" ,
116+ nb_frames, expected_frames_per_ch
117+ ) ,
118+ } ) ;
119+ }
120+
121+ unsafe {
122+ // Pass null ctx + null callback; C++ ignores them in direct mode
123+ let data: & [ i16 ] = frame. data . as_ref ( ) ;
124+ let ok = self . sys_handle . capture_frame (
125+ data,
126+ self . sample_rate ,
127+ self . num_channels ,
128+ nb_frames,
129+ std:: ptr:: null ( ) ,
130+ std:: mem:: zeroed :: < sys_at:: CompleteCallback > ( ) ,
131+ ) ;
132+ if !ok {
133+ return Err ( RtcError {
134+ error_type : RtcErrorType :: InvalidState ,
135+ message : "failed to capture frame without buffering" . to_owned ( ) ,
136+ } ) ;
137+ }
138+ }
139+ return Ok ( ( ) ) ;
140+ }
141+
142+ // Buffered path.
81143 extern "C" fn lk_audio_source_complete ( userdata : * const sys_at:: SourceContext ) {
82144 let tx = unsafe { Box :: from_raw ( userdata as * mut oneshot:: Sender < ( ) > ) } ;
83145 let _ = tx. send ( ( ) ) ;
@@ -91,6 +153,7 @@ impl NativeAudioSource {
91153 let ctx_ptr = Box :: into_raw ( ctx) as * const sys_at:: SourceContext ;
92154
93155 unsafe {
156+ // In the fast path, C++ never store / invoke on_complete / ctx.
94157 if !self . sys_handle . capture_frame (
95158 chunk,
96159 self . sample_rate ,
0 commit comments