• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2020 The ChromiumOS Authors
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 std::fs::File;
11 use std::os::unix::io::FromRawFd;
12 use std::path::Path;
13 use std::sync::Arc;
14 use std::time::Duration;
15 use std::time::Instant;
16 
17 use audio_streams::shm_streams::BufferSet;
18 use audio_streams::shm_streams::ServerRequest;
19 use audio_streams::shm_streams::SharedMemory as AudioSharedMemory;
20 use audio_streams::shm_streams::ShmStream;
21 use audio_streams::shm_streams::ShmStreamSource;
22 use audio_streams::BoxError;
23 use audio_streams::SampleFormat;
24 use audio_streams::StreamDirection;
25 use audio_streams::StreamEffect;
26 use base::error;
27 use base::Error as SysError;
28 use base::MemoryMapping;
29 use base::MemoryMappingBuilder;
30 use base::RawDescriptor;
31 use base::SharedMemory;
32 use base::SharedMemoryUnix;
33 use data_model::VolatileMemory;
34 use sync::Mutex;
35 
36 use super::shm_vios::Error;
37 use super::shm_vios::Result;
38 use super::shm_vios::VioSClient;
39 use super::shm_vios::VioSStreamParams;
40 use crate::virtio::snd::common::*;
41 use crate::virtio::snd::constants::*;
42 
43 // This is the error type used in audio_streams::shm_streams. Unfortunately, it's not declared
44 // public there so it needs to be re-declared here. It also prevents the usage of anyhow::Error.
45 type GenericResult<T> = std::result::Result<T, BoxError>;
46 
47 enum StreamState {
48     Available,
49     Acquired,
50     Active,
51 }
52 
53 struct StreamDesc {
54     state: Arc<Mutex<StreamState>>,
55     direction: StreamDirection,
56 }
57 
58 /// Adapter that provides the ShmStreamSource trait around the VioS backend.
59 pub struct VioSShmStreamSource {
60     vios_client: Arc<VioSClient>,
61     stream_descs: Vec<StreamDesc>,
62 }
63 
64 impl VioSShmStreamSource {
65     /// Creates a new stream source given the path to the audio server's socket.
new<P: AsRef<Path>>(server: P) -> Result<VioSShmStreamSource>66     pub fn new<P: AsRef<Path>>(server: P) -> Result<VioSShmStreamSource> {
67         let vios_client = Arc::new(VioSClient::try_new(server)?);
68         let mut stream_descs: Vec<StreamDesc> = Vec::new();
69         let mut idx = 0u32;
70         while let Some(info) = vios_client.stream_info(idx) {
71             stream_descs.push(StreamDesc {
72                 state: Arc::new(Mutex::new(StreamState::Active)),
73                 direction: if info.direction == VIRTIO_SND_D_OUTPUT {
74                     StreamDirection::Playback
75                 } else {
76                     StreamDirection::Capture
77                 },
78             });
79             idx += 1;
80         }
81         Ok(Self {
82             vios_client,
83             stream_descs,
84         })
85     }
86 }
87 
88 impl VioSShmStreamSource {
new_stream_inner( &mut self, stream_id: u32, direction: StreamDirection, num_channels: usize, format: SampleFormat, frame_rate: u32, buffer_size: usize, _effects: &[StreamEffect], client_shm: &dyn AudioSharedMemory<Error = base::Error>, _buffer_offsets: [u64; 2], ) -> GenericResult<Box<dyn ShmStream>>89     fn new_stream_inner(
90         &mut self,
91         stream_id: u32,
92         direction: StreamDirection,
93         num_channels: usize,
94         format: SampleFormat,
95         frame_rate: u32,
96         buffer_size: usize,
97         _effects: &[StreamEffect],
98         client_shm: &dyn AudioSharedMemory<Error = base::Error>,
99         _buffer_offsets: [u64; 2],
100     ) -> GenericResult<Box<dyn ShmStream>> {
101         let frame_size = num_channels * format.sample_bytes();
102         let period_bytes = (frame_size * buffer_size) as u32;
103         self.vios_client.prepare_stream(stream_id)?;
104         let params = VioSStreamParams {
105             buffer_bytes: 2 * period_bytes,
106             period_bytes,
107             features: 0u32,
108             channels: num_channels as u8,
109             format: from_sample_format(format),
110             rate: virtio_frame_rate(frame_rate)?,
111         };
112         self.vios_client.set_stream_parameters(stream_id, params)?;
113         self.vios_client.start_stream(stream_id)?;
114         VioSndShmStream::new(
115             buffer_size,
116             num_channels,
117             format,
118             frame_rate,
119             stream_id,
120             direction,
121             self.vios_client.clone(),
122             client_shm,
123             self.stream_descs[stream_id as usize].state.clone(),
124         )
125     }
126 
get_unused_stream_id(&self, direction: StreamDirection) -> Option<u32>127     fn get_unused_stream_id(&self, direction: StreamDirection) -> Option<u32> {
128         self.stream_descs
129             .iter()
130             .position(|s| match &*s.state.lock() {
131                 StreamState::Available => s.direction == direction,
132                 _ => false,
133             })
134             .map(|idx| idx as u32)
135     }
136 }
137 
138 impl ShmStreamSource<base::Error> for VioSShmStreamSource {
139     /// Creates a new stream
140     #[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: &dyn AudioSharedMemory<Error = base::Error>, buffer_offsets: [u64; 2], ) -> GenericResult<Box<dyn ShmStream>>141     fn new_stream(
142         &mut self,
143         direction: StreamDirection,
144         num_channels: usize,
145         format: SampleFormat,
146         frame_rate: u32,
147         buffer_size: usize,
148         effects: &[StreamEffect],
149         client_shm: &dyn AudioSharedMemory<Error = base::Error>,
150         buffer_offsets: [u64; 2],
151     ) -> GenericResult<Box<dyn ShmStream>> {
152         self.vios_client.start_bg_thread()?;
153         let stream_id = self
154             .get_unused_stream_id(direction)
155             .ok_or(Box::new(Error::NoStreamsAvailable))?;
156         let stream = self
157             .new_stream_inner(
158                 stream_id,
159                 direction,
160                 num_channels,
161                 format,
162                 frame_rate,
163                 buffer_size,
164                 effects,
165                 client_shm,
166                 buffer_offsets,
167             )
168             .map_err(|e| {
169                 // Attempt to release the stream so that it can be used later. This is a best effort
170                 // attempt, so we ignore any error it may return.
171                 let _ = self.vios_client.release_stream(stream_id);
172                 e
173             })?;
174         *self.stream_descs[stream_id as usize].state.lock() = StreamState::Acquired;
175         Ok(stream)
176     }
177 
178     /// Get a list of file descriptors used by the implementation.
179     ///
180     /// Returns any open file descriptors needed by the implementation.
181     /// This list helps users of the ShmStreamSource enter Linux jails without
182     /// closing needed file descriptors.
keep_fds(&self) -> Vec<RawDescriptor>183     fn keep_fds(&self) -> Vec<RawDescriptor> {
184         self.vios_client.keep_rds()
185     }
186 }
187 
188 /// Adapter around a VioS stream that implements the ShmStream trait.
189 pub struct VioSndShmStream {
190     num_channels: usize,
191     frame_rate: u32,
192     buffer_size: usize,
193     frame_size: usize,
194     interval: Duration,
195     next_frame: Duration,
196     start_time: Instant,
197     stream_id: u32,
198     direction: StreamDirection,
199     vios_client: Arc<VioSClient>,
200     client_shm: SharedMemory,
201     state: Arc<Mutex<StreamState>>,
202 }
203 
204 impl VioSndShmStream {
205     /// 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: &dyn AudioSharedMemory<Error = base::Error>, state: Arc<Mutex<StreamState>>, ) -> GenericResult<Box<dyn ShmStream>>206     fn new(
207         buffer_size: usize,
208         num_channels: usize,
209         format: SampleFormat,
210         frame_rate: u32,
211         stream_id: u32,
212         direction: StreamDirection,
213         vios_client: Arc<VioSClient>,
214         client_shm: &dyn AudioSharedMemory<Error = base::Error>,
215         state: Arc<Mutex<StreamState>>,
216     ) -> GenericResult<Box<dyn ShmStream>> {
217         let interval = Duration::from_millis(buffer_size as u64 * 1000 / frame_rate as u64);
218 
219         let dup_fd = unsafe {
220             // Safe because fcntl doesn't affect memory and client_shm should wrap a known valid
221             // file descriptor.
222             libc::fcntl(client_shm.as_raw_fd(), libc::F_DUPFD_CLOEXEC, 0)
223         };
224         if dup_fd < 0 {
225             return Err(Box::new(Error::DupError(SysError::last())));
226         }
227         let file = unsafe {
228             // safe because we checked the result of libc::fcntl()
229             File::from_raw_fd(dup_fd)
230         };
231         let client_shm_clone = SharedMemory::from_file(file).map_err(Error::BaseMmapError)?;
232 
233         Ok(Box::new(Self {
234             num_channels,
235             frame_rate,
236             buffer_size,
237             frame_size: format.sample_bytes() * num_channels,
238             interval,
239             next_frame: interval,
240             start_time: Instant::now(),
241             stream_id,
242             direction,
243             vios_client,
244             client_shm: client_shm_clone,
245             state,
246         }))
247     }
248 }
249 
250 impl ShmStream for VioSndShmStream {
frame_size(&self) -> usize251     fn frame_size(&self) -> usize {
252         self.frame_size
253     }
254 
num_channels(&self) -> usize255     fn num_channels(&self) -> usize {
256         self.num_channels
257     }
258 
frame_rate(&self) -> u32259     fn frame_rate(&self) -> u32 {
260         self.frame_rate
261     }
262 
263     /// Waits until the next time a frame should be sent to the server. The server may release the
264     /// previous buffer much sooner than it needs the next one, so this function may sleep to wait
265     /// for the right time.
wait_for_next_action_with_timeout( &mut self, timeout: Duration, ) -> GenericResult<Option<ServerRequest>>266     fn wait_for_next_action_with_timeout(
267         &mut self,
268         timeout: Duration,
269     ) -> GenericResult<Option<ServerRequest>> {
270         let elapsed = self.start_time.elapsed();
271         if elapsed < self.next_frame {
272             if timeout < self.next_frame - elapsed {
273                 std::thread::sleep(timeout);
274                 return Ok(None);
275             } else {
276                 std::thread::sleep(self.next_frame - elapsed);
277             }
278         }
279         self.next_frame += self.interval;
280         Ok(Some(ServerRequest::new(self.buffer_size, self)))
281     }
282 }
283 
284 impl BufferSet for VioSndShmStream {
callback(&mut self, offset: usize, frames: usize) -> GenericResult<()>285     fn callback(&mut self, offset: usize, frames: usize) -> GenericResult<()> {
286         match self.direction {
287             StreamDirection::Playback => {
288                 let requested_size = frames * self.frame_size;
289                 let shm_ref = &mut self.client_shm;
290                 let (_, res) = self.vios_client.inject_audio_data::<Result<()>, _>(
291                     self.stream_id,
292                     requested_size,
293                     |slice| {
294                         if requested_size != slice.size() {
295                             error!(
296                                 "Buffer size is different than the requested size: {} vs {}",
297                                 requested_size,
298                                 slice.size()
299                             );
300                         }
301                         let size = std::cmp::min(requested_size, slice.size());
302                         let (src_mmap, mmap_offset) = mmap_buffer(shm_ref, offset, size)?;
303                         let src_slice = src_mmap
304                             .get_slice(mmap_offset, size)
305                             .map_err(Error::VolatileMemoryError)?;
306                         src_slice.copy_to_volatile_slice(slice);
307                         Ok(())
308                     },
309                 )?;
310                 res?;
311             }
312             StreamDirection::Capture => {
313                 let requested_size = frames * self.frame_size;
314                 let shm_ref = &mut self.client_shm;
315                 let (_, res) = self.vios_client.request_audio_data::<Result<()>, _>(
316                     self.stream_id,
317                     requested_size,
318                     |slice| {
319                         if requested_size != slice.size() {
320                             error!(
321                                 "Buffer size is different than the requested size: {} vs {}",
322                                 requested_size,
323                                 slice.size()
324                             );
325                         }
326                         let size = std::cmp::min(requested_size, slice.size());
327                         let (dst_mmap, mmap_offset) = mmap_buffer(shm_ref, offset, size)?;
328                         let dst_slice = dst_mmap
329                             .get_slice(mmap_offset, size)
330                             .map_err(Error::VolatileMemoryError)?;
331                         slice.copy_to_volatile_slice(dst_slice);
332                         Ok(())
333                     },
334                 )?;
335                 res?;
336             }
337         }
338         Ok(())
339     }
340 
ignore(&mut self) -> GenericResult<()>341     fn ignore(&mut self) -> GenericResult<()> {
342         Ok(())
343     }
344 }
345 
346 impl Drop for VioSndShmStream {
drop(&mut self)347     fn drop(&mut self) {
348         let stream_id = self.stream_id;
349         if let Err(e) = self
350             .vios_client
351             .stop_stream(stream_id)
352             .and_then(|_| self.vios_client.release_stream(stream_id))
353         {
354             error!("Failed to stop and release stream {}: {}", stream_id, e);
355         }
356         *self.state.lock() = StreamState::Available;
357     }
358 }
359 
360 /// Memory map a shared memory object to access an audio buffer. The buffer may not be located at an
361 /// offset aligned to page size, so the offset within the mapped region is returned along with the
362 /// MemoryMapping struct.
mmap_buffer( src: &mut SharedMemory, offset: usize, size: usize, ) -> Result<(MemoryMapping, usize)>363 fn mmap_buffer(
364     src: &mut SharedMemory,
365     offset: usize,
366     size: usize,
367 ) -> Result<(MemoryMapping, usize)> {
368     // If the buffer is not aligned to page size a bigger region needs to be mapped.
369     let aligned_offset = offset & !(base::pagesize() - 1);
370     let offset_from_mapping_start = offset - aligned_offset;
371     let extended_size = size + offset_from_mapping_start;
372 
373     let mmap = MemoryMappingBuilder::new(extended_size)
374         .offset(aligned_offset as u64)
375         .from_shared_memory(src)
376         .build()
377         .map_err(Error::GuestMmapError)?;
378 
379     Ok((mmap, offset_from_mapping_start))
380 }
381