Skip to content
Open
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
12 changes: 12 additions & 0 deletions asio-sys/src/bindings/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -943,6 +943,18 @@ impl Driver {
let mut dcb = DRIVER_EVENT_CALLBACKS.lock().unwrap();
dcb.retain(|&(id, _)| id != rem_id);
}

/// Returns the name of the channel at the given index.
///
/// `channel` is a 0-based channel index. `is_input` selects the input (`true`) or output
/// (`false`) direction.
///
/// The driver must already be loaded (i.e. this `Driver` instance must be alive).
pub fn channel_name(&self, channel: i32, is_input: bool) -> Result<String, AsioError> {
let _guard = self.inner.lock_state();
let info = asio_channel_info(channel, is_input)?;
Ok(driver_name_to_utf8(&info.name).into_owned())
}
}

impl DriverState {
Expand Down
7 changes: 7 additions & 0 deletions examples/custom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,13 @@ impl DeviceTrait for MyDevice {
handle: Some(handle),
})
}

fn get_channel_name(&self, channel_index: u16, input: bool) -> Result<String, Error> {
Ok(format!(
"{} {channel_index}",
if input { "Input" } else { "Output" }
))
}
}

impl fmt::Display for MyDevice {
Expand Down
5 changes: 5 additions & 0 deletions src/host/aaudio/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,11 @@ impl DeviceTrait for Device {
sample_format,
)
}

#[allow(unused_variables)]
fn get_channel_name(&self, channel_index: u16, input: bool) -> Result<String, Error> {
Err(Error::new(ErrorKind::UnsupportedOperation))
}
}

impl PartialEq for Device {
Expand Down
5 changes: 5 additions & 0 deletions src/host/alsa/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,11 @@ impl DeviceTrait for Device {
);
Ok(stream)
}

#[allow(unused_variables)]
fn get_channel_name(&self, channel_index: u16, input: bool) -> Result<String, Error> {
Err(Error::new(ErrorKind::UnsupportedOperation))
}
}

#[derive(Debug)]
Expand Down
31 changes: 31 additions & 0 deletions src/host/asio/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ pub struct Device {
input_sample_format: Option<SampleFormat>,
output_sample_format: Option<SampleFormat>,
supported_sample_rates: Box<[SampleRate]>,
input_channel_names: Box<[String]>,
output_channel_names: Box<[String]>,

// Input and/or Output stream.
// A driver can only have one of each.
Expand Down Expand Up @@ -127,6 +129,26 @@ impl Device {
}
configs
}

pub fn get_channel_name(&self, channel_index: u16, input: bool) -> Result<String, Error> {
let names = if input {
&self.input_channel_names
} else {
&self.output_channel_names
};

names.get(channel_index as usize).cloned().ok_or_else(|| {
Error::with_message(
ErrorKind::InvalidInput,
format!(
"channel index {} is out of range (device has {} {} channels)",
channel_index,
names.len(),
if input { "input" } else { "output" },
),
)
})
}
}

