devices/virtio/snd/vios_backend/
shm_streams.rs

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
10use std::fs::File;
11use std::os::unix::io::FromRawFd;
12use std::path::Path;
13use std::sync::Arc;
14use std::time::Duration;
15use std::time::Instant;
16
17use audio_streams::shm_streams::BufferSet;
18use audio_streams::shm_streams::ServerRequest;
19use audio_streams::shm_streams::SharedMemory as AudioSharedMemory;
20use audio_streams::shm_streams::ShmStream;
21use audio_streams::shm_streams::ShmStreamSource;
22use audio_streams::BoxError;
23use audio_streams::SampleFormat;
24use audio_streams::StreamDirection;
25use audio_streams::StreamEffect;
26use base::error;
27use base::linux::SharedMemoryLinux;
28use base::Error as SysError;
29use base::MemoryMapping;
30use base::MemoryMappingBuilder;
31use base::RawDescriptor;
32use base::SharedMemory;
33use base::VolatileMemory;
34use sync::Mutex;
35
36use super::shm_vios::Error;
37use super::shm_vios::Result;
38use super::shm_vios::VioSClient;
39use super::shm_vios::VioSStreamParams;
40use crate::virtio::snd::common::*;
41use 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.
45type GenericResult<T> = std::result::Result<T, BoxError>;
46
47enum StreamState {
48    Available,
49    Acquired,
50    Active,
51}
52
53struct StreamDesc {
54    state: Arc<Mutex<StreamState>>,
55    direction: StreamDirection,
56}
57
58/// Adapter that provides the ShmStreamSource trait around the VioS backend.
59pub struct VioSShmStreamSource {
60    vios_client: Arc<Mutex<VioSClient>>,
61    stream_descs: Vec<StreamDesc>,
62}
63
64impl VioSShmStreamSource {
65    /// Creates a new stream source given the path to the audio server's socket.
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
88impl VioSShmStreamSource {
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
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
140impl ShmStreamSource<base::Error> for VioSShmStreamSource {
141    /// Creates a new stream
142    #[allow(clippy::too_many_arguments)]
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.
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.
190pub 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
205impl VioSndShmStream {
206    /// Creates a new shm stream.
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
249impl ShmStream for VioSndShmStream {
250    fn frame_size(&self) -> usize {
251        self.frame_size
252    }
253
254    fn num_channels(&self) -> usize {
255        self.num_channels
256    }
257
258    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.
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
283impl BufferSet for VioSndShmStream {
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
343    fn ignore(&mut self) -> GenericResult<()> {
344        Ok(())
345    }
346}
347
348impl Drop for VioSndShmStream {
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.
367fn 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}