617 lines
22 KiB
Rust
617 lines
22 KiB
Rust
#![allow(dead_code)]
|
|
use super::Stream;
|
|
use super::StreamMetaData;
|
|
use crate::daq::streamdata::*;
|
|
use crate::config::{self, *};
|
|
use crate::daq::{self, *};
|
|
use anyhow::{bail, Result};
|
|
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
|
use cpal::{Device, Host, Sample, SampleFormat, SupportedBufferSize};
|
|
use crossbeam::atomic::AtomicCell;
|
|
use crossbeam::channel::{Receiver, Sender};
|
|
use itertools::Itertools;
|
|
use num::ToPrimitive;
|
|
use reinterpret::reinterpret_slice;
|
|
use std::any::{Any, TypeId};
|
|
use std::collections::VecDeque;
|
|
use std::fmt::Debug;
|
|
use std::sync::Arc;
|
|
|
|
/// Convert CPAL sampleformat datatype
|
|
impl From<DataType> for cpal::SampleFormat {
|
|
fn from(dt: DataType) -> cpal::SampleFormat {
|
|
match dt {
|
|
DataType::F64 => SampleFormat::F64,
|
|
DataType::F32 => SampleFormat::F32,
|
|
DataType::I8 => SampleFormat::I8,
|
|
DataType::I16 => SampleFormat::I16,
|
|
DataType::I32 => SampleFormat::I32,
|
|
}
|
|
}
|
|
}
|
|
// Convert datatype to CPAL sample format
|
|
impl From<cpal::SampleFormat> for DataType {
|
|
fn from(sf: cpal::SampleFormat) -> DataType {
|
|
match sf {
|
|
SampleFormat::F64 => DataType::F64,
|
|
SampleFormat::F32 => DataType::F32,
|
|
SampleFormat::I8 => DataType::I8,
|
|
SampleFormat::I16 => DataType::I16,
|
|
SampleFormat::I32 => DataType::I32,
|
|
_ => panic!("Not implemented sample format: {}", sf),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Cpal api
|
|
pub struct CpalApi {
|
|
host: cpal::Host,
|
|
}
|
|
pub struct CpalStream {
|
|
stream: cpal::Stream,
|
|
md: Arc<StreamMetaData>,
|
|
noutchannels: usize,
|
|
status: Arc<AtomicCell<StreamStatus>>,
|
|
}
|
|
impl Stream for CpalStream {
|
|
fn metadata(&self) -> Arc<StreamMetaData> {
|
|
self.md.clone()
|
|
}
|
|
fn ninchannels(&self) -> usize {
|
|
self.md.nchannels()
|
|
}
|
|
fn noutchannels(&self) -> usize {
|
|
self.noutchannels
|
|
}
|
|
fn status(&self) -> StreamStatus {
|
|
self.status.load()
|
|
}
|
|
}
|
|
|
|
impl CpalApi {
|
|
pub fn new() -> CpalApi {
|
|
// for h in cpal::platform::available_hosts() {
|
|
// println!("h: {:?}", h);
|
|
// }
|
|
CpalApi {
|
|
host: cpal::default_host(),
|
|
}
|
|
}
|
|
pub fn getDeviceInfo(&self) -> Result<Vec<DeviceInfo>> {
|
|
let srs_1 = [
|
|
1000, 2000, 4000, 8000, 12000, 16000, 24000, 48000, 96000, 192000, 384000,
|
|
];
|
|
let srs_2 = [11025, 22050, 44100, 88200];
|
|
|
|
let mut srs_tot = Vec::from_iter(srs_1.iter().chain(srs_2.iter()));
|
|
srs_tot.sort();
|
|
let srs_tot = Vec::from_iter(srs_tot.iter().copied().map(|i| *i as Flt));
|
|
|
|
// srs_tot.sort();
|
|
|
|
let mut devs = vec![];
|
|
for dev in self.host.devices()? {
|
|
// println!("{:?}", dev.name());
|
|
let mut iChannelCount = 0;
|
|
let mut oChannelCount = 0;
|
|
|
|
let mut avSampleRates = srs_tot.clone();
|
|
let mut avFramesPerBlock = vec![256 as usize, 512, 1024, 2048, 8192];
|
|
|
|
let mut sample_formats = vec![];
|
|
// Search for sample formats
|
|
if let Ok(icfg) = dev.supported_input_configs() {
|
|
for icfg in icfg {
|
|
let thissf = icfg.sample_format();
|
|
if thissf.is_uint() {
|
|
continue;
|
|
}
|
|
sample_formats.push(icfg.sample_format());
|
|
avSampleRates.retain(|sr| *sr >= icfg.min_sample_rate().0 as Flt);
|
|
avSampleRates.retain(|sr| *sr <= icfg.max_sample_rate().0 as Flt);
|
|
if let SupportedBufferSize::Range { min, max } = icfg.buffer_size() {
|
|
avFramesPerBlock.retain(|i| i >= &(*min as usize));
|
|
avFramesPerBlock.retain(|i| i <= &(*max as usize));
|
|
}
|
|
iChannelCount = icfg.channels() as u8;
|
|
// avFramesPerBlock.retain(|i| i >= icfg.buffer_size().)
|
|
}
|
|
}
|
|
if let Ok(ocfg) = dev.supported_input_configs() {
|
|
for ocfg in ocfg {
|
|
let thissf = ocfg.sample_format();
|
|
if thissf.is_uint() {
|
|
continue;
|
|
}
|
|
sample_formats.push(thissf);
|
|
avSampleRates.retain(|sr| *sr >= ocfg.min_sample_rate().0 as Flt);
|
|
avSampleRates.retain(|sr| *sr <= ocfg.max_sample_rate().0 as Flt);
|
|
if let SupportedBufferSize::Range { min, max } = ocfg.buffer_size() {
|
|
avFramesPerBlock.retain(|i| i >= &(*min as usize));
|
|
avFramesPerBlock.retain(|i| i <= &(*max as usize));
|
|
}
|
|
oChannelCount = ocfg.channels() as u8;
|
|
}
|
|
}
|
|
sample_formats.dedup();
|
|
if sample_formats.len() == 0 {
|
|
continue;
|
|
}
|
|
|
|
let dtypes: Vec<DataType> =
|
|
sample_formats.iter().dedup().map(|i| (*i).into()).collect();
|
|
|
|
let prefDataType = match dtypes.iter().position(|d| d == &DataType::F32) {
|
|
Some(idx) => dtypes[idx],
|
|
None => dtypes[dtypes.len() - 1],
|
|
};
|
|
let prefSampleRate = *avSampleRates.last().unwrap_or(&48000.);
|
|
devs.push(DeviceInfo {
|
|
api: super::StreamApiDescr::Cpal,
|
|
device_name: dev.name()?,
|
|
avDataTypes: dtypes,
|
|
prefDataType,
|
|
|
|
avSampleRates,
|
|
prefSampleRate,
|
|
avFramesPerBlock,
|
|
prefFramesPerBlock: 2048,
|
|
|
|
iChannelCount,
|
|
oChannelCount,
|
|
|
|
hasInputIEPE: false,
|
|
hasInputACCouplingSwitch: false,
|
|
hasInputTrigger: false,
|
|
hasInternalOutputMonitor: false,
|
|
duplexModeForced: false,
|
|
physicalIOQty: Qty::Number,
|
|
})
|
|
}
|
|
|
|
Ok(devs)
|
|
}
|
|
|
|
// Create the error function closure, that capture the send channel on which error messages from the stream are sent
|
|
fn create_errfcn(
|
|
send_ch: Option<Sender<RawStreamData>>,
|
|
status: Arc<AtomicCell<StreamStatus>>,
|
|
) -> impl FnMut(cpal::StreamError) {
|
|
let errfn = move |err: cpal::StreamError| {
|
|
let serr = match err {
|
|
cpal::StreamError::DeviceNotAvailable => StreamError::DeviceNotAvailable,
|
|
cpal::StreamError::BackendSpecific { err: _ } => StreamError::DriverError,
|
|
};
|
|
if let Some(sender) = &send_ch {
|
|
sender.send(RawStreamData::StreamError(serr)).unwrap();
|
|
}
|
|
status.store(StreamStatus::Error(serr));
|
|
};
|
|
errfn
|
|
}
|
|
|
|
fn create_incallback<T>(
|
|
config: &cpal::StreamConfig,
|
|
sender: Sender<RawStreamData>,
|
|
framesPerBlock: usize,
|
|
en_inchannels: Vec<usize>,
|
|
) -> impl FnMut(&[T], &cpal::InputCallbackInfo)
|
|
where
|
|
T: 'static + Sample + ToPrimitive,
|
|
{
|
|
let tot_inch = config.channels as usize;
|
|
|
|
let mut q = VecDeque::<T>::with_capacity(2 * tot_inch * framesPerBlock);
|
|
|
|
let mut enabled_ch_data: Vec<T> =
|
|
vec![Sample::EQUILIBRIUM; en_inchannels.len() * framesPerBlock];
|
|
|
|
// The actual callback that is returned
|
|
move |input: &[T], _: &cpal::InputCallbackInfo| {
|
|
// Copy elements over in ring buffer
|
|
q.extend(input);
|
|
|
|
while q.len() > tot_inch * framesPerBlock {
|
|
// println!("q full enough: {}", q.len());
|
|
|
|
// // Loop over enabled channels
|
|
for (i, ch) in en_inchannels.iter().enumerate() {
|
|
let in_iterator = q.iter().skip(*ch).step_by(tot_inch);
|
|
let out_iterator = enabled_ch_data
|
|
.iter_mut()
|
|
.skip(i)
|
|
.step_by(en_inchannels.len());
|
|
|
|
// Copy over elements, *DEINTERLEAVED*
|
|
out_iterator.zip(in_iterator).for_each(|(o, i)| {
|
|
*o = *i;
|
|
});
|
|
}
|
|
|
|
// Drain copied elements from ring buffer
|
|
q.drain(0..framesPerBlock * tot_inch);
|
|
|
|
// Send over data
|
|
let msg = RawStreamData::from(enabled_ch_data.clone());
|
|
sender.send(msg).unwrap()
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Create an input stream for a CPAL device.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * sf: Sample format
|
|
fn build_input_stream(
|
|
sf: cpal::SampleFormat,
|
|
config: &cpal::StreamConfig,
|
|
device: &cpal::Device,
|
|
sender: Sender<RawStreamData>,
|
|
en_inchannels: Vec<usize>,
|
|
framesPerBlock: usize,
|
|
) -> Result<(cpal::Stream, Arc<AtomicCell<StreamStatus>>)> {
|
|
let status = Arc::new(AtomicCell::new(StreamStatus::NotRunning));
|
|
|
|
let errfcn = CpalApi::create_errfcn(Some(sender.clone()), status.clone());
|
|
|
|
macro_rules! build_stream{
|
|
($($cpaltype:pat => $rtype:ty),*) => {
|
|
match sf {
|
|
$(
|
|
$cpaltype => {
|
|
let icb = CpalApi::create_incallback::<$rtype>(&config, sender, framesPerBlock, en_inchannels);
|
|
device.build_input_stream(
|
|
&config,
|
|
icb,
|
|
errfcn,
|
|
None)?
|
|
}),*,
|
|
_ => bail!("Unsupported sample format '{}'", sf)
|
|
}
|
|
}
|
|
}
|
|
let stream: cpal::Stream = build_stream!(
|
|
SampleFormat::I8 => i8,
|
|
SampleFormat::I16 => i16,
|
|
SampleFormat::I32 => i32,
|
|
SampleFormat::F32 => f32
|
|
);
|
|
Ok((stream, status))
|
|
}
|
|
|
|
fn create_outcallback<T>(
|
|
config: &cpal::StreamConfig,
|
|
streamstatus: Arc<AtomicCell<StreamStatus>>,
|
|
receiver: Receiver<RawStreamData>,
|
|
framesPerBlock: usize,
|
|
) -> impl FnMut(&mut [T], &cpal::OutputCallbackInfo)
|
|
where
|
|
T: 'static + Sample + Debug,
|
|
{
|
|
let tot_outch: usize = config.channels as usize;
|
|
// println!("Numer of channels: {:?}", tot_outch);
|
|
let mut callback_ctr: usize = 0;
|
|
let mut q = VecDeque::<T>::with_capacity(2 * tot_outch * framesPerBlock);
|
|
|
|
move |data, _info: &_| {
|
|
let nsamples_asked = data.len();
|
|
let status = streamstatus.load();
|
|
callback_ctr += 1;
|
|
|
|
let mut setToEquilibrium = || data.iter_mut().for_each(|v| *v = Sample::EQUILIBRIUM);
|
|
match status {
|
|
StreamStatus::NotRunning | StreamStatus::Error(_) => {
|
|
setToEquilibrium();
|
|
return;
|
|
}
|
|
_ => {}
|
|
}
|
|
|
|
if q.len() < nsamples_asked {
|
|
// Obtain new samples from the generator
|
|
for dat in receiver.try_iter() {
|
|
let slice = dat.getRef::<T>();
|
|
if let StreamStatus::Running = status {
|
|
q.extend(slice);
|
|
}
|
|
}
|
|
}
|
|
|
|
if q.len() >= nsamples_asked {
|
|
// All right, we have enough samples to send out! They are
|
|
// drained from the queue
|
|
data.iter_mut()
|
|
.zip(q.drain(..nsamples_asked))
|
|
.for_each(|(o, i)| *o = i);
|
|
} else if callback_ctr <= 2 {
|
|
// For the first two blocks, we allow dat the data is not yet
|
|
// ready, without complaining on underruns
|
|
setToEquilibrium();
|
|
} else {
|
|
// Output buffer underrun
|
|
streamstatus.store(StreamStatus::Error(StreamError::OutputUnderrunError));
|
|
setToEquilibrium();
|
|
}
|
|
}
|
|
}
|
|
|
|
fn build_output_stream(
|
|
sf: cpal::SampleFormat,
|
|
config: &cpal::StreamConfig,
|
|
device: &cpal::Device,
|
|
receiver: Receiver<RawStreamData>,
|
|
framesPerBlock: usize,
|
|
) -> Result<(cpal::Stream, Arc<AtomicCell<StreamStatus>>)> {
|
|
// let tot_ch = config.channels as usize;
|
|
|
|
let status = Arc::new(AtomicCell::new(StreamStatus::NotRunning));
|
|
|
|
let err_cb = CpalApi::create_errfcn(None, status.clone());
|
|
macro_rules! build_stream{
|
|
($($cpaltype:pat => $rtype:ty),*) => {
|
|
match sf {
|
|
$(
|
|
$cpaltype => {
|
|
let outcallback = CpalApi::create_outcallback::<$rtype>(config, status.clone(), receiver, framesPerBlock);
|
|
device.build_output_stream(
|
|
&config,
|
|
outcallback,
|
|
err_cb,
|
|
None)?
|
|
}),*,
|
|
_ => bail!("Unsupported sample format '{}'", sf)
|
|
}
|
|
}
|
|
}
|
|
let stream: cpal::Stream = build_stream!(
|
|
SampleFormat::I8 => i8,
|
|
SampleFormat::I16 => i16,
|
|
SampleFormat::I32 => i32,
|
|
SampleFormat::F32 => f32
|
|
);
|
|
|
|
Ok((stream, status))
|
|
}
|
|
|
|
/// Create CPAL specific configuration, from our specified daq config and device info
|
|
fn create_cpal_config<T>(
|
|
st: StreamType,
|
|
devinfo: &DeviceInfo,
|
|
conf: &DaqConfig,
|
|
_dev: &cpal::Device,
|
|
conf_iterator: T,
|
|
) -> Result<cpal::SupportedStreamConfig>
|
|
where
|
|
T: Iterator<Item = cpal::SupportedStreamConfigRange>,
|
|
{
|
|
let nchannels = match st {
|
|
StreamType::Input => devinfo.iChannelCount,
|
|
StreamType::Output => devinfo.oChannelCount,
|
|
_ => unreachable!(),
|
|
};
|
|
for cpalconf in conf_iterator {
|
|
if cpalconf.sample_format() == conf.dtype.into() {
|
|
// Specified sample format is available
|
|
if cpalconf.channels() == nchannels as u16 {
|
|
let requested_sr = conf.sampleRate(devinfo);
|
|
if cpalconf.min_sample_rate().0 as Flt <= requested_sr
|
|
&& cpalconf.max_sample_rate().0 as Flt >= requested_sr
|
|
{
|
|
// Sample rate falls within range.
|
|
let requested_fpb = conf.framesPerBlock(devinfo) as u32;
|
|
// Last check: check if buffer size is allowed
|
|
match cpalconf.buffer_size() {
|
|
SupportedBufferSize::Range { min, max } => {
|
|
if min >= &requested_fpb || max <= &requested_fpb {
|
|
bail!(
|
|
"Frames per block should be >= {} and <= {}. Requested {}.",
|
|
min,
|
|
max,
|
|
requested_fpb
|
|
)
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
return Ok(cpalconf.with_sample_rate(cpal::SampleRate(requested_sr as u32)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
bail!("API error: specified DAQ configuration is not available for device")
|
|
}
|
|
|
|
/// Start a stream for a device with a given configuration.
|
|
pub fn startInputStream(
|
|
&self,
|
|
stype: StreamType,
|
|
devinfo: &DeviceInfo,
|
|
conf: &DaqConfig,
|
|
sender: Sender<RawStreamData>,
|
|
) -> Result<Box<dyn Stream>> {
|
|
for cpaldev in self.host.devices()? {
|
|
// See if we can create a supported stream config.
|
|
let supported_config = match stype {
|
|
StreamType::Duplex => bail!("Duplex stream not supported for CPAL"),
|
|
StreamType::Input => CpalApi::create_cpal_config(
|
|
stype.clone(),
|
|
devinfo,
|
|
conf,
|
|
&cpaldev,
|
|
cpaldev.supported_input_configs()?,
|
|
),
|
|
StreamType::Output => CpalApi::create_cpal_config(
|
|
stype.clone(),
|
|
devinfo,
|
|
conf,
|
|
&cpaldev,
|
|
cpaldev.supported_output_configs()?,
|
|
),
|
|
}?;
|
|
let framesPerBlock = conf.framesPerBlock(devinfo);
|
|
|
|
let sf = supported_config.sample_format();
|
|
let config: cpal::StreamConfig = supported_config.config();
|
|
|
|
let meta = StreamMetaData::new(
|
|
&conf.enabledInchannelConfig(),
|
|
conf.dtype,
|
|
supported_config.sample_rate().0 as Flt,
|
|
framesPerBlock,
|
|
)?;
|
|
let meta = Arc::new(meta);
|
|
|
|
let (stream, status) = CpalApi::build_input_stream(
|
|
sf,
|
|
&config,
|
|
&cpaldev,
|
|
sender,
|
|
conf.enabledInchannelsList(),
|
|
framesPerBlock,
|
|
)?;
|
|
|
|
stream.play()?;
|
|
status.store(StreamStatus::Running);
|
|
|
|
return Ok(Box::new(CpalStream {
|
|
stream,
|
|
md: meta,
|
|
noutchannels: 0,
|
|
status,
|
|
}));
|
|
}
|
|
bail!(format!(
|
|
"Error: requested device {} not found. Please make sure the device is available.",
|
|
devinfo.device_name
|
|
))
|
|
}
|
|
|
|
/// Start a default input stream.
|
|
///
|
|
///
|
|
pub fn startDefaultInputStream(
|
|
&mut self,
|
|
sender: Sender<RawStreamData>,
|
|
) -> Result<Box<dyn Stream>> {
|
|
if let Some(device) = self.host.default_input_device() {
|
|
if let Ok(config) = device.default_input_config() {
|
|
let framesPerBlock: usize = 4096;
|
|
let final_config = cpal::StreamConfig {
|
|
channels: config.channels(),
|
|
sample_rate: config.sample_rate(),
|
|
buffer_size: cpal::BufferSize::Fixed(framesPerBlock as u32),
|
|
};
|
|
let en_inchannels = Vec::from_iter((0..config.channels()).map(|i| i as usize));
|
|
|
|
let sf = config.sample_format();
|
|
let (stream, status) = CpalApi::build_input_stream(
|
|
sf,
|
|
&final_config,
|
|
&device,
|
|
sender,
|
|
en_inchannels,
|
|
framesPerBlock,
|
|
)?;
|
|
stream.play()?;
|
|
status.store(StreamStatus::Running);
|
|
|
|
// Daq: default channel config
|
|
let daqchannels = Vec::from_iter(
|
|
(0..final_config.channels)
|
|
.map(|i| DaqChannel::defaultAudio(format!("Unnamed input channel {}", i))),
|
|
);
|
|
|
|
// Specify data tape
|
|
let dtype = DataType::from(sf);
|
|
|
|
// Create stream metadata
|
|
let md = StreamMetaData::new(
|
|
&daqchannels,
|
|
dtype,
|
|
config.sample_rate().0 as Flt,
|
|
framesPerBlock,
|
|
)?;
|
|
let md = Arc::new(md);
|
|
Ok(Box::new(CpalStream {
|
|
stream,
|
|
md,
|
|
noutchannels: 0,
|
|
status,
|
|
}))
|
|
} else {
|
|
bail!("Could not obtain default input configuration")
|
|
}
|
|
} else {
|
|
bail!("Could not open default input device")
|
|
}
|
|
}
|
|
|
|
pub fn startDefaultOutputStream(
|
|
&self,
|
|
receiver: Receiver<RawStreamData>,
|
|
) -> Result<Box<dyn Stream>> {
|
|
if let Some(device) = self.host.default_output_device() {
|
|
if let Ok(config) = device.default_output_config() {
|
|
// let framesPerBlock: usize = 256;
|
|
// let framesPerBlock: usize = 8192;
|
|
let framesPerBlock: usize = config.sample_rate().0 as usize;
|
|
// let framesPerBlock: usize = 256;
|
|
let final_config = cpal::StreamConfig {
|
|
channels: config.channels(),
|
|
sample_rate: config.sample_rate(),
|
|
buffer_size: cpal::BufferSize::Fixed(framesPerBlock as u32),
|
|
};
|
|
// let en_outchannels = Vec::from_iter((0..config.channels()).map(|i| i as usize));
|
|
|
|
let sampleformat = config.sample_format();
|
|
let (stream, status) = CpalApi::build_output_stream(
|
|
sampleformat,
|
|
&final_config,
|
|
&device,
|
|
receiver,
|
|
framesPerBlock,
|
|
)?;
|
|
|
|
stream.play()?;
|
|
status.store(StreamStatus::Running);
|
|
|
|
// Daq: default channel config
|
|
let daqchannels =
|
|
Vec::from_iter((0..final_config.channels).map(|i| {
|
|
DaqChannel::defaultAudio(format!("Unnamed output channel {}", i))
|
|
}));
|
|
|
|
// // Specify data tape
|
|
let dtype = DataType::from(sampleformat);
|
|
|
|
// // Create stream metadata
|
|
let md = StreamMetaData::new(
|
|
&daqchannels,
|
|
dtype,
|
|
config.sample_rate().0 as Flt,
|
|
framesPerBlock,
|
|
)?;
|
|
let md = Arc::new(md);
|
|
let str = Box::new(CpalStream {
|
|
stream,
|
|
md,
|
|
noutchannels: daqchannels.len(),
|
|
status,
|
|
});
|
|
Ok(str)
|
|
} else {
|
|
bail!("Could not obtain default output configuration")
|
|
} // Default output config is OK
|
|
} else {
|
|
bail!("Could not open output device")
|
|
} // Could not
|
|
}
|
|
// Create an output stream, using given signal generators for each channel.
|
|
// }
|
|
|
|
pub fn startOutputStream(&self, rx: Receiver<RawStreamData>) -> Result<Box<dyn Stream>> {
|
|
bail!("Not implemented");
|
|
}
|
|
}
|