blob: 402a4a27442daf8c7c70a73b6a2ed32e4e1184e0 [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.
//! Provides an interface for playing and recording audio through CRAS server.
//!
//! `CrasClient` implements `StreamSource` trait and it can create playback or capture
//! stream - `CrasStream` which can be a
//! - `PlaybackBufferStream` for audio playback or
//! - `CaptureBufferStream` for audio capture.
//!
//! # Example of file audio playback
//!
//! `PlaybackBuffer`s to be filled with audio samples are obtained by calling
//! `next_playback_buffer` from `CrasStream`.
//!
//! Users playing audio fill the provided buffers with audio. When a `PlaybackBuffer` is dropped,
//! the samples written to it are committed to the `CrasStream` it came from.
//!
//!
//! ```
//! // An example of playing raw audio data from a given file
//! use std::env;
//! use std::fs::File;
//! use std::io::{Read, Write};
//! use std::thread::{spawn, JoinHandle};
//! type Result<T> = std::result::Result<T, BoxError>;
//!
//! use libcras::{BoxError, CrasClient, CrasClientType};
//! use audio_streams::{SampleFormat, StreamSource};
//!
//! const BUFFER_SIZE: usize = 256;
//! const FRAME_RATE: u32 = 44100;
//! const NUM_CHANNELS: usize = 2;
//! const FORMAT: SampleFormat = SampleFormat::S16LE;
//!
//! # fn main() -> Result<()> {
//! # let args: Vec<String> = env::args().collect();
//! # match args.len() {
//! # 2 => {
//! let mut cras_client = CrasClient::new()?;
//! cras_client.set_client_type(CrasClientType::CRAS_CLIENT_TYPE_TEST);
//! let (_control, mut stream) = cras_client
//! .new_playback_stream(NUM_CHANNELS, FORMAT, FRAME_RATE, BUFFER_SIZE)?;
//!
//! // Plays 1000 * BUFFER_SIZE samples from the given file
//! let mut file = File::open(&args[1])?;
//! let mut local_buffer = [0u8; BUFFER_SIZE * NUM_CHANNELS * 2];
//! for _i in 0..1000 {
//! // Reads data to local buffer
//! let _read_count = file.read(&mut local_buffer)?;
//!
//! // Gets writable buffer from stream and
//! let mut buffer = stream.next_playback_buffer()?;
//! // Writes data to stream buffer
//! let _write_frames = buffer.write(&local_buffer)?;
//! }
//! // Stream and client should gracefully be closed out of this scope
//! # }
//! # _ => {
//! # println!("{} /path/to/playback_file.raw", args[0]);
//! # }
//! # };
//! # Ok(())
//! # }
//! ```
//!
//! # Example of file audio capture
//!
//! `CaptureBuffer`s which contain audio samples are obtained by calling
//! `next_capture_buffer` from `CrasStream`.
//!
//! Users get captured audio samples from the provided buffers. When a `CaptureBuffer` is dropped,
//! the number of read samples will be committed to the `CrasStream` it came from.
//! ```
//! use std::env;
//! use std::fs::File;
//! use std::io::{Read, Write};
//! use std::thread::{spawn, JoinHandle};
//! type Result<T> = std::result::Result<T, BoxError>;
//!
//! use libcras::{BoxError, CrasClient, CrasClientType};
//! use audio_streams::{SampleFormat, StreamSource};
//!
//! const BUFFER_SIZE: usize = 256;
//! const FRAME_RATE: u32 = 44100;
//! const NUM_CHANNELS: usize = 2;
//! const FORMAT: SampleFormat = SampleFormat::S16LE;
//!
//! # fn main() -> Result<()> {
//! # let args: Vec<String> = env::args().collect();
//! # match args.len() {
//! # 2 => {
//! let mut cras_client = CrasClient::new()?;
//! cras_client.set_client_type(CrasClientType::CRAS_CLIENT_TYPE_TEST);
//! let (_control, mut stream) = cras_client
//! .new_capture_stream(NUM_CHANNELS, FORMAT, FRAME_RATE, BUFFER_SIZE)?;
//!
//! // Capture 1000 * BUFFER_SIZE samples to the given file
//! let mut file = File::create(&args[1])?;
//! let mut local_buffer = [0u8; BUFFER_SIZE * NUM_CHANNELS * 2];
//! for _i in 0..1000 {
//!
//! // Gets readable buffer from stream and
//! let mut buffer = stream.next_capture_buffer()?;
//! // Reads data to local buffer
//! let read_count = buffer.read(&mut local_buffer)?;
//! // Writes data to file
//! let _read_frames = file.write(&local_buffer[..read_count])?;
//! }
//! // Stream and client should gracefully be closed out of this scope
//! # }
//! # _ => {
//! # println!("{} /path/to/capture_file.raw", args[0]);
//! # }
//! # };
//! # Ok(())
//! # }
//! ```
use std::io;
use std::mem;
use std::os::unix::{
io::{AsRawFd, RawFd},
net::UnixStream,
};
use std::{error, fmt};
pub use audio_streams::BoxError;
use audio_streams::{
capture::{CaptureBufferStream, NoopCaptureStream},
shm_streams::{NullShmStream, ShmStream, ShmStreamSource},
BufferDrop, NoopStreamControl, PlaybackBufferStream, SampleFormat, StreamControl,
StreamDirection, StreamEffect, StreamSource,
};
use cras_sys::gen::*;
pub use cras_sys::gen::{
CRAS_CLIENT_TYPE as CrasClientType, CRAS_NODE_TYPE as CrasNodeType,
CRAS_STREAM_EFFECT as CrasStreamEffect,
};
pub use cras_sys::{AudioDebugInfo, CrasIodevInfo, CrasIonodeInfo, Error as CrasSysError};
use sys_util::{PollContext, PollToken, SharedMemory};
mod audio_socket;
use crate::audio_socket::AudioSocket;
mod cras_server_socket;
use crate::cras_server_socket::CrasServerSocket;
pub use crate::cras_server_socket::CrasSocketType;
mod cras_shm;
use crate::cras_shm::CrasServerState;
pub mod cras_shm_stream;
use crate::cras_shm_stream::CrasShmStream;
mod cras_stream;
use crate::cras_stream::{CrasCaptureData, CrasPlaybackData, CrasStream, CrasStreamData};
mod cras_client_message;
use crate::cras_client_message::*;
#[derive(Debug)]
pub enum Error {
CrasClientMessageError(cras_client_message::Error),
CrasStreamError(cras_stream::Error),
CrasSysError(cras_sys::Error),
IoError(io::Error),
SysUtilError(sys_util::Error),
MessageTypeError,
UnexpectedExit,
}
impl error::Error for Error {}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::CrasClientMessageError(ref err) => err.fmt(f),
Error::CrasStreamError(ref err) => err.fmt(f),
Error::CrasSysError(ref err) => err.fmt(f),
Error::IoError(ref err) => err.fmt(f),
Error::SysUtilError(ref err) => err.fmt(f),
Error::MessageTypeError => write!(f, "Message type error"),
Error::UnexpectedExit => write!(f, "Unexpected exit"),
}
}
}
type Result<T> = std::result::Result<T, Error>;
impl From<io::Error> for Error {
fn from(io_err: io::Error) -> Self {
Error::IoError(io_err)
}
}
impl From<sys_util::Error> for Error {
fn from(sys_util_err: sys_util::Error) -> Self {
Error::SysUtilError(sys_util_err)
}
}
impl From<cras_stream::Error> for Error {
fn from(err: cras_stream::Error) -> Self {
Error::CrasStreamError(err)
}
}
impl From<cras_client_message::Error> for Error {
fn from(err: cras_client_message::Error) -> Self {
Error::CrasClientMessageError(err)
}
}
/// A CRAS server client, which implements StreamSource and ShmStreamSource.
/// It can create audio streams connecting to CRAS server.
pub struct CrasClient<'a> {
server_socket: CrasServerSocket,
server_state: CrasServerState<'a>,
client_id: u32,
next_stream_id: u32,
cras_capture: bool,
client_type: CRAS_CLIENT_TYPE,
}
impl<'a> CrasClient<'a> {
/// Blocks creating a `CrasClient` with registered `client_id`
///
/// # Results
///
/// * `CrasClient` - A client to interact with CRAS server
///
/// # Errors
///
/// Returns error if error occurs while handling server message or message
/// type is incorrect
pub fn new() -> Result<Self> {
Self::with_type(CrasSocketType::Legacy)
}
/// Tries to create a `CrasClient` with a given `CrasSocketType`.
///
/// # Errors
///
/// Returns error if error occurs while handling server message or message
/// type is incorrect.
pub fn with_type(socket_type: CrasSocketType) -> Result<Self> {
// Create a connection to the server.
let mut server_socket = CrasServerSocket::with_type(socket_type)?;
// Gets client ID and server state fd from server
if let ServerResult::Connected(client_id, server_state_fd) =
CrasClient::wait_for_message(&mut server_socket)?
{
Ok(Self {
server_socket,
server_state: CrasServerState::try_new(server_state_fd)?,
client_id,
next_stream_id: 0,
cras_capture: false,
client_type: CRAS_CLIENT_TYPE::CRAS_CLIENT_TYPE_UNKNOWN,
})
} else {
Err(Error::MessageTypeError)
}
}
/// Enables capturing audio through CRAS server.
pub fn enable_cras_capture(&mut self) {
self.cras_capture = true;
}
/// Set the type of this client to report to CRAS when connecting streams.
pub fn set_client_type(&mut self, client_type: CRAS_CLIENT_TYPE) {
self.client_type = client_type;
}
/// Sets the system volume to `volume`.
///
/// Send a message to the server to request setting the system volume
/// to `volume`. No response is returned from the server.
///
/// # Errors
///
/// If writing the message to the server socket failed.
pub fn set_system_volume(&mut self, volume: u32) -> Result<()> {
let header = cras_server_message {
length: mem::size_of::<cras_set_system_volume>() as u32,
id: CRAS_SERVER_MESSAGE_ID::CRAS_SERVER_SET_SYSTEM_VOLUME,
};
let msg = cras_set_system_volume { header, volume };
self.server_socket.send_server_message_with_fds(&msg, &[])?;
Ok(())
}
/// Sets the system mute status to `mute`.
///
/// Send a message to the server to request setting the system mute
/// to `mute`. No response is returned from the server.
///
/// # Errors
///
/// If writing the message to the server socket failed.
pub fn set_system_mute(&mut self, mute: bool) -> Result<()> {
let header = cras_server_message {
length: mem::size_of::<cras_set_system_mute>() as u32,
id: CRAS_SERVER_MESSAGE_ID::CRAS_SERVER_SET_SYSTEM_MUTE,
};
let msg = cras_set_system_mute {
header,
mute: mute as i32,
};
self.server_socket.send_server_message_with_fds(&msg, &[])?;
Ok(())
}
/// Gets the system volume.
///
/// Read the current value for system volume from the server shared memory.
pub fn get_system_volume(&self) -> u32 {
self.server_state.get_system_volume()
}
/// Gets the system mute.
///
/// Read the current value for system mute from the server shared memory.
pub fn get_system_mute(&self) -> bool {
self.server_state.get_system_mute()
}
/// Gets a list of output devices
///
/// Read a list of the currently attached output devices from the server shared memory.
pub fn output_devices(&self) -> impl Iterator<Item = CrasIodevInfo> {
self.server_state.output_devices()
}
/// Gets a list of input devices
///
/// Read a list of the currently attached input devices from the server shared memory.
pub fn input_devices(&self) -> impl Iterator<Item = CrasIodevInfo> {
self.server_state.input_devices()
}
/// Gets a list of output nodes
///
/// Read a list of the currently attached output nodes from the server shared memory.
pub fn output_nodes(&self) -> impl Iterator<Item = CrasIonodeInfo> {
self.server_state.output_nodes()
}
/// Gets a list of input nodes
///
/// Read a list of the currently attached input nodes from the server shared memory.
pub fn input_nodes(&self) -> impl Iterator<Item = CrasIonodeInfo> {
self.server_state.input_nodes()
}
/// Gets the server's audio debug info.
///
/// Sends a message to the server requesting an update of audio debug info,
/// waits for the response, and then reads the info from the server state.
///
/// # Errors
///
/// * If sending the message to the server failed.
/// * If an unexpected response message is received.
pub fn get_audio_debug_info(&mut self) -> Result<AudioDebugInfo> {
let header = cras_server_message {
length: mem::size_of::<cras_dump_audio_thread>() as u32,
id: CRAS_SERVER_MESSAGE_ID::CRAS_SERVER_DUMP_AUDIO_THREAD,
};
let msg = cras_dump_audio_thread { header };
self.server_socket.send_server_message_with_fds(&msg, &[])?;
match CrasClient::wait_for_message(&mut self.server_socket)? {
ServerResult::DebugInfoReady => Ok(self
.server_state
.get_audio_debug_info()
.map_err(Error::CrasSysError)?),
_ => Err(Error::MessageTypeError),
}
}
// Gets next server_stream_id from client and increment stream_id counter.
fn next_server_stream_id(&mut self) -> u32 {
let res = self.next_stream_id;
self.next_stream_id += 1;
self.server_stream_id(res)
}
// Gets server_stream_id from given stream_id
fn server_stream_id(&self, stream_id: u32) -> u32 {
(self.client_id << 16) | stream_id
}
// Creates general stream with given parameters
fn create_stream<'b, T: BufferDrop + CrasStreamData<'b>>(
&mut self,
device_index: Option<u32>,
block_size: u32,
direction: CRAS_STREAM_DIRECTION,
rate: u32,
channel_num: usize,
format: SampleFormat,
) -> Result<CrasStream<'b, T>> {
let stream_id = self.next_server_stream_id();
// Prepares server message
let audio_format =
cras_audio_format_packed::new(format.into(), rate, channel_num, direction);
let msg_header = cras_server_message {
length: mem::size_of::<cras_connect_message>() as u32,
id: CRAS_SERVER_MESSAGE_ID::CRAS_SERVER_CONNECT_STREAM,
};
let server_cmsg = cras_connect_message {
header: msg_header,
proto_version: CRAS_PROTO_VER,
direction,
stream_id,
stream_type: CRAS_STREAM_TYPE::CRAS_STREAM_TYPE_DEFAULT,
buffer_frames: block_size,
cb_threshold: block_size,
flags: 0,
format: audio_format,
dev_idx: device_index.unwrap_or(CRAS_SPECIAL_DEVICE::NO_DEVICE as u32),
effects: 0,
client_type: self.client_type,
client_shm_size: 0,
buffer_offsets: [0, 0],
};
// Creates AudioSocket pair
let (sock1, sock2) = UnixStream::pair()?;
// Sends `CRAS_SERVER_CONNECT_STREAM` message
let socks = [sock2.as_raw_fd()];
self.server_socket
.send_server_message_with_fds(&server_cmsg, &socks)?;
let audio_socket = AudioSocket::new(sock1);
loop {
let result = CrasClient::wait_for_message(&mut self.server_socket)?;
if let ServerResult::StreamConnected(_stream_id, header_fd, samples_fd) = result {
return CrasStream::try_new(
stream_id,
self.server_socket.try_clone()?,
block_size,
direction,
rate,
channel_num,
format.into(),
audio_socket,
header_fd,
samples_fd,
)
.map_err(Error::CrasStreamError);
}
}
}
/// Creates a new playback stream pinned to the device at `device_index`.
///
/// # Arguments
///
/// * `device_index` - The device to which the stream will be attached.
/// * `num_channels` - The count of audio channels for the stream.
/// * `format` - The format to use for stream audio samples.
/// * `frame_rate` - The sample rate of the stream.
/// * `buffer_size` - The transfer size granularity in frames.
#[allow(clippy::type_complexity)]
pub fn new_pinned_playback_stream(
&mut self,
device_index: u32,
num_channels: usize,
format: SampleFormat,
frame_rate: u32,
buffer_size: usize,
) -> std::result::Result<(Box<dyn StreamControl>, Box<dyn PlaybackBufferStream>), BoxError>
{
Ok((
Box::new(NoopStreamControl::new()),
Box::new(self.create_stream::<CrasPlaybackData>(
Some(device_index),
buffer_size as u32,
CRAS_STREAM_DIRECTION::CRAS_STREAM_OUTPUT,
frame_rate,
num_channels,
format,
)?),
))
}
/// Creates a new capture stream pinned to the device at `device_index`.
///
/// This is useful for, among other things, capturing from a loopback
/// device.
///
/// # Arguments
///
/// * `device_index` - The device to which the stream will be attached.
/// * `num_channels` - The count of audio channels for the stream.
/// * `format` - The format to use for stream audio samples.
/// * `frame_rate` - The sample rate of the stream.
/// * `buffer_size` - The transfer size granularity in frames.
#[allow(clippy::type_complexity)]
pub fn new_pinned_capture_stream(
&mut self,
device_index: u32,
num_channels: usize,
format: SampleFormat,
frame_rate: u32,
buffer_size: usize,
) -> std::result::Result<(Box<dyn StreamControl>, Box<dyn CaptureBufferStream>), BoxError> {
Ok((
Box::new(NoopStreamControl::new()),
Box::new(self.create_stream::<CrasCaptureData>(
Some(device_index),
buffer_size as u32,
CRAS_STREAM_DIRECTION::CRAS_STREAM_INPUT,
frame_rate,
num_channels,
format,
)?),
))
}
// Blocks handling the first server message received from `socket`.
fn wait_for_message(socket: &mut CrasServerSocket) -> Result<ServerResult> {
#[derive(PollToken)]
enum Token {
ServerMsg,
}
let poll_ctx: PollContext<Token> =
PollContext::new().and_then(|pc| pc.add(socket, Token::ServerMsg).and(Ok(pc)))?;
let events = poll_ctx.wait()?;
// Check the first readable message
let tokens: Vec<Token> = events.iter_readable().map(|e| e.token()).collect();
tokens
.get(0)
.ok_or(Error::UnexpectedExit)
.and_then(|ref token| {
match token {
Token::ServerMsg => ServerResult::handle_server_message(socket),
}
.map_err(Into::into)
})
}
/// Returns any open file descriptors needed by CrasClient.
/// This function is shared between StreamSource and ShmStreamSource.
fn keep_fds(&self) -> Vec<RawFd> {
vec![self.server_socket.as_raw_fd()]
}
}
impl<'a> StreamSource for CrasClient<'a> {
#[allow(clippy::type_complexity)]
fn new_playback_stream(
&mut self,
num_channels: usize,
format: SampleFormat,
frame_rate: u32,
buffer_size: usize,
) -> std::result::Result<(Box<dyn StreamControl>, Box<dyn PlaybackBufferStream>), BoxError>
{
Ok((
Box::new(NoopStreamControl::new()),
Box::new(self.create_stream::<CrasPlaybackData>(
None,
buffer_size as u32,
CRAS_STREAM_DIRECTION::CRAS_STREAM_OUTPUT,
frame_rate,
num_channels,
format,
)?),
))
}
#[allow(clippy::type_complexity)]
fn new_capture_stream(
&mut self,
num_channels: usize,
format: SampleFormat,
frame_rate: u32,
buffer_size: usize,
) -> std::result::Result<(Box<dyn StreamControl>, Box<dyn CaptureBufferStream>), BoxError> {
if self.cras_capture {
Ok((
Box::new(NoopStreamControl::new()),
Box::new(self.create_stream::<CrasCaptureData>(
None,
buffer_size as u32,
CRAS_STREAM_DIRECTION::CRAS_STREAM_INPUT,
frame_rate,
num_channels,
format,
)?),
))
} else {
Ok((
Box::new(NoopStreamControl::new()),
Box::new(NoopCaptureStream::new(
num_channels,
format,
frame_rate,
buffer_size,
)),
))
}
}
fn keep_fds(&self) -> Option<Vec<RawFd>> {
Some(CrasClient::keep_fds(self))
}
}
impl<'a> ShmStreamSource for CrasClient<'a> {
fn new_stream(
&mut self,
direction: StreamDirection,
num_channels: usize,
format: SampleFormat,
frame_rate: u32,
buffer_size: usize,
effects: &[StreamEffect],
client_shm: &SharedMemory,
buffer_offsets: [u64; 2],
) -> std::result::Result<Box<dyn ShmStream>, BoxError> {
if direction == StreamDirection::Capture && !self.cras_capture {
return Ok(Box::new(NullShmStream::new(
buffer_size,
num_channels,
format,
frame_rate,
)));
}
let buffer_size = buffer_size as u32;
// Prepares server message
let stream_id = self.next_server_stream_id();
let audio_format = cras_audio_format_packed::new(
format.into(),
frame_rate,
num_channels,
direction.into(),
);
let msg_header = cras_server_message {
length: mem::size_of::<cras_connect_message>() as u32,
id: CRAS_SERVER_MESSAGE_ID::CRAS_SERVER_CONNECT_STREAM,
};
let server_cmsg = cras_connect_message {
header: msg_header,
proto_version: CRAS_PROTO_VER,
direction: direction.into(),
stream_id,
stream_type: CRAS_STREAM_TYPE::CRAS_STREAM_TYPE_DEFAULT,
buffer_frames: buffer_size,
cb_threshold: buffer_size,
flags: 0,
format: audio_format,
dev_idx: CRAS_SPECIAL_DEVICE::NO_DEVICE as u32,
effects: effects.iter().collect::<CrasStreamEffect>().into(),
client_type: self.client_type,
client_shm_size: client_shm.size(),
buffer_offsets,
};
// Creates AudioSocket pair
let (sock1, sock2) = UnixStream::pair()?;
// Sends `CRAS_SERVER_CONNECT_STREAM` message
let fds = [sock2.as_raw_fd(), client_shm.as_raw_fd()];
self.server_socket
.send_server_message_with_fds(&server_cmsg, &fds)?;
loop {
let result = CrasClient::wait_for_message(&mut self.server_socket)?;
if let ServerResult::StreamConnected(_stream_id, header_fd, _samples_fd) = result {
let audio_socket = AudioSocket::new(sock1);
let stream = CrasShmStream::try_new(
stream_id,
self.server_socket.try_clone()?,
audio_socket,
direction,
num_channels,
frame_rate,
format,
header_fd,
client_shm.size() as usize,
)?;
return Ok(Box::new(stream));
}
}
}
fn keep_fds(&self) -> Vec<RawFd> {
CrasClient::keep_fds(self)
}
}