mirror of
https://github.com/haileys/bark
synced 2024-11-28 22:30:24 +00:00
add abstraction layer between alsa and audio api
This commit is contained in:
parent
1443526155
commit
b6a91a9949
8 changed files with 147 additions and 98 deletions
76
bark/src/audio/alsa/config.rs
Normal file
76
bark/src/audio/alsa/config.rs
Normal 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(())
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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()?)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}")]
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue