• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2020 The Chromium OS 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 
5 //! Provides an implementation of the audio_streams::shm_streams::ShmStream trait using the VioS
6 //! client.
7 //! Given that the VioS server doesn't emit an event when the next buffer is expected, this
8 //! implementation uses thread::sleep to drive the frame timings.
9 
10 use super::shm_vios::{VioSClient, VioSStreamParams};
11 
12 use crate::virtio::snd::constants::*;
13 
14 use audio_streams::shm_streams::{BufferSet, ServerRequest, ShmStream, ShmStreamSource};
15 use audio_streams::{BoxError, SampleFormat, StreamDirection, StreamEffect};
16 
17 use base::{error, SharedMemory, SharedMemoryUnix};
18 
19 use std::fs::File;
20 use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
21 use std::path::Path;
22 use std::sync::Arc;
23 use std::time::{Duration, Instant};
24 
25 use sys_util::{Error as SysError, SharedMemory as SysSharedMemory};
26 
27 use super::shm_vios::{Error, Result};
28 
29 // This is the error type used in audio_streams::shm_streams. Unfortunately, it's not declared
30 // public there so it needs to be re-declared here. It also prevents the usage of anyhow::Error.
31 type GenericResult<T> = std::result::Result<T, BoxError>;
32 
33 /// Adapter that provides the ShmStreamSource trait around the VioS backend.
34 pub struct VioSShmStreamSource {
35     vios_client: Arc<VioSClient>,
36 }
37 
38 impl VioSShmStreamSource {
39     /// Creates a new stream source given the path to the audio server's socket.
new<P: AsRef<Path>>(server: P) -> Result<VioSShmStreamSource>40     pub fn new<P: AsRef<Path>>(server: P) -> Result<VioSShmStreamSource> {
41         Ok(Self {
42             vios_client: Arc::new(VioSClient::try_new(server)?),
43         })
44     }
45 }
46 
from_sample_format(format: SampleFormat) -> u847 fn from_sample_format(format: SampleFormat) -> u8 {
48     match format {
49         SampleFormat::U8 => VIRTIO_SND_PCM_FMT_U8,
50         SampleFormat::S16LE => VIRTIO_SND_PCM_FMT_U16,
51         SampleFormat::S24LE => VIRTIO_SND_PCM_FMT_U24,
52         SampleFormat::S32LE => VIRTIO_SND_PCM_FMT_U32,
53     }
54 }
55 
virtio_frame_rate(frame_rate: u32) -> GenericResult<u8>56 fn virtio_frame_rate(frame_rate: u32) -> GenericResult<u8> {
57     Ok(match frame_rate {
58         5512u32 => VIRTIO_SND_PCM_RATE_5512,
59         8000u32 => VIRTIO_SND_PCM_RATE_8000,
60         11025u32 => VIRTIO_SND_PCM_RATE_11025,
61         16000u32 => VIRTIO_SND_PCM_RATE_16000,
62         22050u32 => VIRTIO_SND_PCM_RATE_22050,
63         32000u32 => VIRTIO_SND_PCM_RATE_32000,
64         44100u32 => VIRTIO_SND_PCM_RATE_44100,
65         48000u32 => VIRTIO_SND_PCM_RATE_48000,
66         64000u32 => VIRTIO_SND_PCM_RATE_64000,
67         88200u32 => VIRTIO_SND_PCM_RATE_88200,
68         96000u32 => VIRTIO_SND_PCM_RATE_96000,
69         176400u32 => VIRTIO_SND_PCM_RATE_176400,
70         192000u32 => VIRTIO_SND_PCM_RATE_192000,
71         384000u32 => VIRTIO_SND_PCM_RATE_384000,
72         _ => {
73             return Err(Box::new(Error::UnsupportedFrameRate(frame_rate)));
74         }
75     })
76 }
77 
78 impl ShmStreamSource for VioSShmStreamSource {
79     /// Creates a new stream
80     #[allow(clippy::too_many_arguments)]
new_stream( &mut self, direction: StreamDirection, num_channels: usize, format: SampleFormat, frame_rate: u32, buffer_size: usize, _effects: &[StreamEffect], client_shm: &SysSharedMemory, _buffer_offsets: [u64; 2], ) -> GenericResult<Box<dyn ShmStream>>81     fn new_stream(
82         &mut self,
83         direction: StreamDirection,
84         num_channels: usize,
85         format: SampleFormat,
86         frame_rate: u32,
87         buffer_size: usize,
88         _effects: &[StreamEffect],
89         client_shm: &SysSharedMemory,
90         _buffer_offsets: [u64; 2],
91     ) -> GenericResult<Box<dyn ShmStream>> {
92         self.vios_client.ensure_bg_thread_started()?;
93         let virtio_dir = match direction {
94             StreamDirection::Playback => VIRTIO_SND_D_OUTPUT,
95             StreamDirection::Capture => VIRTIO_SND_D_INPUT,
96         };
97         let frame_size = num_channels * format.sample_bytes();
98         let period_bytes = (frame_size * buffer_size) as u32;
99         let stream_id = self
100             .vios_client
101             .get_unused_stream_id(virtio_dir)
102             .ok_or(Box::new(Error::NoStreamsAvailable))?;
103         // Create the stream object before any errors can be returned to guarantee the stream will
104         // be released in all cases
105         let stream_box = VioSndShmStream::new(
106             buffer_size,
107             num_channels,
108             format,
109             frame_rate,
110             stream_id,
111             direction,
112             self.vios_client.clone(),
113             client_shm,
114         );
115         self.vios_client.prepare_stream(stream_id)?;
116         let params = VioSStreamParams {
117             buffer_bytes: 2 * period_bytes,
118             period_bytes,
119             features: 0u32,
120             channels: num_channels as u8,
121             format: from_sample_format(format),
122             rate: virtio_frame_rate(frame_rate)?,
123         };
124         self.vios_client.set_stream_parameters(stream_id, params)?;
125         self.vios_client.start_stream(stream_id)?;
126         stream_box
127     }
128 
129     /// Get a list of file descriptors used by the implementation.
130     ///
131     /// Returns any open file descriptors needed by the implementation.
132     /// This list helps users of the ShmStreamSource enter Linux jails without
133     /// closing needed file descriptors.
keep_fds(&self) -> Vec<RawFd>134     fn keep_fds(&self) -> Vec<RawFd> {
135         self.vios_client.keep_fds()
136     }
137 }
138 
139 /// Adapter around a VioS stream that implements the ShmStream trait.
140 pub struct VioSndShmStream {
141     num_channels: usize,
142     frame_rate: u32,
143     buffer_size: usize,
144     frame_size: usize,
145     interval: Duration,
146     next_frame: Duration,
147     start_time: Instant,
148     stream_id: u32,
149     direction: StreamDirection,
150     vios_client: Arc<VioSClient>,
151     client_shm: SharedMemory,
152 }
153 
154 impl VioSndShmStream {
155     /// Creates a new shm stream.
new( buffer_size: usize, num_channels: usize, format: SampleFormat, frame_rate: u32, stream_id: u32, direction: StreamDirection, vios_client: Arc<VioSClient>, client_shm: &SysSharedMemory, ) -> GenericResult<Box<dyn ShmStream>>156     fn new(
157         buffer_size: usize,
158         num_channels: usize,
159         format: SampleFormat,
160         frame_rate: u32,
161         stream_id: u32,
162         direction: StreamDirection,
163         vios_client: Arc<VioSClient>,
164         client_shm: &SysSharedMemory,
165     ) -> GenericResult<Box<dyn ShmStream>> {
166         let interval = Duration::from_millis(buffer_size as u64 * 1000 / frame_rate as u64);
167 
168         let dup_fd = unsafe {
169             // Safe because fcntl doesn't affect memory and client_shm should wrap a known valid
170             // file descriptor.
171             libc::fcntl(client_shm.as_raw_fd(), libc::F_DUPFD_CLOEXEC, 0)
172         };
173         if dup_fd < 0 {
174             return Err(Box::new(Error::DupError(SysError::last())));
175         }
176         let file = unsafe {
177             // safe because we checked the result of libc::fcntl()
178             File::from_raw_fd(dup_fd)
179         };
180         let client_shm_clone =
181             SharedMemory::from_file(file).map_err(|e| Error::BaseMmapError(e))?;
182 
183         Ok(Box::new(Self {
184             num_channels,
185             frame_rate,
186             buffer_size,
187             frame_size: format.sample_bytes() * num_channels,
188             interval,
189             next_frame: interval,
190             start_time: Instant::now(),
191             stream_id,
192             direction,
193             vios_client,
194             client_shm: client_shm_clone,
195         }))
196     }
197 }
198 
199 impl ShmStream for VioSndShmStream {
frame_size(&self) -> usize200     fn frame_size(&self) -> usize {
201         self.frame_size
202     }
203 
num_channels(&self) -> usize204     fn num_channels(&self) -> usize {
205         self.num_channels
206     }
207 
frame_rate(&self) -> u32208     fn frame_rate(&self) -> u32 {
209         self.frame_rate
210     }
211 
212     /// Waits until the next time a frame should be sent to the server. The server may release the
213     /// previous buffer much sooner than it needs the next one, so this function may sleep to wait
214     /// for the right time.
wait_for_next_action_with_timeout( &mut self, timeout: Duration, ) -> GenericResult<Option<ServerRequest>>215     fn wait_for_next_action_with_timeout(
216         &mut self,
217         timeout: Duration,
218     ) -> GenericResult<Option<ServerRequest>> {
219         let elapsed = self.start_time.elapsed();
220         if elapsed < self.next_frame {
221             if timeout < self.next_frame - elapsed {
222                 std::thread::sleep(timeout);
223                 return Ok(None);
224             } else {
225                 std::thread::sleep(self.next_frame - elapsed);
226             }
227         }
228         self.next_frame += self.interval;
229         Ok(Some(ServerRequest::new(self.buffer_size, self)))
230     }
231 }
232 
233 impl BufferSet for VioSndShmStream {
callback(&mut self, offset: usize, frames: usize) -> GenericResult<()>234     fn callback(&mut self, offset: usize, frames: usize) -> GenericResult<()> {
235         match self.direction {
236             StreamDirection::Playback => {
237                 self.vios_client.inject_audio_data(
238                     self.stream_id,
239                     &mut self.client_shm,
240                     offset,
241                     frames * self.frame_size,
242                 )?;
243             }
244             StreamDirection::Capture => {
245                 self.vios_client.request_audio_data(
246                     self.stream_id,
247                     &mut self.client_shm,
248                     offset,
249                     frames * self.frame_size,
250                 )?;
251             }
252         }
253         Ok(())
254     }
255 
ignore(&mut self) -> GenericResult<()>256     fn ignore(&mut self) -> GenericResult<()> {
257         Ok(())
258     }
259 }
260 
261 impl Drop for VioSndShmStream {
drop(&mut self)262     fn drop(&mut self) {
263         let stream_id = self.stream_id;
264         if let Err(e) = self
265             .vios_client
266             .stop_stream(stream_id)
267             .and_then(|_| self.vios_client.release_stream(stream_id))
268         {
269             error!("Failed to stop and release stream {}: {}", stream_id, e);
270         }
271     }
272 }
273