Skip to content

Commit

Permalink
add abstraction layer between alsa and audio api
Browse files Browse the repository at this point in the history
  • Loading branch information
haileys committed Feb 14, 2024
1 parent 1443526 commit b6a91a9
Show file tree
Hide file tree
Showing 8 changed files with 147 additions and 98 deletions.
76 changes: 76 additions & 0 deletions bark/src/audio/alsa/config.rs
Original file line number Diff line number Diff line change
@@ -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(())
}
14 changes: 4 additions & 10 deletions bark/src/audio/input.rs → bark/src/audio/alsa/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()?);

Expand All @@ -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
Expand Down
14 changes: 4 additions & 10 deletions bark/src/audio/output.rs → bark/src/audio/alsa/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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..];
Expand All @@ -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
Expand Down
73 changes: 0 additions & 73 deletions bark/src/audio/config.rs
Original file line number Diff line number Diff line change
@@ -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);
Expand All @@ -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(())
}
62 changes: 60 additions & 2 deletions bark/src/audio/mod.rs
Original file line number Diff line number Diff line change
@@ -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()?)
}
}
2 changes: 1 addition & 1 deletion bark/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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}")]
Expand Down
2 changes: 1 addition & 1 deletion bark/src/receive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion bark/src/stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit b6a91a9

Please sign in to comment.