fuchsia_audio_dai/
driver.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use anyhow::{Error, format_err};
6use fidl::endpoints::{Proxy, ServerEnd};
7use fidl_fuchsia_hardware_audio::*;
8
9use futures::future::{self, Either};
10use futures::{Future, FutureExt, TryFutureExt};
11use std::path::{Path, PathBuf};
12
13#[derive(Debug)]
14pub struct DigitalAudioInterface {
15    /// The path used to connect to the device.
16    path: PathBuf,
17    /// The proxy to the device, if connected.
18    proxy: Option<DaiProxy>,
19}
20
21impl DigitalAudioInterface {
22    /// A new interface that will connect to the device at `path`.
23    /// The interface is unconnected when created.
24    pub fn new(path: &Path) -> Self {
25        Self { path: path.to_path_buf(), proxy: None }
26    }
27
28    /// Build a DAI from a proxy.  Path will be empty.
29    pub(crate) fn from_proxy(proxy: DaiProxy) -> Self {
30        Self { path: PathBuf::new(), proxy: Some(proxy) }
31    }
32
33    /// Connect to the DigitalAudioInterface.
34    pub fn connect(&mut self) -> Result<(), Error> {
35        if let Some(proxy) = &self.proxy {
36            if !proxy.is_closed() {
37                return Ok(());
38            }
39        }
40        let (dai_connect_proxy, dai_connect_server) =
41            fidl::endpoints::create_proxy::<DaiConnectorMarker>();
42        let path = self.path.to_str().ok_or_else(|| format_err!("invalid DAI path"))?;
43        fdio::service_connect(path, dai_connect_server.into_channel())?;
44
45        let (ours, theirs) = fidl::endpoints::create_proxy::<DaiMarker>();
46        dai_connect_proxy.connect(theirs)?;
47
48        self.proxy = Some(ours);
49        Ok(())
50    }
51
52    fn get_proxy(&self) -> Result<&DaiProxy, Error> {
53        self.proxy.as_ref().ok_or_else(|| format_err!("Proxy not connected"))
54    }
55
56    /// Get the properties of the DAI.
57    /// Will attempt to connect to the DAI if not connected.
58    pub fn properties(&self) -> impl Future<Output = Result<DaiProperties, Error>> {
59        match self.get_proxy() {
60            Err(e) => Either::Left(future::ready(Err(e))),
61            Ok(proxy) => Either::Right(proxy.clone().get_properties().err_into()),
62        }
63    }
64
65    pub fn dai_formats(
66        &self,
67    ) -> impl Future<Output = Result<Vec<DaiSupportedFormats>, Error>> + use<> {
68        let proxy = match self.get_proxy() {
69            Err(e) => return Either::Left(future::ready(Err(e))),
70            Ok(proxy) => proxy,
71        };
72        Either::Right(proxy.clone().get_dai_formats().map(|o| match o {
73            Err(e) => Err(e.into()),
74            Ok(Err(e)) => Err(zx::Status::from_raw(e).into()),
75            Ok(Ok(o)) => Ok(o),
76        }))
77    }
78
79    pub fn ring_buffer_formats(
80        &self,
81    ) -> impl Future<Output = Result<Vec<SupportedFormats>, Error>> {
82        let proxy = match self.get_proxy() {
83            Err(e) => return Either::Left(future::ready(Err(e))),
84            Ok(proxy) => proxy,
85        };
86        Either::Right(proxy.clone().get_ring_buffer_formats().map(|o| match o {
87            Err(e) => Err(e.into()),
88            Ok(Err(e)) => Err(zx::Status::from_raw(e).into()),
89            Ok(Ok(o)) => Ok(o),
90        }))
91    }
92
93    pub fn create_ring_buffer(
94        &self,
95        dai_format: DaiFormat,
96        buffer_format: Format,
97        ring_buffer_client: ServerEnd<RingBufferMarker>,
98    ) -> Result<(), Error> {
99        let proxy = self.get_proxy()?;
100        proxy
101            .create_ring_buffer(&dai_format, &buffer_format, ring_buffer_client)
102            .map_err(Into::into)
103    }
104}
105
106pub(crate) fn ensure_pcm_format_is_supported(
107    ring_buffer_formats: &[SupportedFormats],
108    pcm_format: &PcmFormat,
109) -> Result<(), Error> {
110    for format in ring_buffer_formats {
111        if let SupportedFormats {
112            pcm_supported_formats:
113                Some(PcmSupportedFormats {
114                    channel_sets: Some(channel_sets),
115                    sample_formats: Some(sample_formats),
116                    bytes_per_sample: Some(bytes_per_sample),
117                    valid_bits_per_sample: Some(valid_bits_per_sample),
118                    frame_rates: Some(frame_rates),
119                    ..
120                }),
121            ..
122        } = format
123        {
124            if channel_sets[0].attributes.as_ref().unwrap().len()
125                == pcm_format.number_of_channels as usize
126                && sample_formats.contains(&pcm_format.sample_format)
127                && bytes_per_sample.contains(&pcm_format.bytes_per_sample)
128                && valid_bits_per_sample.contains(&pcm_format.valid_bits_per_sample)
129                && frame_rates.contains(&pcm_format.frame_rate)
130            {
131                return Ok(());
132            }
133        }
134    }
135    Err(format_err!("DAI does not support PCM format: {:?}", pcm_format))
136}
137
138pub(crate) fn ensure_dai_format_is_supported(
139    supported_formats: &[DaiSupportedFormats],
140    dai_format: &DaiFormat,
141) -> Result<(), Error> {
142    for dai_supported in supported_formats.iter() {
143        if dai_supported.number_of_channels.contains(&dai_format.number_of_channels)
144            && dai_supported.sample_formats.contains(&dai_format.sample_format)
145            && dai_supported.frame_formats.contains(&dai_format.frame_format)
146            && dai_supported.frame_rates.contains(&dai_format.frame_rate)
147            && dai_supported.bits_per_slot.contains(&dai_format.bits_per_slot)
148            && dai_supported.bits_per_sample.contains(&dai_format.bits_per_sample)
149        {
150            return Ok(());
151        }
152    }
153    Err(format_err!("DAI does not support DAI format: {dai_format:?} not in {supported_formats:?}"))
154}
155
156#[cfg(test)]
157mod tests {
158    use super::*;
159
160    use async_utils::PollExt;
161    use fuchsia_async as fasync;
162    use futures::StreamExt;
163    use futures::task::Poll;
164    use std::pin::pin;
165
166    fn connected_dai() -> (DigitalAudioInterface, DaiRequestStream) {
167        let (proxy, requests) = fidl::endpoints::create_proxy_and_stream::<DaiMarker>();
168        let dai = DigitalAudioInterface::from_proxy(proxy);
169        (dai, requests)
170    }
171
172    #[fuchsia::test]
173    fn get_properties() {
174        let mut exec = fasync::TestExecutor::new();
175        // Unconnected DAI
176        let dai = DigitalAudioInterface { path: PathBuf::new(), proxy: None };
177
178        let _ = exec
179            .run_singlethreaded(&mut dai.properties())
180            .expect_err("properties of an unconnected DAI should be error");
181
182        let (dai, mut requests) = connected_dai();
183
184        let properties_fut = dai.properties();
185        let mut properties_fut = pin!(properties_fut);
186
187        exec.run_until_stalled(&mut properties_fut).expect_pending("should be pending");
188
189        match exec.run_until_stalled(&mut requests.next()) {
190            Poll::Ready(Some(Ok(DaiRequest::GetProperties { responder }))) => responder
191                .send(&DaiProperties {
192                    is_input: Some(true),
193                    manufacturer: Some(String::from("Fuchsia")),
194                    product_name: Some(String::from("Spinny Audio")),
195                    ..Default::default()
196                })
197                .expect("send response okay"),
198            x => panic!("Expected a ready GetProperties request, got {:?}", x),
199        };
200
201        let result = exec.run_until_stalled(&mut properties_fut).expect("response from properties");
202        let properties = result.expect("ok response");
203        assert_eq!(Some(true), properties.is_input);
204    }
205
206    #[fuchsia::test]
207    fn dai_formats() {
208        let mut exec = fasync::TestExecutor::new();
209        let (dai, mut requests) = connected_dai();
210
211        let supported_formats_fut = dai.dai_formats();
212        let mut supported_formats_fut = pin!(supported_formats_fut);
213
214        // Doesn't need to continue to be held to complete this
215        drop(dai);
216
217        exec.run_until_stalled(&mut supported_formats_fut).expect_pending("should be pending");
218
219        match exec.run_until_stalled(&mut requests.next()) {
220            Poll::Ready(Some(Ok(DaiRequest::GetDaiFormats { responder }))) => responder
221                .send(Ok(&[
222                    DaiSupportedFormats {
223                        number_of_channels: vec![1],
224                        sample_formats: vec![
225                            DaiSampleFormat::PcmSigned,
226                            DaiSampleFormat::PcmUnsigned,
227                        ],
228                        frame_formats: vec![DaiFrameFormat::FrameFormatStandard(
229                            DaiFrameFormatStandard::Tdm1,
230                        )],
231                        frame_rates: vec![44100],
232                        bits_per_slot: vec![16],
233                        bits_per_sample: vec![16],
234                    },
235                    DaiSupportedFormats {
236                        number_of_channels: vec![2],
237                        sample_formats: vec![DaiSampleFormat::PcmSigned],
238                        frame_formats: vec![DaiFrameFormat::FrameFormatStandard(
239                            DaiFrameFormatStandard::I2S,
240                        )],
241                        frame_rates: vec![8000],
242                        bits_per_slot: vec![32],
243                        bits_per_sample: vec![32],
244                    },
245                ]))
246                .expect("send response okay"),
247            x => panic!("expected a ready GetDaiFormats, got {:?}", x),
248        };
249
250        let result = exec.run_until_stalled(&mut supported_formats_fut).expect("response");
251        let formats = result.expect("ok response");
252        assert_eq!(2, formats.len());
253    }
254}