use audio_core_bsd::AudioFrame;
use crate::device::{DeviceInfo, StreamParams};
use crate::Result;
pub trait OutputSink: Send {
fn write(&mut self, frame: &AudioFrame) -> Result<()>;
fn flush(&mut self) -> Result<()> {
Ok(())
}
}
pub trait InputSource: Send {
fn read(&mut self) -> Result<AudioFrame>;
fn latency_ms(&self) -> Result<f64> {
Err(crate::IoError::Backend("latency unavailable".into()))
}
}
pub trait AudioBackend: Send + Sync {
fn enumerate_devices(&self) -> Vec<DeviceInfo>;
fn default_output(&self) -> Option<DeviceInfo>;
fn default_input(&self) -> Option<DeviceInfo>;
fn open_output(&self, dev: &str, params: StreamParams) -> Result<Box<dyn OutputSink>>;
fn open_input(&self, dev: &str, params: StreamParams) -> Result<Box<dyn InputSource>>;
}
#[cfg(test)]
mod tests {
use super::*;
use crate::device::{DeviceDirection, DeviceInfo};
use audio_core_bsd::AudioFrame;
struct StubBackend;
impl AudioBackend for StubBackend {
fn enumerate_devices(&self) -> Vec<DeviceInfo> {
vec![DeviceInfo::new(
"stub",
DeviceDirection::Output,
2,
vec![48_000],
true,
)]
}
fn default_output(&self) -> Option<DeviceInfo> {
self.enumerate_devices().into_iter().next()
}
fn default_input(&self) -> Option<DeviceInfo> {
None
}
fn open_output(&self, dev: &str, params: StreamParams) -> Result<Box<dyn OutputSink>> {
params.validate()?;
if dev != "stub" {
return Err(crate::IoError::DeviceNotFound(dev.into()));
}
Ok(Box::new(StubSink))
}
fn open_input(&self, _dev: &str, _params: StreamParams) -> Result<Box<dyn InputSource>> {
Err(crate::IoError::Backend("no input on stub".into()))
}
}
struct StubSink;
impl OutputSink for StubSink {
fn write(&mut self, _frame: &AudioFrame) -> Result<()> {
Ok(())
}
}
#[test]
fn stub_backend_enumerates_one_output_device() {
let b = StubBackend;
let devs = b.enumerate_devices();
assert_eq!(devs.len(), 1);
assert_eq!(devs[0].name, "stub");
}
#[test]
fn dyn_backend_object_compiles_and_works() {
let b: Box<dyn AudioBackend> = Box::new(StubBackend);
assert!(b.default_output().is_some());
assert!(b.default_input().is_none());
}
#[test]
fn open_output_validates_params() {
let b = StubBackend;
let bad = StreamParams::pcm_48k_stereo().with_channels(0);
let err = b.open_output("stub", bad).err().unwrap();
assert!(err.to_string().contains("invalid channel count"));
}
#[test]
fn open_output_unknown_device_is_not_found() {
let b = StubBackend;
let err = b
.open_output("nope", StreamParams::pcm_48k_stereo())
.err()
.unwrap();
assert!(err.to_string().contains("device not found"));
}
#[test]
fn open_output_returns_writable_sink() {
let b = StubBackend;
let mut sink = b
.open_output("stub", StreamParams::pcm_48k_stereo())
.unwrap();
let frame = AudioFrame::silence(2, 4, 48_000);
sink.write(&frame).unwrap();
sink.flush().unwrap();
}
#[test]
fn open_input_reports_unsupported_on_stub() {
let b = StubBackend;
let err = b
.open_input("stub", StreamParams::pcm_48k_mono())
.err()
.unwrap();
assert!(err.to_string().contains("backend initialisation failed"));
}
#[test]
fn input_source_default_latency_is_unavailable() {
struct NoSrc;
impl InputSource for NoSrc {
fn read(&mut self) -> Result<AudioFrame> {
Ok(AudioFrame::silence(1, 0, 48_000))
}
}
let mut s = NoSrc;
let frame = s.read().unwrap();
assert_eq!(frame.num_frames(), 0);
assert!(s.latency_ms().is_err());
}
}