impl PartialEq for Device {
Expand Down Expand Up @@ -213,6 +235,13 @@ impl Iterator for Devices {
.filter(|&r| driver.can_sample_rate(r.into()).unwrap_or(false))
.collect();

let input_channel_names: Box<[String]> = (0..channels.ins)
.map(|ch| driver.channel_name(ch, true).unwrap_or_default())
.collect();
let output_channel_names: Box<[String]> = (0..channels.outs)
.map(|ch| driver.channel_name(ch, false).unwrap_or_default())
.collect();

self.current_driver = Some(driver);

let asio_streams = Arc::new(Mutex::new(sys::AsioStreams {
Expand All @@ -230,6 +259,8 @@ impl Iterator for Devices {
input_sample_format,
output_sample_format,
supported_sample_rates,
input_channel_names,
output_channel_names,
asio_streams,
// Initialize with sentinel value so it never matches global flag state (0 or 1).
current_callback_flag: Arc::new(AtomicU32::new(u32::MAX)),
Expand Down
4 changes: 4 additions & 0 deletions src/host/asio/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,10 @@ impl DeviceTrait for Device {
timeout,
)
}

fn get_channel_name(&self, channel_index: u16, input: bool) -> Result<String, Error> {
Device::get_channel_name(self, channel_index, input)
}
}

impl StreamTrait for Stream {
Expand Down
5 changes: 5 additions & 0 deletions src/host/audioworklet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,11 @@ impl DeviceTrait for Device {
_latency_poller: latency_poller,
})
}

#[allow(unused_variables)]
fn get_channel_name(&self, channel_index: u16, input: bool) -> Result<String, Error> {
Err(Error::new(ErrorKind::UnsupportedOperation))
}
}

impl StreamTrait for Stream {
Expand Down
5 changes: 5 additions & 0 deletions src/host/coreaudio/ios/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,11 @@ impl DeviceTrait for Device {
stream.signal_ready();
Ok(stream)
}

#[allow(unused_variables)]
fn get_channel_name(&self, channel_index: u16, input: bool) -> Result<String, Error> {
Err(Error::new(ErrorKind::UnsupportedOperation))
}
}

pub struct Stream {
Expand Down
70 changes: 66 additions & 4 deletions src/host/coreaudio/macos/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,11 @@ use objc2_core_audio::{
kAudioDevicePropertyDeviceUID, kAudioDevicePropertyLatency,
kAudioDevicePropertyNominalSampleRate, kAudioDevicePropertySafetyOffset,
kAudioDevicePropertyStreamConfiguration, kAudioDevicePropertyStreamFormat,
kAudioObjectPropertyClass, kAudioObjectPropertyElementMain, kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyScopeInput, kAudioObjectPropertyScopeOutput, AudioClassID, AudioDeviceID,
AudioObjectGetPropertyData, AudioObjectGetPropertyDataSize, AudioObjectID,
AudioObjectPropertyAddress, AudioObjectPropertyScope, AudioObjectSetPropertyData,
kAudioObjectPropertyClass, kAudioObjectPropertyElementMain, kAudioObjectPropertyElementName,
kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyScopeInput,
kAudioObjectPropertyScopeOutput, AudioClassID, AudioDeviceID, AudioObjectGetPropertyData,
AudioObjectGetPropertyDataSize, AudioObjectID, AudioObjectPropertyAddress,
AudioObjectPropertyScope, AudioObjectSetPropertyData,
};
use objc2_core_audio_types::{
AudioBuffer, AudioBufferList, AudioStreamBasicDescription, AudioValueRange,
Expand Down Expand Up @@ -354,6 +355,10 @@ impl DeviceTrait for Device {
timeout,
)
}

fn get_channel_name(&self, channel_index: u16, input: bool) -> Result<String, Error> {
Device::get_channel_name(self, channel_index, input)
}
}

#[derive(Clone)]
Expand Down Expand Up @@ -687,6 +692,24 @@ impl Device {
.map(|mut configs| configs.next().is_some())
.unwrap_or(false)
}

fn get_channel_name(&self, channel_index: u16, input: bool) -> Result<String, Error> {
if input && !self.supports_input() {
return Err(Error::with_message(
ErrorKind::InvalidInput,
"Device does not support input",
));
}

if !input && !self.supports_output() {
return Err(Error::with_message(
ErrorKind::InvalidInput,
"Device does not support output",
));
}

unsafe { get_channel_name_for_device(self.audio_device_id, channel_index, input) }
}
}

impl Device {
Expand Down Expand Up @@ -1084,3 +1107,42 @@ pub(crate) fn get_device_buffer_frame_size(
)?;
Ok(frames as usize)
}

unsafe fn get_channel_name_for_device(
device_id: AudioDeviceID,
channel_index: u16,
input: bool,
) -> Result<String, Error> {
let mut channel_name: *mut CFString = std::ptr::null_mut();
let mut data_size = size_of::<*mut CFString>() as u32;

let property_address = AudioObjectPropertyAddress {
mSelector: kAudioObjectPropertyElementName,
mScope: if input {
kAudioObjectPropertyScopeInput
} else {
kAudioObjectPropertyScopeOutput
},
// Channels numbers start on 1 here
mElement: channel_index as u32 + 1,
};

let status = AudioObjectGetPropertyData(
device_id,
NonNull::from(&property_address),
0,
null(),
NonNull::from(&mut data_size),
NonNull::from(&mut channel_name).cast(),
);
check_os_status(status)?;

if !channel_name.is_null() {
Ok(CFRetained::from_raw(NonNull::new(channel_name).unwrap()).to_string())
} else {
Err(Error::with_message(
ErrorKind::Other,
"channel name is null",
))
}
}
5 changes: 5 additions & 0 deletions src/host/custom/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,11 @@ impl DeviceTrait for Device {
timeout,
)
}

#[allow(unused_variables)]
fn get_channel_name(&self, channel_index: u16, input: bool) -> Result<String, Error> {
Err(Error::new(ErrorKind::UnsupportedOperation))
}
}

impl StreamTrait for Stream {
Expand Down
5 changes: 5 additions & 0 deletions src/host/jack/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,11 @@ impl DeviceTrait for Device {
build()
}
}

#[allow(unused_variables)]
fn get_channel_name(&self, channel_index: u16, input: bool) -> Result<String, Error> {
Err(Error::new(ErrorKind::UnsupportedOperation))
}
}

