• 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::linux::SharedMemoryLinux;
28 use base::Error as SysError;
29 use base::MemoryMapping;
30 use base::MemoryMappingBuilder;
31 use base::RawDescriptor;
32 use base::SharedMemory;
33 use base::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<Mutex<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(Mutex::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.lock().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.lock().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
113             .lock()
114             .set_stream_parameters(stream_id, params)?;
115         self.vios_client.lock().start_stream(stream_id)?;
116         VioSndShmStream::new(
117             buffer_size,
118             num_channels,
119             format,
120             frame_rate,
121             stream_id,
122             direction,
123             self.vios_client.clone(),
124             client_shm,
125             self.stream_descs[stream_id as usize].state.clone(),
126         )
127     }
128 
get_unused_stream_id(&self, direction: StreamDirection) -> Option<u32>129     fn get_unused_stream_id(&self, direction: StreamDirection) -> Option<u32> {
130         self.stream_descs
131             .iter()
132             .position(|s| match &*s.state.lock() {
133                 StreamState::Available => s.direction == direction,
134                 _ => false,
135             })
136             .map(|idx| idx as u32)
137     }
138 }
139 
140 impl ShmStreamSource<base::Error> for VioSShmStreamSource {
141     /// Creates a new stream
142     #[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>>143     fn new_stream(
144         &mut self,
145         direction: StreamDirection,
146         num_channels: usize,
147         format: SampleFormat,
148         frame_rate: u32,
149         buffer_size: usize,
150         effects: &[StreamEffect],
151         client_shm: &dyn AudioSharedMemory<Error = base::Error>,
152         buffer_offsets: [u64; 2],
153     ) -> GenericResult<Box<dyn ShmStream>> {
154         self.vios_client.lock().start_bg_thread()?;
155         let stream_id = self
156             .get_unused_stream_id(direction)
157             .ok_or(Box::new(Error::NoStreamsAvailable))?;
158         let stream = self
159             .new_stream_inner(
160                 stream_id,
161                 direction,
162                 num_channels,
163                 format,
164                 frame_rate,
165                 buffer_size,
166                 effects,
167                 client_shm,
168                 buffer_offsets,
169             )
170             .map_err(|e| {
171                 // Attempt to release the stream so that it can be used later. This is a best effort
172                 // attempt, so we ignore any error it may return.
173                 let _ = self.vios_client.lock().release_stream(stream_id);
174                 e
175             })?;
176         *self.stream_descs[stream_id as usize].state.lock() = StreamState::Acquired;
177         Ok(stream)
178     }
179 
180     /// Get a list of file descriptors used by the implementation.
181     ///
182     /// Returns any open file descriptors needed by the implementation.
183     /// This list helps users of the ShmStreamSource enter Linux jails without
184     /// closing needed file descriptors.
keep_fds(&self) -> Vec<RawDescriptor>185     fn keep_fds(&self) -> Vec<RawDescriptor> {
186         self.vios_client.lock().keep_rds()
187     }
188 }
189 
190 /// Adapter around a VioS stream that implements the ShmStream trait.
191 pub struct VioSndShmStream {
192     num_channels: usize,
193     frame_rate: u32,
194     buffer_size: usize,
195     frame_size: usize,
196     interval: Duration,
197     next_frame: Duration,
198     start_time: Instant,
199     stream_id: u32,
200     direction: StreamDirection,
201     vios_client: Arc<Mutex<VioSClient>>,
202     client_shm: SharedMemory,
203     state: Arc<Mutex<StreamState>>,
204 }
205 
206 impl VioSndShmStream {
207     /// 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<Mutex<VioSClient>>, client_shm: &dyn AudioSharedMemory<Error = base::Error>, state: Arc<Mutex<StreamState>>, ) -> GenericResult<Box<dyn ShmStream>>208     fn new(
209         buffer_size: usize,
210         num_channels: usize,
211         format: SampleFormat,
212         frame_rate: u32,
213         stream_id: u32,
214         direction: StreamDirection,
215         vios_client: Arc<Mutex<VioSClient>>,
216         client_shm: &dyn AudioSharedMemory<Error = base::Error>,
217         state: Arc<Mutex<StreamState>>,
218     ) -> GenericResult<Box<dyn ShmStream>> {
219         let interval = Duration::from_millis(buffer_size as u64 * 1000 / frame_rate as u64);
220 
221         // SAFETY:
222         // Safe because fcntl doesn't affect memory and client_shm should wrap a known valid
223         // file descriptor.
224         let dup_fd = unsafe { libc::fcntl(client_shm.as_raw_fd(), libc::F_DUPFD_CLOEXEC, 0) };
225         if dup_fd < 0 {
226             return Err(Box::new(Error::DupError(SysError::last())));
227         }
228         // SAFETY:
229         // safe because we checked the result of libc::fcntl()
230         let file = unsafe { File::from_raw_fd(dup_fd) };
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.lock().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
316                     .vios_client
317                     .lock()
318                     .request_audio_data::<Result<()>, _>(
319                         self.stream_id,
320                         requested_size,
321                         |slice| {
322                             if requested_size != slice.size() {
323                                 error!(
324                                     "Buffer size is different than the requested size: {} vs {}",
325                                     requested_size,
326                                     slice.size()
327                                 );
328                             }
329                             let size = std::cmp::min(requested_size, slice.size());
330                             let (dst_mmap, mmap_offset) = mmap_buffer(shm_ref, offset, size)?;
331                             let dst_slice = dst_mmap
332                                 .get_slice(mmap_offset, size)
333                                 .map_err(Error::VolatileMemoryError)?;
334                             slice.copy_to_volatile_slice(dst_slice);
335                             Ok(())
336                         },
337                     )?;
338                 res?;
339             }
340         }
341         Ok(())
342     }
343 
ignore(&mut self) -> GenericResult<()>344     fn ignore(&mut self) -> GenericResult<()> {
345         Ok(())
346     }
347 }
348 
349 impl Drop for VioSndShmStream {
drop(&mut self)350     fn drop(&mut self) {
351         let stream_id = self.stream_id;
352         {
353             let vios_client = self.vios_client.lock();
354             if let Err(e) = vios_client
355                 .stop_stream(stream_id)
356                 .and_then(|_| vios_client.release_stream(stream_id))
357             {
358                 error!("Failed to stop and release stream {}: {}", stream_id, e);
359             }
360         }
361         *self.state.lock() = StreamState::Available;
362     }
363 }
364 
365 /// Memory map a shared memory object to access an audio buffer. The buffer may not be located at an
366 /// offset aligned to page size, so the offset within the mapped region is returned along with the
367 /// MemoryMapping struct.
mmap_buffer( src: &mut SharedMemory, offset: usize, size: usize, ) -> Result<(MemoryMapping, usize)>368 fn mmap_buffer(
369     src: &mut SharedMemory,
370     offset: usize,
371     size: usize,
372 ) -> Result<(MemoryMapping, usize)> {
373     // If the buffer is not aligned to page size a bigger region needs to be mapped.
374     let aligned_offset = offset & !(base::pagesize() - 1);
375     let offset_from_mapping_start = offset - aligned_offset;
376     let extended_size = size + offset_from_mapping_start;
377 
378     let mmap = MemoryMappingBuilder::new(extended_size)
379         .offset(aligned_offset as u64)
380         .from_shared_memory(src)
381         .build()
382         .map_err(Error::GuestMmapError)?;
383 
384     Ok((mmap, offset_from_mapping_start))
385 }
386