From b6a91a9949ed52cf3a7f99509e9a32949609198e Mon Sep 17 00:00:00 2001 From: Hailey Somerville Date: Wed, 14 Feb 2024 16:01:27 +1100 Subject: [PATCH] add abstraction layer between alsa and audio api --- bark/src/audio/alsa/config.rs | 76 +++++++++++++++++++++++++++++ bark/src/audio/{ => alsa}/input.rs | 14 ++---- bark/src/audio/{ => alsa}/output.rs | 14 ++---- bark/src/audio/config.rs | 73 --------------------------- bark/src/audio/mod.rs | 62 ++++++++++++++++++++++- bark/src/main.rs | 2 +- bark/src/receive.rs | 2 +- bark/src/stream.rs | 2 +- 8 files changed, 147 insertions(+), 98 deletions(-) create mode 100644 bark/src/audio/alsa/config.rs rename bark/src/audio/{ => alsa}/input.rs (89%) rename bark/src/audio/{ => alsa}/output.rs (82%) diff --git a/bark/src/audio/alsa/config.rs b/bark/src/audio/alsa/config.rs new file mode 100644 index 0000000..70610ed --- /dev/null +++ b/bark/src/audio/alsa/config.rs @@ -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 +{ + 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(()) +} diff --git a/bark/src/audio/input.rs b/bark/src/audio/alsa/input.rs similarity index 89% rename from bark/src/audio/input.rs rename to bark/src/audio/alsa/input.rs index fddcfa9..ddd2a85 100644 --- a/bark/src/audio/input.rs +++ b/bark/src/audio/alsa/input.rs @@ -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 { let pcm = config::open_pcm(&opt, Direction::Capture)?; Ok(Input { pcm }) } - pub fn read(&self, mut audio: &mut [Frame]) -> Result { + pub fn read(&self, mut audio: &mut [Frame]) -> Result { 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 { + fn read_partial(&self, audio: &mut [Frame]) -> Result { let io = unsafe { // the checked versions of this function call // snd_pcm_hw_params_current which mallocs under the hood diff --git a/bark/src/audio/output.rs b/bark/src/audio/alsa/output.rs similarity index 82% rename from bark/src/audio/output.rs rename to bark/src/audio/alsa/output.rs index a06ac74..2bf7c4d 100644 --- a/bark/src/audio/output.rs +++ b/bark/src/audio/alsa/output.rs @@ -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 { 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 { + fn write_partial(&self, audio: &[Frame]) -> Result { let io = unsafe { // the checked versions of this function call // snd_pcm_hw_params_current which mallocs under the hood diff --git a/bark/src/audio/config.rs b/bark/src/audio/config.rs index aa1bd29..48261af 100644 --- a/bark/src/audio/config.rs +++ b/bark/src/audio/config.rs @@ -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 -{ - 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(()) -} diff --git a/bark/src/audio/mod.rs b/bark/src/audio/mod.rs index 1b82683..7a2fe0d 100644 --- a/bark/src/audio/mod.rs +++ b/bark/src/audio/mod.rs @@ -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 { + Ok(Input { + alsa: alsa::input::Input::new(opt)?, + }) + } + + pub fn read(&self, audio: &mut [Frame]) -> Result { + Ok(self.alsa.read(audio)?) + } +} + +pub struct Output { + alsa: alsa::output::Output, +} + +impl Output { + pub fn new(opt: DeviceOpt) -> Result { + 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 { + Ok(self.alsa.delay()?) + } +} diff --git a/bark/src/main.rs b/bark/src/main.rs index 776327d..41bf500 100644 --- a/bark/src/main.rs +++ b/bark/src/main.rs @@ -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}")] diff --git a/bark/src/receive.rs b/bark/src/receive.rs index a9edb3c..4fb54c3 100644 --- a/bark/src/receive.rs +++ b/bark/src/receive.rs @@ -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; diff --git a/bark/src/stream.rs b/bark/src/stream.rs index ef49a2d..4f0f1a4 100644 --- a/bark/src/stream.rs +++ b/bark/src/stream.rs @@ -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;