impl PartialEq for Device {
Expand Down
7 changes: 6 additions & 1 deletion src/host/null/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use std::time::Duration;

use crate::{
traits::{DeviceTrait, HostTrait, StreamTrait},
Data, DeviceDescription, DeviceDescriptionBuilder, DeviceId, Error, FrameCount,
Data, DeviceDescription, DeviceDescriptionBuilder, DeviceId, Error, ErrorKind, FrameCount,
InputCallbackInfo, OutputCallbackInfo, SampleFormat, StreamConfig, StreamInstant,
SupportedStreamConfig, SupportedStreamConfigRange,
};
Expand Down Expand Up @@ -104,6 +104,11 @@ impl DeviceTrait for Device {
{
unimplemented!()
}

#[allow(unused_variables)]
fn get_channel_name(&self, channel_index: u16, input: bool) -> Result<String, Error> {
Err(Error::new(ErrorKind::UnsupportedOperation))
}
}

impl HostTrait for Host {
Expand Down
5 changes: 5 additions & 0 deletions src/host/pipewire/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -692,6 +692,11 @@ impl DeviceTrait for Device {
stream.signal_ready();
Ok(stream)
}

#[allow(unused_variables)]
fn get_channel_name(&self, channel_index: u16, input: bool) -> Result<String, Error> {
Err(Error::new(ErrorKind::UnsupportedOperation))
}
}

#[derive(Clone, Default)]
Expand Down
5 changes: 5 additions & 0 deletions src/host/pulseaudio/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,11 @@ impl DeviceTrait for Device {
String::from_utf8_lossy(name.as_bytes()),
))
}

#[allow(unused_variables)]
fn get_channel_name(&self, channel_index: u16, input: bool) -> Result<String, Error> {
Err(Error::new(ErrorKind::UnsupportedOperation))
}
}

fn make_sample_spec(config: StreamConfig, format: protocol::SampleFormat) -> protocol::SampleSpec {
Expand Down
5 changes: 5 additions & 0 deletions src/host/wasapi/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,11 @@ impl DeviceTrait for Device {
stream.signal_ready();
Ok(stream)
}

#[allow(unused_variables)]
fn get_channel_name(&self, channel_index: u16, input: bool) -> Result<String, Error> {
Err(Error::new(ErrorKind::UnsupportedOperation))
}
}

struct Endpoint {
Expand Down
5 changes: 5 additions & 0 deletions src/host/webaudio/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,11 @@ impl DeviceTrait for Device {
is_started,
})
}

#[allow(unused_variables)]
fn get_channel_name(&self, channel_index: u16, input: bool) -> Result<String, Error> {
Err(Error::new(ErrorKind::UnsupportedOperation))
}
}

impl Stream {
Expand Down
9 changes: 9 additions & 0 deletions src/platform/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,15 @@ macro_rules! impl_platform_host {
)*
}
}

fn get_channel_name(&self, channel_index: u16, input: bool) -> Result<String, crate::Error> {
match self.0 {
$(
$(#[cfg($feat)])?
DeviceInner::$HostVariant(ref d) => d.get_channel_name(channel_index, input),
)*
}
}
}

impl crate::traits::HostTrait for Host {
Expand Down
24 changes: 24 additions & 0 deletions src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,30 @@ pub trait DeviceTrait: PartialEq + Eq + Hash + Debug + Display {
where
D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static,
E: FnMut(Error) + Send + 'static;

/// Obtain the associated string name for a channel index.
///
/// This method is only implemented for CoreAudio (macOS) and ASIO (Windows). All other
/// backends will return [`ErrorKind::UnsupportedOperation`].
///
/// # Parameters
///
/// * `channel_index` - Channel index to query name for.
/// * `input` - Whether to query an input channel (true) or output channel (false).
///
/// # Errors
///
/// - [`ErrorKind::UnsupportedOperation`] if the backend does not implement channel name
/// queries.
/// - [`ErrorKind::InvalidInput`] if the channel index is out of range for the device,
/// or if the device does not support the requested direction (input/output).
/// - [`ErrorKind::Other`] for unclassifiable backend failures (e.g., the channel name could
/// not be retrieved from the device).
///
/// [`ErrorKind::UnsupportedOperation`]: crate::ErrorKind::UnsupportedOperation
/// [`ErrorKind::InvalidInput`]: crate::ErrorKind::InvalidInput
/// [`ErrorKind::Other`]: crate::ErrorKind::Other
fn get_channel_name(&self, channel_index: u16, input: bool) -> Result<String, Error>;
}

/// A stream created from [`Device`](DeviceTrait), with methods to control it.
Expand Down
Loading