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