1 // Copyright 2020 The Chromium OS Authors. All rights reserved.
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 super::shm_vios::{VioSClient, VioSStreamParams};
11
12 use crate::virtio::snd::constants::*;
13
14 use audio_streams::shm_streams::{BufferSet, ServerRequest, ShmStream, ShmStreamSource};
15 use audio_streams::{BoxError, SampleFormat, StreamDirection, StreamEffect};
16
17 use base::{error, SharedMemory, SharedMemoryUnix};
18
19 use std::fs::File;
20 use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
21 use std::path::Path;
22 use std::sync::Arc;
23 use std::time::{Duration, Instant};
24
25 use sys_util::{Error as SysError, SharedMemory as SysSharedMemory};
26
27 use super::shm_vios::{Error, Result};
28
29 // This is the error type used in audio_streams::shm_streams. Unfortunately, it's not declared
30 // public there so it needs to be re-declared here. It also prevents the usage of anyhow::Error.
31 type GenericResult<T> = std::result::Result<T, BoxError>;
32
33 /// Adapter that provides the ShmStreamSource trait around the VioS backend.
34 pub struct VioSShmStreamSource {
35 vios_client: Arc<VioSClient>,
36 }
37
38 impl VioSShmStreamSource {
39 /// Creates a new stream source given the path to the audio server's socket.
new<P: AsRef<Path>>(server: P) -> Result<VioSShmStreamSource>40 pub fn new<P: AsRef<Path>>(server: P) -> Result<VioSShmStreamSource> {
41 Ok(Self {
42 vios_client: Arc::new(VioSClient::try_new(server)?),
43 })
44 }
45 }
46
from_sample_format(format: SampleFormat) -> u847 fn from_sample_format(format: SampleFormat) -> u8 {
48 match format {
49 SampleFormat::U8 => VIRTIO_SND_PCM_FMT_U8,
50 SampleFormat::S16LE => VIRTIO_SND_PCM_FMT_U16,
51 SampleFormat::S24LE => VIRTIO_SND_PCM_FMT_U24,
52 SampleFormat::S32LE => VIRTIO_SND_PCM_FMT_U32,
53 }
54 }
55
virtio_frame_rate(frame_rate: u32) -> GenericResult<u8>56 fn virtio_frame_rate(frame_rate: u32) -> GenericResult<u8> {
57 Ok(match frame_rate {
58 5512u32 => VIRTIO_SND_PCM_RATE_5512,
59 8000u32 => VIRTIO_SND_PCM_RATE_8000,
60 11025u32 => VIRTIO_SND_PCM_RATE_11025,
61 16000u32 => VIRTIO_SND_PCM_RATE_16000,
62 22050u32 => VIRTIO_SND_PCM_RATE_22050,
63 32000u32 => VIRTIO_SND_PCM_RATE_32000,
64 44100u32 => VIRTIO_SND_PCM_RATE_44100,
65 48000u32 => VIRTIO_SND_PCM_RATE_48000,
66 64000u32 => VIRTIO_SND_PCM_RATE_64000,
67 88200u32 => VIRTIO_SND_PCM_RATE_88200,
68 96000u32 => VIRTIO_SND_PCM_RATE_96000,
69 176400u32 => VIRTIO_SND_PCM_RATE_176400,
70 192000u32 => VIRTIO_SND_PCM_RATE_192000,
71 384000u32 => VIRTIO_SND_PCM_RATE_384000,
72 _ => {
73 return Err(Box::new(Error::UnsupportedFrameRate(frame_rate)));
74 }
75 })
76 }
77
78 impl ShmStreamSource for VioSShmStreamSource {
79 /// Creates a new stream
80 #[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: &SysSharedMemory, _buffer_offsets: [u64; 2], ) -> GenericResult<Box<dyn ShmStream>>81 fn new_stream(
82 &mut self,
83 direction: StreamDirection,
84 num_channels: usize,
85 format: SampleFormat,
86 frame_rate: u32,
87 buffer_size: usize,
88 _effects: &[StreamEffect],
89 client_shm: &SysSharedMemory,
90 _buffer_offsets: [u64; 2],
91 ) -> GenericResult<Box<dyn ShmStream>> {
92 self.vios_client.ensure_bg_thread_started()?;
93 let virtio_dir = match direction {
94 StreamDirection::Playback => VIRTIO_SND_D_OUTPUT,
95 StreamDirection::Capture => VIRTIO_SND_D_INPUT,
96 };
97 let frame_size = num_channels * format.sample_bytes();
98 let period_bytes = (frame_size * buffer_size) as u32;
99 let stream_id = self
100 .vios_client
101 .get_unused_stream_id(virtio_dir)
102 .ok_or(Box::new(Error::NoStreamsAvailable))?;
103 // Create the stream object before any errors can be returned to guarantee the stream will
104 // be released in all cases
105 let stream_box = VioSndShmStream::new(
106 buffer_size,
107 num_channels,
108 format,
109 frame_rate,
110 stream_id,
111 direction,
112 self.vios_client.clone(),
113 client_shm,
114 );
115 self.vios_client.prepare_stream(stream_id)?;
116 let params = VioSStreamParams {
117 buffer_bytes: 2 * period_bytes,
118 period_bytes,
119 features: 0u32,
120 channels: num_channels as u8,
121 format: from_sample_format(format),
122 rate: virtio_frame_rate(frame_rate)?,
123 };
124 self.vios_client.set_stream_parameters(stream_id, params)?;
125 self.vios_client.start_stream(stream_id)?;
126 stream_box
127 }
128
129 /// Get a list of file descriptors used by the implementation.
130 ///
131 /// Returns any open file descriptors needed by the implementation.
132 /// This list helps users of the ShmStreamSource enter Linux jails without
133 /// closing needed file descriptors.
keep_fds(&self) -> Vec<RawFd>134 fn keep_fds(&self) -> Vec<RawFd> {
135 self.vios_client.keep_fds()
136 }
137 }
138
139 /// Adapter around a VioS stream that implements the ShmStream trait.
140 pub struct VioSndShmStream {
141 num_channels: usize,
142 frame_rate: u32,
143 buffer_size: usize,
144 frame_size: usize,
145 interval: Duration,
146 next_frame: Duration,
147 start_time: Instant,
148 stream_id: u32,
149 direction: StreamDirection,
150 vios_client: Arc<VioSClient>,
151 client_shm: SharedMemory,
152 }
153
154 impl VioSndShmStream {
155 /// 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: &SysSharedMemory, ) -> GenericResult<Box<dyn ShmStream>>156 fn new(
157 buffer_size: usize,
158 num_channels: usize,
159 format: SampleFormat,
160 frame_rate: u32,
161 stream_id: u32,
162 direction: StreamDirection,
163 vios_client: Arc<VioSClient>,
164 client_shm: &SysSharedMemory,
165 ) -> GenericResult<Box<dyn ShmStream>> {
166 let interval = Duration::from_millis(buffer_size as u64 * 1000 / frame_rate as u64);
167
168 let dup_fd = unsafe {
169 // Safe because fcntl doesn't affect memory and client_shm should wrap a known valid
170 // file descriptor.
171 libc::fcntl(client_shm.as_raw_fd(), libc::F_DUPFD_CLOEXEC, 0)
172 };
173 if dup_fd < 0 {
174 return Err(Box::new(Error::DupError(SysError::last())));
175 }
176 let file = unsafe {
177 // safe because we checked the result of libc::fcntl()
178 File::from_raw_fd(dup_fd)
179 };
180 let client_shm_clone =
181 SharedMemory::from_file(file).map_err(|e| Error::BaseMmapError(e))?;
182
183 Ok(Box::new(Self {
184 num_channels,
185 frame_rate,
186 buffer_size,
187 frame_size: format.sample_bytes() * num_channels,
188 interval,
189 next_frame: interval,
190 start_time: Instant::now(),
191 stream_id,
192 direction,
193 vios_client,
194 client_shm: client_shm_clone,
195 }))
196 }
197 }
198
199 impl ShmStream for VioSndShmStream {
frame_size(&self) -> usize200 fn frame_size(&self) -> usize {
201 self.frame_size
202 }
203
num_channels(&self) -> usize204 fn num_channels(&self) -> usize {
205 self.num_channels
206 }
207
frame_rate(&self) -> u32208 fn frame_rate(&self) -> u32 {
209 self.frame_rate
210 }
211
212 /// Waits until the next time a frame should be sent to the server. The server may release the
213 /// previous buffer much sooner than it needs the next one, so this function may sleep to wait
214 /// for the right time.
wait_for_next_action_with_timeout( &mut self, timeout: Duration, ) -> GenericResult<Option<ServerRequest>>215 fn wait_for_next_action_with_timeout(
216 &mut self,
217 timeout: Duration,
218 ) -> GenericResult<Option<ServerRequest>> {
219 let elapsed = self.start_time.elapsed();
220 if elapsed < self.next_frame {
221 if timeout < self.next_frame - elapsed {
222 std::thread::sleep(timeout);
223 return Ok(None);
224 } else {
225 std::thread::sleep(self.next_frame - elapsed);
226 }
227 }
228 self.next_frame += self.interval;
229 Ok(Some(ServerRequest::new(self.buffer_size, self)))
230 }
231 }
232
233 impl BufferSet for VioSndShmStream {
callback(&mut self, offset: usize, frames: usize) -> GenericResult<()>234 fn callback(&mut self, offset: usize, frames: usize) -> GenericResult<()> {
235 match self.direction {
236 StreamDirection::Playback => {
237 self.vios_client.inject_audio_data(
238 self.stream_id,
239 &mut self.client_shm,
240 offset,
241 frames * self.frame_size,
242 )?;
243 }
244 StreamDirection::Capture => {
245 self.vios_client.request_audio_data(
246 self.stream_id,
247 &mut self.client_shm,
248 offset,
249 frames * self.frame_size,
250 )?;
251 }
252 }
253 Ok(())
254 }
255
ignore(&mut self) -> GenericResult<()>256 fn ignore(&mut self) -> GenericResult<()> {
257 Ok(())
258 }
259 }
260
261 impl Drop for VioSndShmStream {
drop(&mut self)262 fn drop(&mut self) {
263 let stream_id = self.stream_id;
264 if let Err(e) = self
265 .vios_client
266 .stop_stream(stream_id)
267 .and_then(|_| self.vios_client.release_stream(stream_id))
268 {
269 error!("Failed to stop and release stream {}: {}", stream_id, e);
270 }
271 }
272 }
273