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