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