xref: /aosp_15_r20/external/crosvm/devices/src/virtio/snd/vios_backend/shm_streams.rs (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
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             .inspect_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             })?;
175         *self.stream_descs[stream_id as usize].state.lock() = StreamState::Acquired;
176         Ok(stream)
177     }
178 
179     /// Get a list of file descriptors used by the implementation.
180     ///
181     /// Returns any open file descriptors needed by the implementation.
182     /// This list helps users of the ShmStreamSource enter Linux jails without
183     /// closing needed file descriptors.
keep_fds(&self) -> Vec<RawDescriptor>184     fn keep_fds(&self) -> Vec<RawDescriptor> {
185         self.vios_client.lock().keep_rds()
186     }
187 }
188 
189 /// Adapter around a VioS stream that implements the ShmStream trait.
190 pub struct VioSndShmStream {
191     num_channels: usize,
192     frame_rate: u32,
193     buffer_size: usize,
194     frame_size: usize,
195     interval: Duration,
196     next_frame: Duration,
197     start_time: Instant,
198     stream_id: u32,
199     direction: StreamDirection,
200     vios_client: Arc<Mutex<VioSClient>>,
201     client_shm: SharedMemory,
202     state: Arc<Mutex<StreamState>>,
203 }
204 
205 impl VioSndShmStream {
206     /// 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>>207     fn new(
208         buffer_size: usize,
209         num_channels: usize,
210         format: SampleFormat,
211         frame_rate: u32,
212         stream_id: u32,
213         direction: StreamDirection,
214         vios_client: Arc<Mutex<VioSClient>>,
215         client_shm: &dyn AudioSharedMemory<Error = base::Error>,
216         state: Arc<Mutex<StreamState>>,
217     ) -> GenericResult<Box<dyn ShmStream>> {
218         let interval = Duration::from_millis(buffer_size as u64 * 1000 / frame_rate as u64);
219 
220         // SAFETY:
221         // Safe because fcntl doesn't affect memory and client_shm should wrap a known valid
222         // file descriptor.
223         let dup_fd = unsafe { libc::fcntl(client_shm.as_raw_fd(), libc::F_DUPFD_CLOEXEC, 0) };
224         if dup_fd < 0 {
225             return Err(Box::new(Error::DupError(SysError::last())));
226         }
227         // SAFETY:
228         // safe because we checked the result of libc::fcntl()
229         let file = unsafe { File::from_raw_fd(dup_fd) };
230         let client_shm_clone = SharedMemory::from_file(file).map_err(Error::BaseMmapError)?;
231 
232         Ok(Box::new(Self {
233             num_channels,
234             frame_rate,
235             buffer_size,
236             frame_size: format.sample_bytes() * num_channels,
237             interval,
238             next_frame: interval,
239             start_time: Instant::now(),
240             stream_id,
241             direction,
242             vios_client,
243             client_shm: client_shm_clone,
244             state,
245         }))
246     }
247 }
248 
249 impl ShmStream for VioSndShmStream {
frame_size(&self) -> usize250     fn frame_size(&self) -> usize {
251         self.frame_size
252     }
253 
num_channels(&self) -> usize254     fn num_channels(&self) -> usize {
255         self.num_channels
256     }
257 
frame_rate(&self) -> u32258     fn frame_rate(&self) -> u32 {
259         self.frame_rate
260     }
261 
262     /// Waits until the next time a frame should be sent to the server. The server may release the
263     /// previous buffer much sooner than it needs the next one, so this function may sleep to wait
264     /// for the right time.
wait_for_next_action_with_timeout( &mut self, timeout: Duration, ) -> GenericResult<Option<ServerRequest>>265     fn wait_for_next_action_with_timeout(
266         &mut self,
267         timeout: Duration,
268     ) -> GenericResult<Option<ServerRequest>> {
269         let elapsed = self.start_time.elapsed();
270         if elapsed < self.next_frame {
271             if timeout < self.next_frame - elapsed {
272                 std::thread::sleep(timeout);
273                 return Ok(None);
274             } else {
275                 std::thread::sleep(self.next_frame - elapsed);
276             }
277         }
278         self.next_frame += self.interval;
279         Ok(Some(ServerRequest::new(self.buffer_size, self)))
280     }
281 }
282 
283 impl BufferSet for VioSndShmStream {
callback(&mut self, offset: usize, frames: usize) -> GenericResult<()>284     fn callback(&mut self, offset: usize, frames: usize) -> GenericResult<()> {
285         match self.direction {
286             StreamDirection::Playback => {
287                 let requested_size = frames * self.frame_size;
288                 let shm_ref = &mut self.client_shm;
289                 let (_, res) = self.vios_client.lock().inject_audio_data::<Result<()>, _>(
290                     self.stream_id,
291                     requested_size,
292                     |slice| {
293                         if requested_size != slice.size() {
294                             error!(
295                                 "Buffer size is different than the requested size: {} vs {}",
296                                 requested_size,
297                                 slice.size()
298                             );
299                         }
300                         let size = std::cmp::min(requested_size, slice.size());
301                         let (src_mmap, mmap_offset) = mmap_buffer(shm_ref, offset, size)?;
302                         let src_slice = src_mmap
303                             .get_slice(mmap_offset, size)
304                             .map_err(Error::VolatileMemoryError)?;
305                         src_slice.copy_to_volatile_slice(slice);
306                         Ok(())
307                     },
308                 )?;
309                 res?;
310             }
311             StreamDirection::Capture => {
312                 let requested_size = frames * self.frame_size;
313                 let shm_ref = &mut self.client_shm;
314                 let (_, res) = self
315                     .vios_client
316                     .lock()
317                     .request_audio_data::<Result<()>, _>(
318                         self.stream_id,
319                         requested_size,
320                         |slice| {
321                             if requested_size != slice.size() {
322                                 error!(
323                                     "Buffer size is different than the requested size: {} vs {}",
324                                     requested_size,
325                                     slice.size()
326                                 );
327                             }
328                             let size = std::cmp::min(requested_size, slice.size());
329                             let (dst_mmap, mmap_offset) = mmap_buffer(shm_ref, offset, size)?;
330                             let dst_slice = dst_mmap
331                                 .get_slice(mmap_offset, size)
332                                 .map_err(Error::VolatileMemoryError)?;
333                             slice.copy_to_volatile_slice(dst_slice);
334                             Ok(())
335                         },
336                     )?;
337                 res?;
338             }
339         }
340         Ok(())
341     }
342 
ignore(&mut self) -> GenericResult<()>343     fn ignore(&mut self) -> GenericResult<()> {
344         Ok(())
345     }
346 }
347 
348 impl Drop for VioSndShmStream {
drop(&mut self)349     fn drop(&mut self) {
350         let stream_id = self.stream_id;
351         {
352             let vios_client = self.vios_client.lock();
353             if let Err(e) = vios_client
354                 .stop_stream(stream_id)
355                 .and_then(|_| vios_client.release_stream(stream_id))
356             {
357                 error!("Failed to stop and release stream {}: {}", stream_id, e);
358             }
359         }
360         *self.state.lock() = StreamState::Available;
361     }
362 }
363 
364 /// Memory map a shared memory object to access an audio buffer. The buffer may not be located at an
365 /// offset aligned to page size, so the offset within the mapped region is returned along with the
366 /// MemoryMapping struct.
mmap_buffer( src: &mut SharedMemory, offset: usize, size: usize, ) -> Result<(MemoryMapping, usize)>367 fn mmap_buffer(
368     src: &mut SharedMemory,
369     offset: usize,
370     size: usize,
371 ) -> Result<(MemoryMapping, usize)> {
372     // If the buffer is not aligned to page size a bigger region needs to be mapped.
373     let aligned_offset = offset & !(base::pagesize() - 1);
374     let offset_from_mapping_start = offset - aligned_offset;
375     let extended_size = size + offset_from_mapping_start;
376 
377     let mmap = MemoryMappingBuilder::new(extended_size)
378         .offset(aligned_offset as u64)
379         .from_shared_memory(src)
380         .build()
381         .map_err(Error::GuestMmapError)?;
382 
383     Ok((mmap, offset_from_mapping_start))
384 }
385