blob: f72cc07c74ccf165de25e9f7dbfa7e887b8fa897 [file] [log] [blame]
// Copyright 2019 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.
use std::time::Duration;
use std::{error, fmt};
use audio_streams::{
shm_streams::{BufferSet, ServerRequest, ShmStream},
BoxError, SampleFormat, StreamDirection,
};
use cras_sys::gen::CRAS_AUDIO_MESSAGE_ID;
use sys_util::error;
use crate::audio_socket::{AudioMessage, AudioSocket};
use crate::cras_server_socket::CrasServerSocket;
use crate::cras_shm::{self, CrasAudioHeader, CrasAudioShmHeaderFd};
#[derive(Debug)]
pub enum Error {
MessageTypeError,
CaptureBufferTooSmall,
}
impl error::Error for Error {}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::MessageTypeError => write!(f, "Message type error"),
Error::CaptureBufferTooSmall => write!(
f,
"Capture buffer too small, must have size at least 'used_size'."
),
}
}
}
/// An object that handles interactions with CRAS for a shm stream.
/// The object implements `ShmStream` and so can be used to wait for
/// `ServerRequest` and `BufferComplete` messages.
pub struct CrasShmStream<'a> {
stream_id: u32,
server_socket: CrasServerSocket,
audio_socket: AudioSocket,
direction: StreamDirection,
header: CrasAudioHeader<'a>,
frame_size: usize,
num_channels: usize,
frame_rate: u32,
// The index of the next buffer within SHM to set the buffer offset for.
next_buffer_idx: usize,
}
impl<'a> CrasShmStream<'a> {
/// Attempt to creates a CrasShmStream with the given arguments.
///
/// # Arguments
///
/// * `stream_id` - The server's ID for the stream.
/// * `server_socket` - The socket that is connected to the server.
/// * `audio_socket` - The socket for audio request and audio available messages.
/// * `direction` - The direction of the stream, `Playback` or `Capture`.
/// * `num_channels` - The number of audio channels for the stream.
/// * `format` - The format to use for the stream's samples.
/// * `header_fd` - The file descriptor for the audio header shm area.
/// * `samples_len` - The size of the audio samples shm area.
///
/// # Returns
///
/// `CrasShmStream` - CRAS client stream.
///
/// # Errors
///
/// * If `header_fd` could not be successfully mmapped.
#[allow(clippy::too_many_arguments)]
pub fn try_new(
stream_id: u32,
server_socket: CrasServerSocket,
audio_socket: AudioSocket,
direction: StreamDirection,
num_channels: usize,
frame_rate: u32,
format: SampleFormat,
header_fd: CrasAudioShmHeaderFd,
samples_len: usize,
) -> Result<Self, BoxError> {
let header = cras_shm::create_header(header_fd, samples_len)?;
Ok(Self {
stream_id,
server_socket,
audio_socket,
direction,
header,
frame_size: format.sample_bytes() * num_channels,
num_channels,
frame_rate,
// We have either sent zero or two offsets to the server, so we will
// need to update index 0 next.
next_buffer_idx: 0,
})
}
}
impl<'a> Drop for CrasShmStream<'a> {
/// Send the disconnect stream message and log an error if sending fails.
fn drop(&mut self) {
if let Err(e) = self.server_socket.disconnect_stream(self.stream_id) {
error!("CrasShmStream::drop error: {}", e);
}
}
}
impl<'a> ShmStream for CrasShmStream<'a> {
fn frame_size(&self) -> usize {
self.frame_size
}
fn num_channels(&self) -> usize {
self.num_channels
}
fn frame_rate(&self) -> u32 {
self.frame_rate
}
fn wait_for_next_action_with_timeout(
&mut self,
timeout: Duration,
) -> Result<Option<ServerRequest>, BoxError> {
let expected_id = match self.direction {
StreamDirection::Playback => CRAS_AUDIO_MESSAGE_ID::AUDIO_MESSAGE_REQUEST_DATA,
StreamDirection::Capture => CRAS_AUDIO_MESSAGE_ID::AUDIO_MESSAGE_DATA_READY,
};
match self
.audio_socket
.read_audio_message_with_timeout(Some(timeout))?
{
Some(AudioMessage::Success { id, frames }) if id == expected_id => {
Ok(Some(ServerRequest::new(frames as usize, self)))
}
None => Ok(None),
_ => Err(Box::new(Error::MessageTypeError)),
}
}
}
impl BufferSet for CrasShmStream<'_> {
fn callback(&mut self, offset: usize, frames: usize) -> Result<(), BoxError> {
self.header
.set_buffer_offset(self.next_buffer_idx, offset)?;
self.next_buffer_idx ^= 1;
let frames = frames as u32;
match self.direction {
StreamDirection::Playback => {
self.header.commit_written_frames(frames)?;
// Notify CRAS that we've made playback data available.
self.audio_socket.data_ready(frames)?
}
StreamDirection::Capture => {
let used_size = self.header.get_used_size();
// Because CRAS doesn't know how long our buffer in shm is, we
// must make sure that there are always at least buffer_size
// frames available so that it doesn't write outside the buffer.
if frames < (used_size / self.frame_size) as u32 {
return Err(Box::new(Error::CaptureBufferTooSmall));
}
self.header.commit_read_frames(frames)?;
self.audio_socket.capture_ready(frames)?;
}
}
Ok(())
}
fn ignore(&mut self) -> Result<(), BoxError> {
// We send an empty buffer for an ignored playback request since the
// server will not read from a 0-length buffer. We don't do anything for
// an ignored capture request, since we don't have a way to communicate
// buffer length to the server, and we don't want the server writing
// data to offsets within the SHM area that aren't audio buffers.
if self.direction == StreamDirection::Playback {
self.callback(0, 0)?;
}
Ok(())
}
}