add abstraction layer between alsa and audio api

This commit is contained in:
Hailey Somerville 2024-02-14 16:01:27 +11:00
parent 1443526155
commit b6a91a9949
8 changed files with 147 additions and 98 deletions

View file

@ -0,0 +1,76 @@
use alsa::{Direction, PCM, pcm::{HwParams, Format, Access}, ValueOr};
use bark_protocol::time::SampleDuration;
use thiserror::Error;
use crate::audio::config::DeviceOpt;
#[derive(Debug, Error)]
pub enum OpenError {
#[error("alsa error: {0}")]
Alsa(#[from] alsa::Error),
#[error("invalid period size (min = {min}, max = {max})")]
InvalidPeriodSize { min: i64, max: i64 },
#[error("invalid buffer size (min = {min}, max = {max})")]
InvalidBufferSize { min: i64, max: i64 },
}
pub fn open_pcm(opt: &DeviceOpt, direction: Direction)
-> Result<PCM, OpenError>
{
let device_name = opt.device.as_deref().unwrap_or("default");
let pcm = PCM::new(device_name, direction, false)?;
{
let hwp = HwParams::any(&pcm)?;
hwp.set_channels(bark_protocol::CHANNELS.0.into())?;
hwp.set_rate(bark_protocol::SAMPLE_RATE.0, ValueOr::Nearest)?;
hwp.set_format(Format::float())?;
hwp.set_access(Access::RWInterleaved)?;
set_period_size(&hwp, opt.period)?;
set_buffer_size(&hwp, opt.buffer)?;
pcm.hw_params(&hwp)?;
}
{
let hwp = pcm.hw_params_current()?;
let swp = pcm.sw_params_current()?;
swp.set_start_threshold(hwp.get_buffer_size()?)?;
}
let (buffer, period) = pcm.get_params()?;
log::info!("opened ALSA with buffer_size={buffer}, period_size={period}");
Ok(pcm)
}
// period is the size of the discrete chunks of data that are sent to hardware
fn set_period_size(hwp: &HwParams, period: SampleDuration)
-> Result<(), OpenError>
{
let min = hwp.get_period_size_min()?;
let max = hwp.get_period_size_max()?;
let period = period.to_frame_count().try_into().ok()
.filter(|size| { *size >= min && *size <= max })
.ok_or(OpenError::InvalidPeriodSize { min, max })?;
hwp.set_period_size(period, ValueOr::Nearest)?;
Ok(())
}
// period is the size of the discrete chunks of data that are sent to hardware
fn set_buffer_size(hwp: &HwParams, buffer: SampleDuration)
-> Result<(), OpenError>
{
let min = hwp.get_buffer_size_min()?;
let max = hwp.get_buffer_size_max()?;
let buffer = buffer.to_frame_count().try_into().ok()
.filter(|size| *size >= min && *size <= max)
.ok_or(OpenError::InvalidBufferSize { min, max })?;
hwp.set_buffer_size(buffer)?;
Ok(())
}

View file

@ -3,28 +3,22 @@ use alsa::pcm::PCM;
use bark_core::audio::{Frame, self};
use bark_protocol::time::{Timestamp, SampleDuration};
use nix::errno::Errno;
use thiserror::Error;
use crate::audio::config::{self, DeviceOpt, OpenError};
use crate::audio::config::DeviceOpt;
use crate::audio::alsa::config::{self, OpenError};
use crate::time;
pub struct Input {
pcm: PCM,
}
#[derive(Debug, Error)]
pub enum ReadAudioError {
#[error("alsa: {0}")]
Alsa(#[from] alsa::Error),
}
impl Input {
pub fn new(opt: DeviceOpt) -> Result<Self, OpenError> {
let pcm = config::open_pcm(&opt, Direction::Capture)?;
Ok(Input { pcm })
}
pub fn read(&self, mut audio: &mut [Frame]) -> Result<Timestamp, ReadAudioError> {
pub fn read(&self, mut audio: &mut [Frame]) -> Result<Timestamp, alsa::Error> {
let now = Timestamp::from_micros_lossy(time::now());
let timestamp = now.saturating_sub(self.delay()?);
@ -36,7 +30,7 @@ impl Input {
Ok(timestamp)
}
fn read_partial(&self, audio: &mut [Frame]) -> Result<usize, ReadAudioError> {
fn read_partial(&self, audio: &mut [Frame]) -> Result<usize, alsa::Error> {
let io = unsafe {
// the checked versions of this function call
// snd_pcm_hw_params_current which mallocs under the hood

View file

@ -3,27 +3,21 @@ use alsa::pcm::PCM;
use bark_core::audio::{Frame, self};
use bark_protocol::time::SampleDuration;
use nix::errno::Errno;
use thiserror::Error;
use crate::audio::config::{self, DeviceOpt, OpenError};
use crate::audio::config::DeviceOpt;
use crate::audio::alsa::config::{self, OpenError};
pub struct Output {
pcm: PCM,
}
#[derive(Debug, Error)]
pub enum WriteAudioError {
#[error("alsa: {0}")]
Alsa(#[from] alsa::Error),
}
impl Output {
pub fn new(opt: DeviceOpt) -> Result<Self, OpenError> {
let pcm = config::open_pcm(&opt, Direction::Playback)?;
Ok(Output { pcm })
}
pub fn write(&self, mut audio: &[Frame]) -> Result<(), WriteAudioError> {
pub fn write(&self, mut audio: &[Frame]) -> Result<(), alsa::Error> {
while audio.len() > 0 {
let n = self.write_partial(audio)?;
audio = &audio[n..];
@ -32,7 +26,7 @@ impl Output {
Ok(())
}
fn write_partial(&self, audio: &[Frame]) -> Result<usize, WriteAudioError> {
fn write_partial(&self, audio: &[Frame]) -> Result<usize, alsa::Error> {
let io = unsafe {
// the checked versions of this function call
// snd_pcm_hw_params_current which mallocs under the hood

View file

@ -1,6 +1,4 @@
use alsa::{Direction, PCM, pcm::{HwParams, Format, Access}, ValueOr};
use bark_protocol::time::SampleDuration;
use thiserror::Error;
pub const DEFAULT_PERIOD: SampleDuration = SampleDuration::from_frame_count(120);
pub const DEFAULT_BUFFER: SampleDuration = SampleDuration::from_frame_count(360);
@ -10,74 +8,3 @@ pub struct DeviceOpt {
pub period: SampleDuration,
pub buffer: SampleDuration,
}
#[derive(Debug, Error)]
pub enum OpenError {
#[error("alsa error: {0}")]
Alsa(#[from] alsa::Error),
#[error("invalid period size (min = {min}, max = {max})")]
InvalidPeriodSize { min: i64, max: i64 },
#[error("invalid buffer size (min = {min}, max = {max})")]
InvalidBufferSize { min: i64, max: i64 },
}
pub fn open_pcm(opt: &DeviceOpt, direction: Direction)
-> Result<PCM, OpenError>
{
let device_name = opt.device.as_deref().unwrap_or("default");
let pcm = PCM::new(device_name, direction, false)?;
{
let hwp = HwParams::any(&pcm)?;
hwp.set_channels(bark_protocol::CHANNELS.0.into())?;
hwp.set_rate(bark_protocol::SAMPLE_RATE.0, ValueOr::Nearest)?;
hwp.set_format(Format::float())?;
hwp.set_access(Access::RWInterleaved)?;
set_period_size(&hwp, opt.period)?;
set_buffer_size(&hwp, opt.buffer)?;
pcm.hw_params(&hwp)?;
}
{
let hwp = pcm.hw_params_current()?;
let swp = pcm.sw_params_current()?;
swp.set_start_threshold(hwp.get_buffer_size()?)?;
}
let (buffer, period) = pcm.get_params()?;
log::info!("opened ALSA with buffer_size={buffer}, period_size={period}");
Ok(pcm)
}
// period is the size of the discrete chunks of data that are sent to hardware
fn set_period_size(hwp: &HwParams, period: SampleDuration)
-> Result<(), OpenError>
{
let min = hwp.get_period_size_min()?;
let max = hwp.get_period_size_max()?;
let period = period.to_frame_count().try_into().ok()
.filter(|size| { *size >= min && *size <= max })
.ok_or(OpenError::InvalidPeriodSize { min, max })?;
hwp.set_period_size(period, ValueOr::Nearest)?;
Ok(())
}
// period is the size of the discrete chunks of data that are sent to hardware
fn set_buffer_size(hwp: &HwParams, buffer: SampleDuration)
-> Result<(), OpenError>
{
let min = hwp.get_buffer_size_min()?;
let max = hwp.get_buffer_size_max()?;
let buffer = buffer.to_frame_count().try_into().ok()
.filter(|size| *size >= min && *size <= max)
.ok_or(OpenError::InvalidBufferSize { min, max })?;
hwp.set_buffer_size(buffer)?;
Ok(())
}

View file

@ -1,3 +1,61 @@
use bark_core::audio::Frame;
use bark_protocol::time::{SampleDuration, Timestamp};
use thiserror::Error;
use self::config::DeviceOpt;
pub mod alsa {
pub mod config;
pub mod input;
pub mod output;
}
pub mod config;
pub mod input;
pub mod output;
#[derive(Debug, Error)]
#[error(transparent)]
pub enum OpenError {
Alsa(#[from] alsa::config::OpenError),
}
#[derive(Debug, Error)]
#[error(transparent)]
pub enum Error {
Alsa(#[from] ::alsa::Error),
}
pub struct Input {
alsa: alsa::input::Input,
}
impl Input {
pub fn new(opt: DeviceOpt) -> Result<Self, OpenError> {
Ok(Input {
alsa: alsa::input::Input::new(opt)?,
})
}
pub fn read(&self, audio: &mut [Frame]) -> Result<Timestamp, Error> {
Ok(self.alsa.read(audio)?)
}
}
pub struct Output {
alsa: alsa::output::Output,
}
impl Output {
pub fn new(opt: DeviceOpt) -> Result<Self, OpenError> {
Ok(Output {
alsa: alsa::output::Output::new(opt)?,
})
}
pub fn write(&self, audio: &[Frame]) -> Result<(), Error> {
Ok(self.alsa.write(audio)?)
}
pub fn delay(&self) -> Result<SampleDuration, Error> {
Ok(self.alsa.delay()?)
}
}

View file

@ -26,7 +26,7 @@ pub enum RunError {
#[error("opening network socket: {0}")]
Listen(#[from] socket::ListenError),
#[error("opening audio device: {0}")]
OpenAudioDevice(#[from] audio::config::OpenError),
OpenAudioDevice(#[from] audio::OpenError),
#[error("receiving from network: {0}")]
Receive(std::io::Error),
#[error("opening encoder: {0}")]

View file

@ -17,7 +17,7 @@ use bark_protocol::types::stats::receiver::{ReceiverStats, StreamStatus};
use bark_protocol::packet::{Audio, Time, PacketKind, StatsReply};
use crate::audio::config::{DEFAULT_PERIOD, DEFAULT_BUFFER, DeviceOpt};
use crate::audio::output::Output;
use crate::audio::Output;
use crate::socket::{ProtocolSocket, Socket, SocketOpt};
use crate::{time, stats, thread};
use crate::RunError;

View file

@ -14,7 +14,7 @@ use bark_protocol::packet::{self, Audio, StatsReply, PacketKind};
use bark_protocol::types::{TimestampMicros, AudioPacketHeader, SessionId, ReceiverId, TimePhase};
use crate::audio::config::{DeviceOpt, DEFAULT_PERIOD, DEFAULT_BUFFER};
use crate::audio::input::Input;
use crate::audio::Input;
use crate::socket::{Socket, SocketOpt, ProtocolSocket};
use crate::{stats, time, config};
use crate::RunError;