Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: Duplex API #553

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -72,5 +72,8 @@ name = "enumerate"
[[example]]
name = "feedback"

[[example]]
name = "duplex"

[[example]]
name = "record_wav"
127 changes: 127 additions & 0 deletions examples/duplex.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
//! Feeds back the input stream directly into the output stream by opening the device in full
//! duplex mode.
//!
//! Assumes that the device can use the f32 sample format.

extern crate anyhow;
extern crate clap;
extern crate cpal;

use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};

#[derive(Debug)]
struct Opt {
#[cfg(all(
any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd"),
feature = "jack"
))]
jack: bool,

device: String,
}

impl Opt {
fn from_args() -> anyhow::Result<Self> {
let app = clap::App::new("duplex").arg_from_usage("[DEVICE] 'The audio device to use'");

#[cfg(all(
any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd"),
feature = "jack"
))]
let app = app.arg_from_usage("-j, --jack 'Use the JACK host");
let matches = app.get_matches();
let device = matches.value_of("DEVICE").unwrap_or("default").to_string();

#[cfg(all(
any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd"),
feature = "jack"
))]
return Ok(Opt {
jack: matches.is_present("jack"),
device,
});

#[cfg(any(
not(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd")),
not(feature = "jack")
))]
Ok(Opt { device })
}
}

fn main() -> anyhow::Result<()> {
let opt = Opt::from_args()?;

// Conditionally compile with jack if the feature is specified.
#[cfg(all(
any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd"),
feature = "jack"
))]
// Manually check for flags. Can be passed through cargo with -- e.g.
// cargo run --release --example beep --features jack -- --jack
let host = if opt.jack {
cpal::host_from_id(cpal::available_hosts()
.into_iter()
.find(|id| *id == cpal::HostId::Jack)
.expect(
"make sure --features jack is specified. only works on OSes where jack is available",
)).expect("jack host unavailable")
} else {
cpal::default_host()
};

#[cfg(any(
not(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd")),
not(feature = "jack")
))]
let host = cpal::default_host();

// Find the device
let device = if opt.device == "default" {
host.default_duplex_device()
} else {
host.duplex_devices()?
.find(|x| x.name().map(|y| y == opt.device).unwrap_or(false))
}
.expect("failed to find device");

println!("Using device: \"{}\"", device.name()?);

let config: cpal::DuplexStreamConfig = device.default_duplex_config()?.into();
let input_channels: usize = config.input_channels.into();
let output_channels: usize = config.output_channels.into();
// Simply copy as many channels as both input and output allow
let copy_channels = input_channels.min(output_channels);

let data_fn = move |data_in: &[f32], data_out: &mut [f32], _: &cpal::DuplexCallbackInfo| {
for (frame_in, frame_out) in data_in
.chunks(input_channels)
.zip(data_out.chunks_mut(output_channels))
{
frame_out[..copy_channels].clone_from_slice(&frame_in[..copy_channels]);
}
};

// Build streams.
println!(
"Attempting to build stream with f32 samples and `{:?}`.",
config
);
let stream = device.build_duplex_stream(&config, data_fn, err_fn)?;
println!("Successfully built stream.");

// Play the stream.
println!("Starting the duplex stream.");
stream.play()?;

// Run for 3 seconds before closing.
println!("Playing for 3 seconds... ");
std::thread::sleep(std::time::Duration::from_secs(3));
drop(stream);
println!("Done!");
Ok(())
}

fn err_fn(err: cpal::StreamError) {
eprintln!("an error occurred on stream: {}", err);
}
6 changes: 6 additions & 0 deletions src/host/alsa/enumerate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ pub fn default_output_device() -> Option<Device> {
})
}

#[inline]
pub fn default_duplex_device() -> Option<Device> {
// TODO
None
}

