Path: blob/main/devices/src/virtio/snd/vios_backend/shm_streams.rs
5394 views
// Copyright 2020 The ChromiumOS Authors1// Use of this source code is governed by a BSD-style license that can be2// found in the LICENSE file.34//! Provides an implementation of the audio_streams::shm_streams::ShmStream trait using the VioS5//! client.6//! Given that the VioS server doesn't emit an event when the next buffer is expected, this7//! implementation uses thread::sleep to drive the frame timings.89use std::fs::File;10use std::os::unix::io::FromRawFd;11use std::path::Path;12use std::sync::Arc;13use std::time::Duration;14use std::time::Instant;1516use audio_streams::shm_streams::BufferSet;17use audio_streams::shm_streams::ServerRequest;18use audio_streams::shm_streams::SharedMemory as AudioSharedMemory;19use audio_streams::shm_streams::ShmStream;20use audio_streams::shm_streams::ShmStreamSource;21use audio_streams::BoxError;22use audio_streams::SampleFormat;23use audio_streams::StreamDirection;24use audio_streams::StreamEffect;25use base::error;26use base::linux::SharedMemoryLinux;27use base::Error as SysError;28use base::MemoryMapping;29use base::MemoryMappingBuilder;30use base::RawDescriptor;31use base::SharedMemory;32use base::VolatileMemory;33use sync::Mutex;3435use super::shm_vios::Error;36use super::shm_vios::Result;37use super::shm_vios::VioSClient;38use super::shm_vios::VioSStreamParams;39use crate::virtio::snd::common::*;40use crate::virtio::snd::constants::*;4142// This is the error type used in audio_streams::shm_streams. Unfortunately, it's not declared43// public there so it needs to be re-declared here. It also prevents the usage of anyhow::Error.44type GenericResult<T> = std::result::Result<T, BoxError>;4546enum StreamState {47Available,48Acquired,49Active,50}5152struct StreamDesc {53state: Arc<Mutex<StreamState>>,54direction: StreamDirection,55}5657/// Adapter that provides the ShmStreamSource trait around the VioS backend.58pub struct VioSShmStreamSource {59vios_client: Arc<Mutex<VioSClient>>,60stream_descs: Vec<StreamDesc>,61}6263impl VioSShmStreamSource {64/// Creates a new stream source given the path to the audio server's socket.65pub fn new<P: AsRef<Path>>(server: P) -> Result<VioSShmStreamSource> {66let vios_client = Arc::new(Mutex::new(VioSClient::try_new(server)?));67let mut stream_descs: Vec<StreamDesc> = Vec::new();68let mut idx = 0u32;69while let Some(info) = vios_client.lock().stream_info(idx) {70stream_descs.push(StreamDesc {71state: Arc::new(Mutex::new(StreamState::Active)),72direction: if info.direction == VIRTIO_SND_D_OUTPUT {73StreamDirection::Playback74} else {75StreamDirection::Capture76},77});78idx += 1;79}80Ok(Self {81vios_client,82stream_descs,83})84}85}8687impl VioSShmStreamSource {88fn new_stream_inner(89&mut self,90stream_id: u32,91direction: StreamDirection,92num_channels: usize,93format: SampleFormat,94frame_rate: u32,95buffer_size: usize,96_effects: &[StreamEffect],97client_shm: &dyn AudioSharedMemory<Error = base::Error>,98_buffer_offsets: [u64; 2],99) -> GenericResult<Box<dyn ShmStream>> {100let frame_size = num_channels * format.sample_bytes();101let period_bytes = (frame_size * buffer_size) as u32;102self.vios_client.lock().prepare_stream(stream_id)?;103let params = VioSStreamParams {104buffer_bytes: 2 * period_bytes,105period_bytes,106features: 0u32,107channels: num_channels as u8,108format: from_sample_format(format),109rate: virtio_frame_rate(frame_rate)?,110};111self.vios_client112.lock()113.set_stream_parameters(stream_id, params)?;114self.vios_client.lock().start_stream(stream_id)?;115VioSndShmStream::new(116buffer_size,117num_channels,118format,119frame_rate,120stream_id,121direction,122self.vios_client.clone(),123client_shm,124self.stream_descs[stream_id as usize].state.clone(),125)126}127128fn get_unused_stream_id(&self, direction: StreamDirection) -> Option<u32> {129self.stream_descs130.iter()131.position(|s| match &*s.state.lock() {132StreamState::Available => s.direction == direction,133_ => false,134})135.map(|idx| idx as u32)136}137}138139impl ShmStreamSource<base::Error> for VioSShmStreamSource {140/// Creates a new stream141#[allow(clippy::too_many_arguments)]142fn new_stream(143&mut self,144direction: StreamDirection,145num_channels: usize,146format: SampleFormat,147frame_rate: u32,148buffer_size: usize,149effects: &[StreamEffect],150client_shm: &dyn AudioSharedMemory<Error = base::Error>,151buffer_offsets: [u64; 2],152) -> GenericResult<Box<dyn ShmStream>> {153self.vios_client.lock().start_bg_thread()?;154let stream_id = self155.get_unused_stream_id(direction)156.ok_or(Box::new(Error::NoStreamsAvailable))?;157let stream = self158.new_stream_inner(159stream_id,160direction,161num_channels,162format,163frame_rate,164buffer_size,165effects,166client_shm,167buffer_offsets,168)169.inspect_err(|_e| {170// Attempt to release the stream so that it can be used later. This is a best effort171// attempt, so we ignore any error it may return.172let _ = self.vios_client.lock().release_stream(stream_id);173})?;174*self.stream_descs[stream_id as usize].state.lock() = StreamState::Acquired;175Ok(stream)176}177178/// 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 without182/// closing needed file descriptors.183fn keep_fds(&self) -> Vec<RawDescriptor> {184self.vios_client.lock().keep_rds()185}186}187188/// Adapter around a VioS stream that implements the ShmStream trait.189pub struct VioSndShmStream {190num_channels: usize,191frame_rate: u32,192buffer_size: usize,193frame_size: usize,194interval: Duration,195next_frame: Duration,196start_time: Instant,197stream_id: u32,198direction: StreamDirection,199vios_client: Arc<Mutex<VioSClient>>,200client_shm: SharedMemory,201state: Arc<Mutex<StreamState>>,202}203204impl VioSndShmStream {205/// Creates a new shm stream.206fn new(207buffer_size: usize,208num_channels: usize,209format: SampleFormat,210frame_rate: u32,211stream_id: u32,212direction: StreamDirection,213vios_client: Arc<Mutex<VioSClient>>,214client_shm: &dyn AudioSharedMemory<Error = base::Error>,215state: Arc<Mutex<StreamState>>,216) -> GenericResult<Box<dyn ShmStream>> {217let interval = Duration::from_millis(buffer_size as u64 * 1000 / frame_rate as u64);218219// SAFETY:220// Safe because fcntl doesn't affect memory and client_shm should wrap a known valid221// file descriptor.222let dup_fd = unsafe { libc::fcntl(client_shm.as_raw_fd(), libc::F_DUPFD_CLOEXEC, 0) };223if dup_fd < 0 {224return Err(Box::new(Error::DupError(SysError::last())));225}226// SAFETY:227// safe because we checked the result of libc::fcntl()228let file = unsafe { File::from_raw_fd(dup_fd) };229let client_shm_clone = SharedMemory::from_file(file).map_err(Error::BaseMmapError)?;230231Ok(Box::new(Self {232num_channels,233frame_rate,234buffer_size,235frame_size: format.sample_bytes() * num_channels,236interval,237next_frame: interval,238start_time: Instant::now(),239stream_id,240direction,241vios_client,242client_shm: client_shm_clone,243state,244}))245}246}247248impl ShmStream for VioSndShmStream {249fn frame_size(&self) -> usize {250self.frame_size251}252253fn num_channels(&self) -> usize {254self.num_channels255}256257fn frame_rate(&self) -> u32 {258self.frame_rate259}260261/// Waits until the next time a frame should be sent to the server. The server may release the262/// previous buffer much sooner than it needs the next one, so this function may sleep to wait263/// for the right time.264fn wait_for_next_action_with_timeout(265&mut self,266timeout: Duration,267) -> GenericResult<Option<ServerRequest>> {268let elapsed = self.start_time.elapsed();269if elapsed < self.next_frame {270if timeout < self.next_frame - elapsed {271std::thread::sleep(timeout);272return Ok(None);273} else {274std::thread::sleep(self.next_frame - elapsed);275}276}277self.next_frame += self.interval;278Ok(Some(ServerRequest::new(self.buffer_size, self)))279}280}281282impl BufferSet for VioSndShmStream {283fn callback(&mut self, offset: usize, frames: usize) -> GenericResult<()> {284match self.direction {285StreamDirection::Playback => {286let requested_size = frames * self.frame_size;287let shm_ref = &mut self.client_shm;288let (_, res) = self.vios_client.lock().inject_audio_data::<Result<()>, _>(289self.stream_id,290requested_size,291|slice| {292if requested_size != slice.size() {293error!(294"Buffer size is different than the requested size: {} vs {}",295requested_size,296slice.size()297);298}299let size = std::cmp::min(requested_size, slice.size());300let (src_mmap, mmap_offset) = mmap_buffer(shm_ref, offset, size)?;301let src_slice = src_mmap302.get_slice(mmap_offset, size)303.map_err(Error::VolatileMemoryError)?;304src_slice.copy_to_volatile_slice(slice);305Ok(())306},307)?;308res?;309}310StreamDirection::Capture => {311let requested_size = frames * self.frame_size;312let shm_ref = &mut self.client_shm;313let (_, res) = self314.vios_client315.lock()316.request_audio_data::<Result<()>, _>(317self.stream_id,318requested_size,319|slice| {320if requested_size != slice.size() {321error!(322"Buffer size is different than the requested size: {} vs {}",323requested_size,324slice.size()325);326}327let size = std::cmp::min(requested_size, slice.size());328let (dst_mmap, mmap_offset) = mmap_buffer(shm_ref, offset, size)?;329let dst_slice = dst_mmap330.get_slice(mmap_offset, size)331.map_err(Error::VolatileMemoryError)?;332slice.copy_to_volatile_slice(dst_slice);333Ok(())334},335)?;336res?;337}338}339Ok(())340}341342fn ignore(&mut self) -> GenericResult<()> {343Ok(())344}345}346347impl Drop for VioSndShmStream {348fn drop(&mut self) {349let stream_id = self.stream_id;350{351let vios_client = self.vios_client.lock();352if let Err(e) = vios_client353.stop_stream(stream_id)354.and_then(|_| vios_client.release_stream(stream_id))355{356error!("Failed to stop and release stream {}: {}", stream_id, e);357}358}359*self.state.lock() = StreamState::Available;360}361}362363/// Memory map a shared memory object to access an audio buffer. The buffer may not be located at an364/// offset aligned to page size, so the offset within the mapped region is returned along with the365/// MemoryMapping struct.366fn mmap_buffer(367src: &mut SharedMemory,368offset: usize,369size: usize,370) -> Result<(MemoryMapping, usize)> {371// If the buffer is not aligned to page size a bigger region needs to be mapped.372let aligned_offset = offset & !(base::pagesize() - 1);373let offset_from_mapping_start = offset - aligned_offset;374let extended_size = size + offset_from_mapping_start;375376let mmap = MemoryMappingBuilder::new(extended_size)377.offset(aligned_offset as u64)378.from_shared_memory(src)379.build()380.map_err(Error::GuestMmapError)?;381382Ok((mmap, offset_from_mapping_start))383}384385386