use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use audio_core_bsd::AudioFrame;
use crate::backend::{AudioBackend, InputSource, OutputSink};
use crate::device::{DeviceDirection, DeviceInfo, StreamParams};
use crate::error::Result;
#[derive(Debug, Default)]
pub struct NullBackend {
output_writes: Arc<AtomicU64>,
input_reads: Arc<AtomicU64>,
}
impl NullBackend {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn output_writes(&self) -> u64 {
self.output_writes.load(Ordering::Relaxed)
}
#[must_use]
pub fn input_reads(&self) -> u64 {
self.input_reads.load(Ordering::Relaxed)
}
pub const DEVICE_NAME: &'static str = "null";
fn device() -> DeviceInfo {
DeviceInfo::new(
Self::DEVICE_NAME,
DeviceDirection::Duplex,
2,
vec![44_100, 48_000, 96_000],
true,
)
}
}
impl AudioBackend for NullBackend {
fn enumerate_devices(&self) -> Vec<DeviceInfo> {
vec![Self::device()]
}
fn default_output(&self) -> Option<DeviceInfo> {
Some(Self::device())
}
fn default_input(&self) -> Option<DeviceInfo> {
Some(Self::device())
}
fn open_output(&self, dev: &str, params: StreamParams) -> Result<Box<dyn OutputSink>> {
params.validate()?;
if dev != Self::DEVICE_NAME {
return Err(crate::IoError::DeviceNotFound(dev.into()));
}
Ok(Box::new(NullSink {
writes: Arc::clone(&self.output_writes),
channels: params.channels,
sample_rate: params.sample_rate,
}))
}
fn open_input(&self, dev: &str, params: StreamParams) -> Result<Box<dyn InputSource>> {
params.validate()?;
if dev != Self::DEVICE_NAME {
return Err(crate::IoError::DeviceNotFound(dev.into()));
}
Ok(Box::new(NullSource {
reads: Arc::clone(&self.input_reads),
channels: params.channels,
sample_rate: params.sample_rate,
frames_per_read: 256,
}))
}
}
pub struct NullSink {
writes: Arc<AtomicU64>,
channels: u16,
sample_rate: u32,
}
impl OutputSink for NullSink {
fn write(&mut self, frame: &AudioFrame) -> Result<()> {
let _ = (self.channels, self.sample_rate, frame);
self.writes.fetch_add(1, Ordering::Relaxed);
Ok(())
}
}
pub struct NullSource {
reads: Arc<AtomicU64>,
channels: u16,
sample_rate: u32,
frames_per_read: usize,
}
impl InputSource for NullSource {
fn read(&mut self) -> Result<AudioFrame> {
self.reads.fetch_add(1, Ordering::Relaxed);
Ok(AudioFrame::silence(
self.channels,
self.frames_per_read,
self.sample_rate,
))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn null_backend_advertises_one_duplex_device() {
let b = NullBackend::new();
let devs = b.enumerate_devices();
assert_eq!(devs.len(), 1);
assert_eq!(devs[0].name, NullBackend::DEVICE_NAME);
assert_eq!(devs[0].direction, DeviceDirection::Duplex);
}
#[test]
fn null_backend_has_default_output_and_input() {
let b = NullBackend::new();
assert!(b.default_output().is_some());
assert!(b.default_input().is_some());
}
#[test]
fn open_output_discards_writes_and_counts() {
let b = NullBackend::new();
let mut sink = b
.open_output(NullBackend::DEVICE_NAME, StreamParams::pcm_48k_stereo())
.unwrap();
let frame = AudioFrame::silence(2, 256, 48_000);
sink.write(&frame).unwrap();
sink.write(&frame).unwrap();
assert_eq!(b.output_writes(), 2);
}
#[test]
fn open_input_returns_silence_and_counts() {
let b = NullBackend::new();
let mut src = b
.open_input(NullBackend::DEVICE_NAME, StreamParams::pcm_48k_mono())
.unwrap();
let frame = src.read().unwrap();
assert_eq!(frame.channels, 1);
assert!(frame.samples.iter().all(|&s| s == 0.0));
assert_eq!(b.input_reads(), 1);
}
#[test]
fn open_output_unknown_device_is_not_found() {
let b = NullBackend::new();
let err = b
.open_output("real", StreamParams::pcm_48k_stereo())
.err()
.unwrap();
assert!(err.to_string().contains("device not found"));
}
#[test]
fn open_output_validates_params() {
let b = NullBackend::new();
let err = b
.open_output(
NullBackend::DEVICE_NAME,
StreamParams::pcm_48k_stereo().with_sample_rate(0),
)
.err()
.unwrap();
assert!(err.to_string().contains("invalid sample rate"));
}
}