1use anyhow::{format_err, Error};
6use fidl::prelude::*;
7use fidl_fuchsia_hardware_audio::*;
8use futures::future::MaybeDone;
9use futures::StreamExt;
10use log::info;
11use std::sync::Arc;
12use {fidl_fuchsia_media as media, fuchsia_async as fasync};
13
14use crate::driver::{ensure_dai_format_is_supported, ensure_pcm_format_is_supported};
15use crate::DigitalAudioInterface;
16
17pub struct DaiAudioDevice {
18 dai: Arc<DigitalAudioInterface>,
20 is_input: bool,
22 configured_formats: Option<(DaiFormat, PcmFormat)>,
26 dai_formats: Vec<DaiSupportedFormats>,
27 pcm_formats: Vec<SupportedFormats>,
28 config_stream_task: MaybeDone<fasync::Task<Result<(), Error>>>,
29}
30
31impl DaiAudioDevice {
32 pub async fn build(mut dai: DigitalAudioInterface) -> Result<Self, Error> {
36 dai.connect()?;
37 let props = dai.properties().await?;
38 let dai_formats = dai.dai_formats().await?;
39 let pcm_formats = dai.ring_buffer_formats().await?;
40 Ok(Self {
41 dai: Arc::new(dai),
42 is_input: props
43 .is_input
44 .ok_or_else(|| format_err!("DAI did not provide required is_input"))?,
45 configured_formats: None,
46 dai_formats,
47 pcm_formats,
48 config_stream_task: MaybeDone::Gone,
49 })
50 }
51
52 pub fn config(&mut self, dai_format: DaiFormat, pcm_format: PcmFormat) -> Result<(), Error> {
53 ensure_dai_format_is_supported(&self.dai_formats[..], &dai_format)?;
54 ensure_pcm_format_is_supported(&self.pcm_formats[..], &pcm_format)?;
55 self.configured_formats = Some((dai_format, pcm_format));
56 Ok(())
57 }
58
59 pub fn start(
64 &mut self,
65 proxy: media::AudioDeviceEnumeratorProxy,
66 name: &str,
67 id: [u8; 16],
68 manufacturer: &str,
69 product: &str,
70 ) -> Result<(), Error> {
71 if let MaybeDone::Future(_) = &self.config_stream_task {
72 return Err(format_err!("Attempted to start a DAI that is still running"));
74 }
75 let (dai_format, pcm_format) =
76 self.configured_formats.clone().ok_or_else(|| format_err!("formats not configured"))?;
77 let (client, request_stream) =
78 fidl::endpoints::create_request_stream::<StreamConfigMarker>();
79 self.config_stream_task =
80 futures::future::maybe_done(fasync::Task::spawn(process_audio_requests(
81 request_stream,
82 self.dai.clone(),
83 pcm_format,
84 dai_format,
85 id,
86 manufacturer.to_string(),
87 product.to_string(),
88 )));
89 Ok(proxy.add_device_by_channel(name, self.is_input, client)?)
90 }
91
92 pub fn dai_formats(&self) -> &Vec<DaiSupportedFormats> {
93 &self.dai_formats
94 }
95
96 pub fn stop(&mut self) {
99 self.config_stream_task = MaybeDone::Gone;
100 }
101}
102
103async fn process_audio_requests(
106 mut stream: StreamConfigRequestStream,
107 dai: Arc<DigitalAudioInterface>,
108 pcm_format: PcmFormat,
109 dai_format: DaiFormat,
110 unique_id: [u8; 16],
111 manufacturer: String,
112 product: String,
113) -> Result<(), Error> {
114 let dai_props = dai.properties().await?;
115 let attributes =
116 Some(vec![ChannelAttributes::default(); pcm_format.number_of_channels as usize]);
117 let channel_set = ChannelSet { attributes: attributes, ..Default::default() };
118 let supported_formats = PcmSupportedFormats {
119 channel_sets: Some(vec![channel_set]),
120 sample_formats: Some(vec![pcm_format.sample_format]),
121 bytes_per_sample: Some(vec![pcm_format.bytes_per_sample]),
122 valid_bits_per_sample: Some(vec![pcm_format.valid_bits_per_sample]),
123 frame_rates: Some(vec![pcm_format.frame_rate]),
124 ..Default::default()
125 };
126 let mut gain_state_replied = false;
127 let mut plug_state_replied = false;
128 while let Some(request) = stream.next().await {
129 if let Err(e) = request {
130 info!(target: "dai_audio_device", "error from the request stream: {:?}", e);
131 break;
132 }
133 match request.unwrap() {
134 StreamConfigRequest::GetHealthState { responder } => {
135 responder.send(&HealthState::default())?;
136 }
137 StreamConfigRequest::SignalProcessingConnect { protocol, control_handle: _ } => {
138 let _ = protocol.close_with_epitaph(zx::Status::NOT_SUPPORTED);
139 }
140 StreamConfigRequest::GetProperties { responder } => {
141 let prop = StreamProperties {
142 unique_id: Some(unique_id.clone()),
143 is_input: dai_props.is_input,
144 can_mute: Some(false),
145 can_agc: Some(false),
146 min_gain_db: Some(0f32),
147 max_gain_db: Some(0f32),
148 gain_step_db: Some(0f32),
149 plug_detect_capabilities: Some(PlugDetectCapabilities::Hardwired),
150 manufacturer: Some(manufacturer.clone()),
151 product: Some(product.clone()),
152 clock_domain: Some(CLOCK_DOMAIN_MONOTONIC),
153 ..Default::default()
154 };
155
156 responder.send(&prop)?;
157 }
158 StreamConfigRequest::GetSupportedFormats { responder } => {
159 let formats_vector = &[SupportedFormats {
160 pcm_supported_formats: Some(supported_formats.clone()),
161 ..Default::default()
162 }];
163 responder.send(formats_vector)?;
164 }
165 StreamConfigRequest::CreateRingBuffer { format, ring_buffer, .. } => {
166 dai.create_ring_buffer(dai_format, format, ring_buffer)?;
167 }
168 StreamConfigRequest::WatchGainState { responder } => {
169 if gain_state_replied {
170 responder.drop_without_shutdown();
172 continue;
173 }
174 let gain_state = GainState {
175 muted: Some(false),
176 agc_enabled: Some(false),
177 gain_db: Some(0.0f32),
178 ..Default::default()
179 };
180 responder.send(&gain_state)?;
181 gain_state_replied = true;
182 }
183 StreamConfigRequest::SetGain { target_state, .. } => {
184 info!(target: "dai_audio_device", "Request to set gain ignored: {:?}", target_state);
186 }
187 StreamConfigRequest::WatchPlugState { responder } => {
188 if plug_state_replied {
189 responder.drop_without_shutdown();
191 continue;
192 }
193 let time = fasync::MonotonicInstant::now();
194 let plug_state = PlugState {
195 plugged: Some(true),
196 plug_state_time: Some(time.into_nanos() as i64),
197 ..Default::default()
198 };
199 responder.send(&plug_state)?;
200 plug_state_replied = true;
201 }
202 }
203 }
204 info!(target: "dai_audio_device", "audio request stream processing ending");
205 Ok(())
206}
207
208#[cfg(test)]
209mod tests {
210 use fidl_fuchsia_media::{AudioDeviceEnumeratorMarker, AudioDeviceEnumeratorRequest};
211 use fixture::fixture;
212 use futures::task::Context;
213 use futures::Future;
214 use std::pin::Pin;
215
216 use super::*;
217 use crate::test::test_digital_audio_interface;
218
219 const SUPPORTED_DAI_FORMAT: DaiFormat = DaiFormat {
220 number_of_channels: 1,
221 channels_to_use_bitmask: 1,
222 sample_format: DaiSampleFormat::PcmSigned,
223 frame_format: DaiFrameFormat::FrameFormatStandard(DaiFrameFormatStandard::Tdm1),
224 frame_rate: 16000,
225 bits_per_slot: 16,
226 bits_per_sample: 16,
227 };
228
229 const UNSUPPORTED_DAI_FORMAT: DaiFormat =
230 DaiFormat { number_of_channels: 4, ..SUPPORTED_DAI_FORMAT };
231
232 const SUPPORTED_PCM_FORMAT: PcmFormat = PcmFormat {
233 number_of_channels: 1,
234 sample_format: SampleFormat::PcmSigned,
235 bytes_per_sample: 2,
236 valid_bits_per_sample: 16,
237 frame_rate: 16000,
238 };
239
240 const UNSUPPORTED_PCM_FORMAT: PcmFormat =
241 PcmFormat { number_of_channels: 4, ..SUPPORTED_PCM_FORMAT };
242
243 async fn fix_audio_device<F, Fut>(_name: &str, test: F)
244 where
245 F: FnOnce(DaiAudioDevice) -> Fut,
246 Fut: futures::Future<Output = ()>,
247 {
248 let (dai, _handle) = test_digital_audio_interface(true);
249 let device = DaiAudioDevice::build(dai).await.expect("builds okay");
250 test(device).await
251 }
252
253 #[fixture(fix_audio_device)]
254 #[fuchsia::test]
255 async fn configure_format_is_supported(mut dai: DaiAudioDevice) {
256 let result = dai.config(UNSUPPORTED_DAI_FORMAT, SUPPORTED_PCM_FORMAT);
257 assert!(result.is_err());
258
259 let result = dai.config(SUPPORTED_DAI_FORMAT, UNSUPPORTED_PCM_FORMAT);
260 assert!(result.is_err());
261
262 let result = dai.config(SUPPORTED_DAI_FORMAT, SUPPORTED_PCM_FORMAT);
263 assert!(result.is_ok());
264
265 let result = dai.config(SUPPORTED_DAI_FORMAT, SUPPORTED_PCM_FORMAT);
267 assert!(result.is_ok());
268 }
269
270 const TEST_DEVICE_NAME: &'static str = &"Test device";
271 const TEST_DEVICE_ID: &'static [u8; 16] =
272 &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
273 const TEST_MANUFACTURER: &'static str = &"Spacely Sprockets";
274 const TEST_PRODUCT: &'static str = &"💖 Test Fuchsia Device";
275
276 async fn setup_audio_stream_proxy(dai: &mut DaiAudioDevice) -> StreamConfigProxy {
277 let (proxy, mut requests) =
278 fidl::endpoints::create_proxy_and_stream::<AudioDeviceEnumeratorMarker>();
279
280 let _ = dai.config(SUPPORTED_DAI_FORMAT, SUPPORTED_PCM_FORMAT).unwrap();
281
282 let _ = dai
283 .start(proxy, TEST_DEVICE_NAME, TEST_DEVICE_ID.clone(), TEST_MANUFACTURER, TEST_PRODUCT)
284 .expect("Should start okay.");
285
286 match requests.next().await {
287 Some(Ok(AudioDeviceEnumeratorRequest::AddDeviceByChannel {
288 device_name,
289 is_input,
290 channel,
291 ..
292 })) => {
293 assert_eq!(&device_name, TEST_DEVICE_NAME, "name should match");
294 assert_eq!(is_input, true, "should be an input device");
295 channel.into_proxy()
296 }
297 x => panic!("Expected AudioDevice to be added by channel, got {:?}", x),
298 }
299 }
300
301 #[fixture(fix_audio_device)]
302 #[fuchsia::test]
303 async fn start_enumerator_registration(mut dai: DaiAudioDevice) {
304 let (proxy, _requests) =
305 fidl::endpoints::create_proxy_and_stream::<AudioDeviceEnumeratorMarker>();
306
307 let _ = dai
309 .start(
310 proxy.clone(),
311 TEST_DEVICE_NAME,
312 TEST_DEVICE_ID.clone(),
313 TEST_MANUFACTURER,
314 TEST_PRODUCT,
315 )
316 .expect_err("Should have been an Error setting up without configure");
317
318 let stream_proxy = setup_audio_stream_proxy(&mut dai).await;
319
320 let properties = stream_proxy.get_properties().await.expect("properties should return");
321 assert_eq!(Some(true), properties.is_input);
322 assert_eq!(TEST_DEVICE_ID, &properties.unique_id.expect("Unique ID should be set"));
323 assert_eq!(
324 TEST_MANUFACTURER,
325 &properties.manufacturer.expect("manufacturer should be set")
326 );
327 assert_eq!(TEST_PRODUCT, &properties.product.expect("product should be set"));
328 }
329
330 #[fixture(fix_audio_device)]
331 #[fuchsia::test]
332 async fn stop_and_restart(mut dai: DaiAudioDevice) {
333 let stream_proxy = setup_audio_stream_proxy(&mut dai).await;
334 let _ = stream_proxy.get_properties().await.expect("properties should return");
335 dai.stop();
336
337 let e = stream_proxy.get_properties().await.expect_err("should have closed audio config");
338 assert!(matches!(e, fidl::Error::ClientChannelClosed { .. }));
339
340 let stream_proxy = setup_audio_stream_proxy(&mut dai).await;
341 let _ = stream_proxy.get_properties().await.expect("properties should return");
342 }
343
344 #[fixture(fix_audio_device)]
345 #[fuchsia::test]
346 async fn reports_correct_supported_formats(mut dai: DaiAudioDevice) {
347 let stream_proxy = setup_audio_stream_proxy(&mut dai).await;
348
349 let mut supported_formats =
350 stream_proxy.get_supported_formats().await.expect("Result from supported formats");
351
352 assert_eq!(1, supported_formats.len());
353 let pcm_formats = supported_formats
354 .pop()
355 .expect("should be a formats")
356 .pcm_supported_formats
357 .expect("pcm supported formats");
358 assert_eq!(
359 SUPPORTED_PCM_FORMAT.number_of_channels as usize,
360 pcm_formats.channel_sets.unwrap()[0].attributes.as_ref().unwrap().len()
361 );
362 assert_eq!(&[SUPPORTED_PCM_FORMAT.sample_format], &pcm_formats.sample_formats.unwrap()[..]);
363 assert_eq!(
364 &[SUPPORTED_PCM_FORMAT.bytes_per_sample],
365 &pcm_formats.bytes_per_sample.unwrap()[..]
366 );
367 assert_eq!(
368 &[SUPPORTED_PCM_FORMAT.valid_bits_per_sample],
369 &pcm_formats.valid_bits_per_sample.unwrap()[..]
370 );
371 assert_eq!(&[SUPPORTED_PCM_FORMAT.frame_rate], &pcm_formats.frame_rates.unwrap()[..]);
372 }
373
374 #[fixture(fix_audio_device)]
375 #[fuchsia::test]
376 async fn ringbuffer_starts_dai_without_error(mut dai: DaiAudioDevice) {
377 let stream_proxy = setup_audio_stream_proxy(&mut dai).await;
378
379 let (rb_proxy, server_end) = fidl::endpoints::create_proxy::<RingBufferMarker>();
380
381 let format = fidl_fuchsia_hardware_audio::Format {
382 pcm_format: Some(SUPPORTED_PCM_FORMAT.clone()),
383 ..Default::default()
384 };
385 stream_proxy.create_ring_buffer(&format, server_end).expect("setup ring buffer okay");
386
387 let start_time = rb_proxy.start().await;
388 assert!(start_time.is_ok());
389 }
390
391 #[fixture(fix_audio_device)]
392 #[fuchsia::test]
393 async fn gain_state_hanging_get(mut dai: DaiAudioDevice) {
394 let stream_proxy = setup_audio_stream_proxy(&mut dai).await;
395
396 let gain_state = stream_proxy.watch_gain_state().await;
397 let gain_state = gain_state.expect("should have received response from GainState");
398 assert_eq!(Some(0.0f32), gain_state.gain_db);
400
401 let mut gain_state_fut =
403 stream_proxy.watch_gain_state().check().expect("message sends fine");
404
405 assert!(Pin::new(&mut gain_state_fut)
411 .poll(&mut Context::from_waker(futures::task::noop_waker_ref()))
412 .is_pending());
413
414 let _ = stream_proxy
416 .set_gain(&GainState { muted: Some(true), ..Default::default() })
417 .expect("set gain");
418 assert!(Pin::new(&mut gain_state_fut)
419 .poll(&mut Context::from_waker(futures::task::noop_waker_ref()))
420 .is_pending());
421 let _ = stream_proxy.get_properties().await.expect("properties");
423 assert!(Pin::new(&mut gain_state_fut)
424 .poll(&mut Context::from_waker(futures::task::noop_waker_ref()))
425 .is_pending());
426 }
427
428 #[fixture(fix_audio_device)]
429 #[fuchsia::test]
430 async fn plug_state_hanging_get(mut dai: DaiAudioDevice) {
431 let stream_proxy = setup_audio_stream_proxy(&mut dai).await;
432
433 let plug_state = stream_proxy.watch_plug_state().await;
434 let plug_state = plug_state.expect("should have received response from PlugState");
435 assert_eq!(Some(true), plug_state.plugged);
437
438 let mut plug_state_fut =
440 stream_proxy.watch_plug_state().check().expect("message sends fine");
441
442 assert!(Pin::new(&mut plug_state_fut)
448 .poll(&mut Context::from_waker(futures::task::noop_waker_ref()))
449 .is_pending());
450
451 let _ = stream_proxy.get_properties().await.expect("properties");
453 assert!(Pin::new(&mut plug_state_fut)
454 .poll(&mut Context::from_waker(futures::task::noop_waker_ref()))
455 .is_pending());
456 }
457}