blob: e5fc83cba90b9e337330ebc3c97b500623364ee0 [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.
//!
//! When implementing an audio playback system, the `StreamSource` trait is implemented.
//! Implementors of this trait allow creation of `PlaybackBufferStream` objects. The
//! `PlaybackBufferStream` provides the actual audio buffers to be filled with audio samples. These
//! buffers are obtained by calling `next_playback_buffer`.
//!
//! Users playing audio fill the provided buffers with audio. When a `PlaybackBuffer` is dropped,
//! the samples written to it are committed to the `PlaybackBufferStream` it came from.
//!
//! ```
//! use audio_streams::{BoxError, SampleFormat, StreamSource, NoopStreamSource};
//! use std::io::Write;
//!
//! const buffer_size: usize = 120;
//! const num_channels: usize = 2;
//!
//! # fn main() -> std::result::Result<(), BoxError> {
//! let mut stream_source = NoopStreamSource::new();
//! let sample_format = SampleFormat::S16LE;
//! let frame_size = num_channels * sample_format.sample_bytes();
//!
//! let (_, mut stream) = stream_source
//! .new_playback_stream(num_channels, sample_format, 48000, buffer_size)?;
//! // Play 10 buffers of DC.
//! let mut buf = Vec::new();
//! buf.resize(buffer_size * frame_size, 0xa5u8);
//! for _ in 0..10 {
//! let mut stream_buffer = stream.next_playback_buffer()?;
//! assert_eq!(stream_buffer.write(&buf)?, buffer_size * frame_size);
//! }
//! # Ok (())
//! # }
//! ```
use std::cmp::min;
use std::error;
use std::fmt::{self, Display};
use std::io::{self, Write};
use std::os::unix::io::RawFd;
use std::result::Result;
use std::str::FromStr;
use std::time::{Duration, Instant};
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum SampleFormat {
U8,
S16LE,
S24LE,
S32LE,
}
impl SampleFormat {
pub fn sample_bytes(self) -> usize {
use SampleFormat::*;
match self {
U8 => 1,
S16LE => 2,
S24LE => 4, // Not a typo, S24_LE samples are stored in 4 byte chunks.
S32LE => 4,
}
}
}
impl Display for SampleFormat {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use SampleFormat::*;
match self {
U8 => write!(f, "Unsigned 8 bit"),
S16LE => write!(f, "Signed 16 bit Little Endian"),
S24LE => write!(f, "Signed 24 bit Little Endian"),
S32LE => write!(f, "Signed 32 bit Little Endian"),
}
}
}
/// Valid directions of an audio stream.
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum StreamDirection {
Playback,
Capture,
}
/// Valid effects for an audio stream.
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum StreamEffect {
NoEffect,
EchoCancellation,
}
pub mod capture;
pub mod shm_streams;
impl Default for StreamEffect {
fn default() -> Self {
StreamEffect::NoEffect
}
}
/// Errors that can pass across threads.
pub type BoxError = Box<dyn error::Error + Send + Sync>;
/// Errors that are possible from a `StreamEffect`.
#[derive(Debug)]
pub enum StreamEffectError {
InvalidEffect,
}
impl error::Error for StreamEffectError {}
impl Display for StreamEffectError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
StreamEffectError::InvalidEffect => write!(f, "Must be in [EchoCancellation, aec]"),
}
}
}
impl FromStr for StreamEffect {
type Err = StreamEffectError;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s {
"EchoCancellation" | "aec" => Ok(StreamEffect::EchoCancellation),
_ => Err(StreamEffectError::InvalidEffect),
}
}
}
/// `StreamSource` creates streams for playback or capture of audio.
pub trait StreamSource: Send {
/// Returns a stream control and buffer generator object. These are separate as the buffer
/// generator might want to be passed to the audio stream.
#[allow(clippy::type_complexity)]
fn new_playback_stream(
&mut self,
num_channels: usize,
format: SampleFormat,
frame_rate: u32,
buffer_size: usize,
) -> Result<(Box<dyn StreamControl>, Box<dyn PlaybackBufferStream>), BoxError>;
/// Returns a stream control and buffer generator object. These are separate as the buffer
/// generator might want to be passed to the audio stream.
/// Default implementation returns `NoopStreamControl` and `NoopCaptureStream`.
#[allow(clippy::type_complexity)]
fn new_capture_stream(
&mut self,
num_channels: usize,
format: SampleFormat,
frame_rate: u32,
buffer_size: usize,
) -> Result<
(
Box<dyn StreamControl>,
Box<dyn capture::CaptureBufferStream>,
),
BoxError,
> {
Ok((
Box::new(NoopStreamControl::new()),
Box::new(capture::NoopCaptureStream::new(
num_channels,
format,
frame_rate,
buffer_size,
)),
))
}
/// Returns any open file descriptors needed by the implementor. The FD list helps users of the
/// StreamSource enter Linux jails making sure not to close needed FDs.
fn keep_fds(&self) -> Option<Vec<RawFd>> {
None
}
}
/// `PlaybackBufferStream` provides `PlaybackBuffer`s to fill with audio samples for playback.
pub trait PlaybackBufferStream: Send {
fn next_playback_buffer(&mut self) -> Result<PlaybackBuffer, BoxError>;
}
/// `StreamControl` provides a way to set the volume and mute states of a stream. `StreamControl`
/// is separate from the stream so it can be owned by a different thread if needed.
pub trait StreamControl: Send + Sync {
fn set_volume(&mut self, _scaler: f64) {}
fn set_mute(&mut self, _mute: bool) {}
}
/// `BufferDrop` is used as a callback mechanism for `PlaybackBuffer` objects. It is meant to be
/// implemented by the audio stream, allowing arbitrary code to be run after a buffer is filled or
/// read by the user.
pub trait BufferDrop {
/// Called when an audio buffer is dropped. `nframes` indicates the number of audio frames that
/// were read or written to the device.
fn trigger(&mut self, nframes: usize);
}
/// Errors that are possible from a `PlaybackBuffer`.
#[derive(Debug)]
pub enum PlaybackBufferError {
InvalidLength,
}
impl error::Error for PlaybackBufferError {}
impl Display for PlaybackBufferError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
PlaybackBufferError::InvalidLength => write!(f, "Invalid buffer length"),
}
}
}
/// `AudioBuffer` is one buffer that holds buffer_size audio frames and its drop function.
/// It is the inner data of `PlaybackBuffer` and `CaptureBuffer`.
struct AudioBuffer<'a> {
buffer: &'a mut [u8],
offset: usize, // Read or Write offset in frames.
frame_size: usize, // Size of a frame in bytes.
drop: &'a mut dyn BufferDrop,
}
/// `PlaybackBuffer` is one buffer that holds buffer_size audio frames. It is used to temporarily
/// allow access to an audio buffer and notifes the owning stream of write completion when dropped.
pub struct PlaybackBuffer<'a> {
buffer: AudioBuffer<'a>,
}
impl<'a> PlaybackBuffer<'a> {
/// Creates a new `PlaybackBuffer` that holds a reference to the backing memory specified in
/// `buffer`.
pub fn new<F>(
frame_size: usize,
buffer: &'a mut [u8],
drop: &'a mut F,
) -> Result<Self, PlaybackBufferError>
where
F: BufferDrop,
{
if buffer.len() % frame_size != 0 {
return Err(PlaybackBufferError::InvalidLength);
}
Ok(PlaybackBuffer {
buffer: AudioBuffer {
buffer,
offset: 0,
frame_size,
drop,
},
})
}
/// Returns the number of audio frames that fit in the buffer.
pub fn frame_capacity(&self) -> usize {
self.buffer.buffer.len() / self.buffer.frame_size
}
/// Writes up to `size` bytes directly to this buffer inside of the given callback function.
pub fn copy_cb<F: FnOnce(&mut [u8])>(&mut self, size: usize, cb: F) {
// only write complete frames.
let len = min(
size / self.buffer.frame_size * self.buffer.frame_size,
self.buffer.buffer.len() - self.buffer.offset,
);
cb(&mut self.buffer.buffer[self.buffer.offset..(self.buffer.offset + len)]);
self.buffer.offset += len;
}
}
impl<'a> Write for PlaybackBuffer<'a> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
// only write complete frames.
let len = buf.len() / self.buffer.frame_size * self.buffer.frame_size;
let written = (&mut self.buffer.buffer[self.buffer.offset..]).write(&buf[..len])?;
self.buffer.offset += written;
Ok(written)
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
impl<'a> Drop for PlaybackBuffer<'a> {
fn drop(&mut self) {
self.buffer
.drop
.trigger(self.buffer.offset / self.buffer.frame_size);
}
}
/// Stream that accepts playback samples but drops them.
pub struct NoopStream {
buffer: Vec<u8>,
frame_size: usize,
interval: Duration,
next_frame: Duration,
start_time: Option<Instant>,
buffer_drop: NoopBufferDrop,
}
/// NoopStream data that is needed from the buffer complete callback.
struct NoopBufferDrop {
which_buffer: bool,
}
impl BufferDrop for NoopBufferDrop {
fn trigger(&mut self, _nwritten: usize) {
// When a buffer completes, switch to the other one.
self.which_buffer ^= true;
}
}
impl NoopStream {
pub fn new(
num_channels: usize,
format: SampleFormat,
frame_rate: u32,
buffer_size: usize,
) -> Self {
let frame_size = format.sample_bytes() * num_channels;
let interval = Duration::from_millis(buffer_size as u64 * 1000 / frame_rate as u64);
NoopStream {
buffer: vec![0; buffer_size * frame_size],
frame_size,
interval,
next_frame: interval,
start_time: None,
buffer_drop: NoopBufferDrop {
which_buffer: false,
},
}
}
}
impl PlaybackBufferStream for NoopStream {
fn next_playback_buffer(&mut self) -> Result<PlaybackBuffer, BoxError> {
if let Some(start_time) = self.start_time {
let elapsed = start_time.elapsed();
if elapsed < self.next_frame {
std::thread::sleep(self.next_frame - elapsed);
}
self.next_frame += self.interval;
} else {
self.start_time = Some(Instant::now());
self.next_frame = self.interval;
}
Ok(PlaybackBuffer::new(
self.frame_size,
&mut self.buffer,
&mut self.buffer_drop,
)?)
}
}
/// No-op control for `NoopStream`s.
#[derive(Default)]
pub struct NoopStreamControl;
impl NoopStreamControl {
pub fn new() -> Self {
NoopStreamControl {}
}
}
impl StreamControl for NoopStreamControl {}
/// Source of `NoopStream` and `NoopStreamControl` objects.
#[derive(Default)]
pub struct NoopStreamSource;
impl NoopStreamSource {
pub fn new() -> Self {
NoopStreamSource {}
}
}
impl StreamSource for NoopStreamSource {
#[allow(clippy::type_complexity)]
fn new_playback_stream(
&mut self,
num_channels: usize,
format: SampleFormat,
frame_rate: u32,
buffer_size: usize,
) -> Result<(Box<dyn StreamControl>, Box<dyn PlaybackBufferStream>), BoxError> {
Ok((
Box::new(NoopStreamControl::new()),
Box::new(NoopStream::new(
num_channels,
format,
frame_rate,
buffer_size,
)),
))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn invalid_buffer_length() {
// Playback buffers can't be created with a size that isn't divisible by the frame size.
let mut pb_buf = [0xa5u8; 480 * 2 * 2 + 1];
let mut buffer_drop = NoopBufferDrop {
which_buffer: false,
};
assert!(PlaybackBuffer::new(2, &mut pb_buf, &mut buffer_drop).is_err());
}
#[test]
fn trigger() {
struct TestDrop {
frame_count: usize,
};
impl BufferDrop for TestDrop {
fn trigger(&mut self, nwritten: usize) {
self.frame_count += nwritten;
}
}
let mut test_drop = TestDrop { frame_count: 0 };
{
const FRAME_SIZE: usize = 4;
let mut buf = [0u8; 480 * FRAME_SIZE];
let mut pb_buf = PlaybackBuffer::new(FRAME_SIZE, &mut buf, &mut test_drop).unwrap();
pb_buf.write_all(&[0xa5u8; 480 * FRAME_SIZE]).unwrap();
}
assert_eq!(test_drop.frame_count, 480);
}
#[test]
fn sixteen_bit_stereo() {
let mut server = NoopStreamSource::new();
let (_, mut stream) = server
.new_playback_stream(2, SampleFormat::S16LE, 48000, 480)
.unwrap();
let mut stream_buffer = stream.next_playback_buffer().unwrap();
assert_eq!(stream_buffer.frame_capacity(), 480);
let pb_buf = [0xa5u8; 480 * 2 * 2];
assert_eq!(stream_buffer.write(&pb_buf).unwrap(), 480 * 2 * 2);
}
#[test]
fn consumption_rate() {
let mut server = NoopStreamSource::new();
let (_, mut stream) = server
.new_playback_stream(2, SampleFormat::S16LE, 48000, 480)
.unwrap();
let start = Instant::now();
{
let mut stream_buffer = stream.next_playback_buffer().unwrap();
let pb_buf = [0xa5u8; 480 * 2 * 2];
assert_eq!(stream_buffer.write(&pb_buf).unwrap(), 480 * 2 * 2);
}
// The second call should block until the first buffer is consumed.
let _stream_buffer = stream.next_playback_buffer().unwrap();
let elapsed = start.elapsed();
assert!(
elapsed > Duration::from_millis(10),
"next_playback_buffer didn't block long enough {}",
elapsed.subsec_millis()
);
}
}