gosuto_libwebrtc/native/
audio_source.rs1use cxx::SharedPtr;
16use tokio::sync::oneshot;
17use gosuto_webrtc_sys::audio_track as sys_at;
18
19use crate::{audio_frame::AudioFrame, audio_source::AudioSourceOptions, RtcError, RtcErrorType};
20
21#[derive(Clone)]
22pub struct NativeAudioSource {
23 sys_handle: SharedPtr<sys_at::ffi::AudioTrackSource>,
24 sample_rate: u32,
25 num_channels: u32,
26 queue_size_samples: u32,
27}
28
29impl NativeAudioSource {
30 pub fn new(
50 options: AudioSourceOptions,
51 sample_rate: u32,
52 num_channels: u32,
53 queue_size_ms: u32,
54 ) -> NativeAudioSource {
55 assert!(queue_size_ms % 10 == 0, "queue_size_ms must be a multiple of 10");
56
57 let sys_handle = sys_at::ffi::new_audio_track_source(
58 options.into(),
59 sample_rate.try_into().unwrap(),
60 num_channels.try_into().unwrap(),
61 queue_size_ms.try_into().unwrap(),
62 );
63
64 let queue_size_samples = (queue_size_ms * sample_rate * num_channels) / 1000;
65 Self { sys_handle, sample_rate, num_channels, queue_size_samples }
66 }
67
68 pub fn sys_handle(&self) -> SharedPtr<sys_at::ffi::AudioTrackSource> {
69 self.sys_handle.clone()
70 }
71
72 pub fn set_audio_options(&self, options: AudioSourceOptions) {
73 self.sys_handle.set_audio_options(&sys_at::ffi::AudioSourceOptions::from(options))
74 }
75
76 pub fn audio_options(&self) -> AudioSourceOptions {
77 self.sys_handle.audio_options().into()
78 }
79
80 pub fn sample_rate(&self) -> u32 {
81 self.sample_rate
82 }
83
84 pub fn num_channels(&self) -> u32 {
85 self.num_channels
86 }
87
88 pub fn clear_buffer(&self) {
89 self.sys_handle.clear_buffer();
90 }
91
92 pub async fn capture_frame(&self, frame: &AudioFrame<'_>) -> Result<(), RtcError> {
93 if self.sample_rate != frame.sample_rate || self.num_channels != frame.num_channels {
94 return Err(RtcError {
95 error_type: RtcErrorType::InvalidState,
96 message: "sample_rate and num_channels don't match".to_owned(),
97 });
98 }
99
100 if self.queue_size_samples == 0 {
102 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 extern "C" fn noop_complete_callback(_ctx: *const sys_at::SourceContext) {
124 }
126
127 unsafe {
128 let data: &[i16] = frame.data.as_ref();
129 let noop_callback = sys_at::CompleteCallback(noop_complete_callback);
132 let ok = self.sys_handle.capture_frame(
133 data,
134 self.sample_rate,
135 self.num_channels,
136 nb_frames,
137 std::ptr::null(), noop_callback,
139 );
140 if !ok {
141 return Err(RtcError {
142 error_type: RtcErrorType::InvalidState,
143 message: "failed to capture frame without buffering".to_owned(),
144 });
145 }
146 }
147 return Ok(());
148 }
149
150 extern "C" fn lk_audio_source_complete(userdata: *const sys_at::SourceContext) {
152 let tx = unsafe { Box::from_raw(userdata as *mut oneshot::Sender<()>) };
153 let _ = tx.send(());
154 }
155
156 for chunk in frame.data.chunks(self.queue_size_samples as usize) {
158 let nb_frames = chunk.len() / self.num_channels as usize;
159 let (tx, rx) = oneshot::channel::<()>();
160 let ctx = Box::new(tx);
161 let ctx_ptr = Box::into_raw(ctx) as *const sys_at::SourceContext;
162
163 unsafe {
164 if !self.sys_handle.capture_frame(
166 chunk,
167 self.sample_rate,
168 self.num_channels,
169 nb_frames,
170 ctx_ptr,
171 sys_at::CompleteCallback(lk_audio_source_complete),
172 ) {
173 return Err(RtcError {
174 error_type: RtcErrorType::InvalidState,
175 message: "failed to capture frame".to_owned(),
176 });
177 }
178 }
179
180 let _ = rx.await;
181 }
182
183 Ok(())
184 }
185}
186
187impl From<sys_at::ffi::AudioSourceOptions> for AudioSourceOptions {
188 fn from(options: sys_at::ffi::AudioSourceOptions) -> Self {
189 Self {
190 echo_cancellation: options.echo_cancellation,
191 noise_suppression: options.noise_suppression,
192 auto_gain_control: options.auto_gain_control,
193 }
194 }
195}
196
197impl From<AudioSourceOptions> for sys_at::ffi::AudioSourceOptions {
198 fn from(options: AudioSourceOptions) -> Self {
199 Self {
200 echo_cancellation: options.echo_cancellation,
201 noise_suppression: options.noise_suppression,
202 auto_gain_control: options.auto_gain_control,
203 }
204 }
205}