impl From<alsa::Error> for DevicesError {
fn from(err: alsa::Error) -> Self {
let err: BackendSpecificError = err.into();
Expand Down
60 changes: 55 additions & 5 deletions src/host/alsa/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ use self::alsa::poll::Descriptors;
use self::parking_lot::Mutex;
use crate::{
BackendSpecificError, BufferSize, BuildStreamError, ChannelCount, Data,
DefaultStreamConfigError, DeviceNameError, DevicesError, InputCallbackInfo, OutputCallbackInfo,
PauseStreamError, PlayStreamError, SampleFormat, SampleRate, StreamConfig, StreamError,
SupportedBufferSize, SupportedStreamConfig, SupportedStreamConfigRange,
SupportedStreamConfigsError,
DefaultStreamConfigError, DeviceNameError, DevicesError, DuplexCallbackInfo,
DuplexStreamConfig, InputCallbackInfo, OutputCallbackInfo, PauseStreamError, PlayStreamError,
SampleFormat, SampleRate, StreamConfig, StreamError, SupportedBufferSize,
SupportedDuplexStreamConfig, SupportedDuplexStreamConfigRange, SupportedStreamConfig,
SupportedStreamConfigRange, SupportedStreamConfigsError,
};
use std::cmp;
use std::convert::TryInto;
Expand All @@ -18,10 +19,13 @@ use std::thread::{self, JoinHandle};
use std::vec::IntoIter as VecIntoIter;
use traits::{DeviceTrait, HostTrait, StreamTrait};

pub use self::enumerate::{default_input_device, default_output_device, Devices};
pub use self::enumerate::{
default_duplex_device, default_input_device, default_output_device, Devices,
};

pub type SupportedInputConfigs = VecIntoIter<SupportedStreamConfigRange>;
pub type SupportedOutputConfigs = VecIntoIter<SupportedStreamConfigRange>;
pub type SupportedDuplexConfigs = VecIntoIter<SupportedDuplexStreamConfigRange>;

mod enumerate;

Expand Down Expand Up @@ -55,11 +59,16 @@ impl HostTrait for Host {
fn default_output_device(&self) -> Option<Self::Device> {
default_output_device()
}

fn default_duplex_device(&self) -> Option<Self::Device> {
default_duplex_device()
}
}

impl DeviceTrait for Device {
type SupportedInputConfigs = SupportedInputConfigs;
type SupportedOutputConfigs = SupportedOutputConfigs;
type SupportedDuplexConfigs = SupportedDuplexConfigs;
type Stream = Stream;

fn name(&self) -> Result<String, DeviceNameError> {
Expand All @@ -78,6 +87,12 @@ impl DeviceTrait for Device {
Device::supported_output_configs(self)
}

fn supported_duplex_configs(
&self,
) -> Result<Self::SupportedDuplexConfigs, SupportedStreamConfigsError> {
Device::supported_duplex_configs(self)
}

fn default_input_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
Device::default_input_config(self)
}
Expand All @@ -86,6 +101,12 @@ impl DeviceTrait for Device {
Device::default_output_config(self)
}

fn default_duplex_config(
&self,
) -> Result<SupportedDuplexStreamConfig, DefaultStreamConfigError> {
Device::default_duplex_config(self)
}

fn build_input_stream_raw<D, E>(
&self,
conf: &StreamConfig,
Expand Down Expand Up @@ -119,6 +140,21 @@ impl DeviceTrait for Device {
let stream = Stream::new_output(Arc::new(stream_inner), data_callback, error_callback);
Ok(stream)
}

fn build_duplex_stream_raw<D, E>(
&self,
_conf: &DuplexStreamConfig,
_sample_format: SampleFormat,
_data_callback: D,
_error_callback: E,
) -> Result<Self::Stream, BuildStreamError>
where
D: FnMut(&Data, &mut Data, &DuplexCallbackInfo) + Send + 'static,
E: FnMut(StreamError) + Send + 'static,
{
// TODO
Err(BuildStreamError::StreamConfigNotSupported)
}
}

struct TriggerSender(libc::c_int);
Expand Down Expand Up @@ -442,6 +478,13 @@ impl Device {
self.supported_configs(alsa::Direction::Playback)
}

fn supported_duplex_configs(
&self,
) -> Result<SupportedDuplexConfigs, SupportedStreamConfigsError> {
// TODO
Ok(Vec::new().into_iter())
}

// ALSA does not offer default stream formats, so instead we compare all supported formats by
// the `SupportedStreamConfigRange::cmp_default_heuristics` order and select the greatest.
fn default_config(
Expand Down Expand Up @@ -489,6 +532,13 @@ impl Device {
fn default_output_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
self.default_config(alsa::Direction::Playback)
}

fn default_duplex_config(
&self,
) -> Result<SupportedDuplexStreamConfig, DefaultStreamConfigError> {
// TODO
Err(DefaultStreamConfigError::StreamTypeNotSupported)
}
}

struct StreamInner {
Expand Down
16 changes: 16 additions & 0 deletions src/host/asio/device.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std;
pub type SupportedInputConfigs = std::vec::IntoIter<SupportedStreamConfigRange>;
pub type SupportedOutputConfigs = std::vec::IntoIter<SupportedStreamConfigRange>;
pub type SupportedDuplexConfigs = std::vec::IntoIter<SupportedDuplexStreamConfigRange>;

use super::parking_lot::Mutex;
use super::sys;
Expand All @@ -13,6 +14,7 @@ use DevicesError;
use SampleFormat;
use SampleRate;
use SupportedBufferSize;
use SupportedDupolexStreamConfigRange;
use SupportedStreamConfig;
use SupportedStreamConfigRange;
use SupportedStreamConfigsError;
Expand Down Expand Up @@ -127,6 +129,13 @@ impl Device {
Ok(supported_configs.into_iter())
}

fn supported_duplex_configs(
&self,
) -> Result<SupportedDuplexConfigs, SupportedStreamConfigsError> {
// TODO
Ok(Vec::new().into_iter())
}

/// Returns the default input config
pub fn default_input_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
let channels = self.driver.channels().map_err(default_config_err)?.ins as u16;
Expand Down Expand Up @@ -167,6 +176,13 @@ impl Device {
sample_format,
})
}

fn default_duplex_config(
&self,
) -> Result<SupportedDuplexStreamConfig, DefaultStreamConfigError> {
// TODO
Err(DefaultStreamConfigError::StreamTypeNotSupported)
}
}

impl Devices {
Expand Down
Loading