blob: a4f683399051d30c26cda8ebe50e58e3b9d84158 [file] [log] [blame]
// Copyright 2020 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! Provides an implementation of the audio_streams::shm_streams::ShmStream trait using the VioS
//! client.
//! Given that the VioS server doesn't emit an event when the next buffer is expected, this
//! implementation uses thread::sleep to drive the frame timings.
use super::shm_vios::{VioSClient, VioSStreamParams};
use crate::virtio::snd::common::*;
use crate::virtio::snd::constants::*;
use audio_streams::shm_streams::{BufferSet, ServerRequest, ShmStream, ShmStreamSource};
use audio_streams::{BoxError, SampleFormat, StreamDirection, StreamEffect};
use base::{error, MemoryMapping, MemoryMappingBuilder, SharedMemory, SharedMemoryUnix};
use data_model::VolatileMemory;
use std::fs::File;
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
use std::path::Path;
use std::sync::Arc;
use std::time::{Duration, Instant};
use sys_util::{Error as SysError, SharedMemory as SysSharedMemory};
use super::shm_vios::{Error, Result};
// This is the error type used in audio_streams::shm_streams. Unfortunately, it's not declared
// public there so it needs to be re-declared here. It also prevents the usage of anyhow::Error.
type GenericResult<T> = std::result::Result<T, BoxError>;
/// Adapter that provides the ShmStreamSource trait around the VioS backend.
pub struct VioSShmStreamSource {
vios_client: Arc<VioSClient>,
}
impl VioSShmStreamSource {
/// Creates a new stream source given the path to the audio server's socket.
pub fn new<P: AsRef<Path>>(server: P) -> Result<VioSShmStreamSource> {
Ok(Self {
vios_client: Arc::new(VioSClient::try_new(server)?),
})
}
}
impl VioSShmStreamSource {
fn 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: &SysSharedMemory,
_buffer_offsets: [u64; 2],
) -> GenericResult<Box<dyn ShmStream>> {
let frame_size = num_channels * format.sample_bytes();
let period_bytes = (frame_size * buffer_size) as u32;
self.vios_client.prepare_stream(stream_id)?;
let params = VioSStreamParams {
buffer_bytes: 2 * period_bytes,
period_bytes,
features: 0u32,
channels: num_channels as u8,
format: from_sample_format(format),
rate: virtio_frame_rate(frame_rate)?,
};
self.vios_client.set_stream_parameters(stream_id, params)?;
self.vios_client.start_stream(stream_id)?;
VioSndShmStream::new(
buffer_size,
num_channels,
format,
frame_rate,
stream_id,
direction,
self.vios_client.clone(),
client_shm,
)
}
}
impl ShmStreamSource for VioSShmStreamSource {
/// Creates a new stream
#[allow(clippy::too_many_arguments)]
fn 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>> {
self.vios_client.start_bg_thread()?;
let virtio_dir = match direction {
StreamDirection::Playback => VIRTIO_SND_D_OUTPUT,
StreamDirection::Capture => VIRTIO_SND_D_INPUT,
};
let stream_id = self
.vios_client
.get_unused_stream_id(virtio_dir)
.ok_or(Box::new(Error::NoStreamsAvailable))?;
self.new_stream_inner(
stream_id,
direction,
num_channels,
format,
frame_rate,
buffer_size,
effects,
client_shm,
buffer_offsets,
)
.map_err(|e| {
// Attempt to release the stream so that it can be used later. This is a best effort
// attempt, so we ignore any error it may return.
let _ = self.vios_client.release_stream(stream_id);
e
})
}
/// Get a list of file descriptors used by the implementation.
///
/// Returns any open file descriptors needed by the implementation.
/// This list helps users of the ShmStreamSource enter Linux jails without
/// closing needed file descriptors.
fn keep_fds(&self) -> Vec<RawFd> {
self.vios_client.keep_fds()
}
}
/// Adapter around a VioS stream that implements the ShmStream trait.
pub struct VioSndShmStream {
num_channels: usize,
frame_rate: u32,
buffer_size: usize,
frame_size: usize,
interval: Duration,
next_frame: Duration,
start_time: Instant,
stream_id: u32,
direction: StreamDirection,
vios_client: Arc<VioSClient>,
client_shm: SharedMemory,
}
impl VioSndShmStream {
/// Creates a new shm stream.
fn 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>> {
let interval = Duration::from_millis(buffer_size as u64 * 1000 / frame_rate as u64);
let dup_fd = unsafe {
// Safe because fcntl doesn't affect memory and client_shm should wrap a known valid
// file descriptor.
libc::fcntl(client_shm.as_raw_fd(), libc::F_DUPFD_CLOEXEC, 0)
};
if dup_fd < 0 {
return Err(Box::new(Error::DupError(SysError::last())));
}
let file = unsafe {
// safe because we checked the result of libc::fcntl()
File::from_raw_fd(dup_fd)
};
let client_shm_clone = SharedMemory::from_file(file).map_err(Error::BaseMmapError)?;
Ok(Box::new(Self {
num_channels,
frame_rate,
buffer_size,
frame_size: format.sample_bytes() * num_channels,
interval,
next_frame: interval,
start_time: Instant::now(),
stream_id,
direction,
vios_client,
client_shm: client_shm_clone,
}))
}
}
impl ShmStream for VioSndShmStream {
fn frame_size(&self) -> usize {
self.frame_size
}
fn num_channels(&self) -> usize {
self.num_channels
}
fn frame_rate(&self) -> u32 {
self.frame_rate
}
/// Waits until the next time a frame should be sent to the server. The server may release the
/// previous buffer much sooner than it needs the next one, so this function may sleep to wait
/// for the right time.
fn wait_for_next_action_with_timeout(
&mut self,
timeout: Duration,
) -> GenericResult<Option<ServerRequest>> {
let elapsed = self.start_time.elapsed();
if elapsed < self.next_frame {
if timeout < self.next_frame - elapsed {
std::thread::sleep(timeout);
return Ok(None);
} else {
std::thread::sleep(self.next_frame - elapsed);
}
}
self.next_frame += self.interval;
Ok(Some(ServerRequest::new(self.buffer_size, self)))
}
}
impl BufferSet for VioSndShmStream {
fn callback(&mut self, offset: usize, frames: usize) -> GenericResult<()> {
match self.direction {
StreamDirection::Playback => {
let requested_size = frames * self.frame_size;
let shm_ref = &mut self.client_shm;
let (_, res) = self.vios_client.inject_audio_data::<Result<()>, _>(
self.stream_id,
requested_size,
|slice| {
if requested_size != slice.size() {
error!(
"Buffer size is different than the requested size: {} vs {}",
requested_size,
slice.size()
);
}
let size = std::cmp::min(requested_size, slice.size());
let (src_mmap, mmap_offset) = mmap_buffer(shm_ref, offset, size)?;
let src_slice = src_mmap
.get_slice(mmap_offset, size)
.map_err(Error::VolatileMemoryError)?;
src_slice.copy_to_volatile_slice(slice);
Ok(())
},
)?;
res?;
}
StreamDirection::Capture => {
let requested_size = frames * self.frame_size;
let shm_ref = &mut self.client_shm;
let (_, res) = self.vios_client.request_audio_data::<Result<()>, _>(
self.stream_id,
requested_size,
|slice| {
if requested_size != slice.size() {
error!(
"Buffer size is different than the requested size: {} vs {}",
requested_size,
slice.size()
);
}
let size = std::cmp::min(requested_size, slice.size());
let (dst_mmap, mmap_offset) = mmap_buffer(shm_ref, offset, size)?;
let dst_slice = dst_mmap
.get_slice(mmap_offset, size)
.map_err(Error::VolatileMemoryError)?;
slice.copy_to_volatile_slice(dst_slice);
Ok(())
},
)?;
res?;
}
}
Ok(())
}
fn ignore(&mut self) -> GenericResult<()> {
Ok(())
}
}
impl Drop for VioSndShmStream {
fn drop(&mut self) {
let stream_id = self.stream_id;
if let Err(e) = self
.vios_client
.stop_stream(stream_id)
.and_then(|_| self.vios_client.release_stream(stream_id))
{
error!("Failed to stop and release stream {}: {}", stream_id, e);
}
}
}
/// Memory map a shared memory object to access an audio buffer. The buffer may not be located at an
/// offset aligned to page size, so the offset within the mapped region is returned along with the
/// MemoryMapping struct.
fn mmap_buffer(
src: &mut SharedMemory,
offset: usize,
size: usize,
) -> Result<(MemoryMapping, usize)> {
// If the buffer is not aligned to page size a bigger region needs to be mapped.
let aligned_offset = offset & !(base::pagesize() - 1);
let offset_from_mapping_start = offset - aligned_offset;
let extended_size = size + offset_from_mapping_start;
let mmap = MemoryMappingBuilder::new(extended_size)
.offset(aligned_offset as u64)
.from_shared_memory(src)
.build()
.map_err(Error::GuestMmapError)?;
Ok((mmap, offset_from_mapping_start))
}