cpal
cpal copied to clipboard
Listing devices cannot connect to server
CPAL Version: 0.13.4
Distro: Garuda Dragonized Linux
I don't really know what's causing this issue, but here's the code I've tried and the output of it
use cpal::default_host;
use cpal::traits::{DeviceTrait, HostTrait};
fn main() {
let host = default_host();
println!("{:?}", cpal::available_hosts());
let devices = host
.devices()
.map(|it| it.map(|it| it.name()))
.unwrap()
.collect::<Result<Vec<_>, _>>()
.unwrap();
println!("{:?}", devices);
}
[Alsa]
Cannot connect to server socket err = No such file or directory
Cannot connect to server request channel
jack server is not running or cannot be started
JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock
JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock
Cannot connect to server socket err = No such file or directory
Cannot connect to server request channel
jack server is not running or cannot be started
JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock
JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock
ALSA lib pcm_oss.c:397:(_snd_pcm_oss_open) Cannot open device /dev/dsp
ALSA lib pcm_oss.c:397:(_snd_pcm_oss_open) Cannot open device /dev/dsp
ALSA lib pcm_usb_stream.c:503:(_snd_pcm_usb_stream_open) Unknown field hint
ALSA lib pcm_usb_stream.c:503:(_snd_pcm_usb_stream_open) Unknown field hint
ALSA lib pcm_route.c:877:(find_matching_chmap) Found no matching channel map
ALSA lib pcm_route.c:877:(find_matching_chmap) Found no matching channel map
ALSA lib pcm_route.c:877:(find_matching_chmap) Found no matching channel map
ALSA lib pcm_usb_stream.c:503:(_snd_pcm_usb_stream_open) Unknown field hint
ALSA lib pcm_usb_stream.c:503:(_snd_pcm_usb_stream_open) Unknown field hint
ALSA lib pcm_usb_stream.c:503:(_snd_pcm_usb_stream_open) Unknown field hint
ALSA lib pcm_usb_stream.c:503:(_snd_pcm_usb_stream_open) Unknown field hint
ALSA lib pcm_dmix.c:1035:(snd_pcm_dmix_open) unable to open slave
ALSA lib pcm_usb_stream.c:503:(_snd_pcm_usb_stream_open) Unknown field hint
ALSA lib pcm_usb_stream.c:503:(_snd_pcm_usb_stream_open) Unknown field hint
["pipewire", "pulse", "default", "hdmi:CARD=NVidia,DEV=0", "hdmi:CARD=NVidia,DEV=1", "hdmi:CARD=NVidia,DEV=2", "hdmi:CARD=NVidia,DEV=3", "hdmi:CARD=NVidia,DEV=4", "hdmi:CARD=NVidia,DEV=5", "hdmi:CARD=NVidia,DEV=6", "sysdefault:CARD=Generic", "front:CARD=Generic,DEV=0", "surround21:CARD=Generic,DEV=0", "surround40:CARD=Generic,DEV=0", "surround41:CARD=Generic,DEV=0", "surround50:CARD=Generic,DEV=0", "surround51:CARD=Generic,DEV=0", "surround71:CARD=Generic,DEV=0", "iec958:CARD=Generic,DEV=0", "sysdefault:CARD=Microphone", "front:CARD=Microphone,DEV=0", "surround21:CARD=Microphone,DEV=0", "surround40:CARD=Microphone,DEV=0", "surround41:CARD=Microphone,DEV=0", "surround50:CARD=Microphone,DEV=0", "surround51:CARD=Microphone,DEV=0", "surround71:CARD=Microphone,DEV=0", "iec958:CARD=Microphone,DEV=0", "sysdefault:CARD=C920", "front:CARD=C920,DEV=0"]
Process finished with exit code 0
~Yay! Too much text nobody is gonna read!~
TL:DR (Too Long, Didn't Read): cpal is not able to handle/catch these errors, as neitheralsa-rs
does, becuase they are written to standard error by ALSA, not Rust; which points to the solution, i.e. to catch stderr.
Therefore, I will create an issue/pull request to alsa-rs :D
P.D. If any other sound library, like JACK, has a similar issue, a similar fix can be written.
On the other hand, it's worth noting that cpal ignores whatever of these could be catched by Rust only, in order to only show the available devices, not those who do not work, or error, and yet are known to ALSA (such as JACK audio I/O device, in this case), which is also helpful to know whether an audio device is for input or output, even though it's not an issue.
Walkthrough
I couldn't not write this little story, apologies :sweat_smile:
Unnecessarily, and by curiosity, I tried to list all devices to select instead of the defaults, and was surprised to see many errors, none which lead to panic or an error in Rust (with Result) that could be fixed.
That's how, all day, I tried to catch an error within Rust, in my code, cpal and its dependency alsa-rs, until I realized these were thrown by ALSA, nowhere in Rust where they could be catched or suppressed from standard error.
Without further ado, here's what happens.
Explaination
In Linux, unless specified, cpal defaults to ALSA 1, 2, contrary to what the errors may say [^1]. devices
has to be called so we can fetch all devices, as it is not done upon creation; however, the method only creates a new Devices
3 instance, which we have to iterate over to get each device (next
) 4, which is why the error is thrown even when using count
.
next
calls DeviceHandles::open
5, which tries to open a handle for both playback and capture, and returns if at least one was opened successfully 6, which is useful to know if a device is either for I/O, even though it's determined differently 7, and is better than the current approach.
Anyways, on both directions try_open
is called with PCM::new
from alsa-rs
8, which returns a Result
from open
9 catched through the macro acheck!
10, which calls the Rust bindings generated in alsa-sys
from ALSA.
At the end, the error roots from acheck!
, which catches a error different to the ones printed, and nowhere suppresses the errors that are printed by ALSA to standard error, which means this is not a cpal issue, and must be fixed in alsa-rs.
Solution
Before anything, to temporarily edit alsa-rs code unto a project, rhack
11 can be used to readily download the crate code and set it up on the current project, or otherwise manually with Cargo.toml's [patch]
12 (can set a GitHub repository).
We just have to catch the errors from standard error, which we can either print or not. In fact, a similar issue, which is "how to catch a C library's stderr in Rust" 13, has already been solved, and is used as reference in the following code.
Code
src/main.rs
:
let host = cpal::default_host();
println!("{:?}", cpal::available_hosts());
let devices = host.devices()
.map(|devices| devices.map(|d| d.name())).unwrap()
.collect::<Result<Vec<String>, cpal::DeviceNameError>>().unwrap();
println!("{:?}", devices);
fn catch<F>(mut f: F) -> Result<PCM>
where F: FnMut() -> Result<PCM> {
use std::os::unix::io::FromRawFd;
use std::io::prelude::BufRead;
use std::io::BufReader;
use std::fs::File;
extern "C" {
fn pipe(fd: *mut i32) -> i32;
fn close(fd: i32) -> i32;
fn dup2(old_fd: i32, new_fd: i32) -> i32;
}
const PIPE_READ: usize = 0;
const PIPE_WRITE: usize = 1;
const STDERR_FILENO: i32 = 2;
// Creates a pipe of standard error (2) which writes on a file descriptor that can be read on;
// returns if unsucessful.
let mut pipefd = [-1; 2];
if unsafe { pipe(&mut pipefd[0]) } == -1 {
eprintln!("Unable to create pipe for stderr"); panic!();
}
// Creates a file that reads from the pipe
let file = unsafe {
File::from_raw_fd(pipefd[PIPE_READ])
};
// Redirects the pipe to the file; returns if unsucessful
if unsafe { dup2(pipefd[PIPE_WRITE], STDERR_FILENO) } == -1 {
eprintln!("Unable to redirect pipe to file"); panic!();
}
// Runs the code that writes to stderr
let result = f();
// Prints the first line of the stderr
let reader = BufReader::new(file);
let line = reader.lines().next().unwrap().unwrap();
if line != "" {
println!("stderr: {:?}", line);
}
// Closes stderr
unsafe {
close(pipefd[PIPE_WRITE]);
};
result
}
// ...
pub fn open(name: &CStr, dir: Direction, nonblock: bool) -> Result<PCM> {
// ...
catch(|| {
let result =
acheck!(snd_pcm_open(&mut r, name.as_ptr(), stream, flags)).map(|_| PCM(r, cell::Cell::new(false))); eprintln!(""); result
})
}
This code is only able to catch the first line of the error (which is cleaner), even though I've tried to catch all. This can only be executed within Linux, due to the use of glibc/Linux libraries. The code that creates and prints the file from the pipe can be deleted to not print any error.
Err?
If you're curious of how to log all the errors from the code flow, here's the code:
Mo' code
fn open(name: &str) -> Result<Self, alsa::Error> {
if capture_err.is_some() | playback_err.is_some() {
println!(">> ALSA playback/capture errors: {:?} {:?} in {name}", playback_err, capture_err);
}
if let Some(err) = capture_err.and(playback_err) {
println!("> An error has occurred on both playback/capture: {} in {name}", err); Err(err)
} else {
Ok(handles)
}
}
Optional
pub fn new(name: &str, dir: Direction, nonblock: bool) -> Result<PCM> {
let pcm = Self::open(&CString::new(name).unwrap(), dir, nonblock);
let error = pcm.as_ref().err();
if error.is_some() {
println!(">>> ALSA PCM error: {:?} in {name}", error);
}; pcm
}
cpal/src/host/alsa/enumerate.rs
:
match DeviceHandles::open(&name) {
Ok(handles) => return Some(Device {
name,
handles: Mutex::new(handles),
}),
Err(e) => println!("< ALSA error: {}", e)
}
and here's how it looks (without the optional logs):
Output
[Alsa]
stderr: "Cannot connect to server socket err = No such file or directory"
stderr: "Cannot connect to server socket err = No such file or directory"
>> ALSA playback/capture errors: Some(Error("snd_pcm_open", ENOENT)) Some(Error("snd_pcm_open", ENOENT)) in jack
> An error has occurred on both playback/capture: ALSA function 'snd_pcm_open' failed with error 'ENOENT: No such file or directory' in jack
stderr: "ALSA lib pcm_oss.c:397:(_snd_pcm_oss_open) Cannot open device /dev/dsp"
stderr: "ALSA lib pcm_oss.c:397:(_snd_pcm_oss_open) Cannot open device /dev/dsp"
>> ALSA playback/capture errors: Some(Error("snd_pcm_open", ENOENT)) Some(Error("snd_pcm_open", ENOENT)) in oss
> An error has occurred on both playback/capture: ALSA function 'snd_pcm_open' failed with error 'ENOENT: No such file or directory' in oss
stderr: "ALSA lib pcm_route.c:877:(find_matching_chmap) Found no matching channel map"
stderr: "ALSA lib pcm_route.c:877:(find_matching_chmap) Found no matching channel map"
>> ALSA playback/capture errors: Some(Error("snd_pcm_open", EINVAL)) Some(Error("snd_pcm_open", EINVAL)) in surround21:CARD=PCH,DEV=0
> An error has occurred on both playback/capture: ALSA function 'snd_pcm_open' failed with error 'EINVAL: Invalid argument' in surround21:CARD=PCH,DEV=0
stderr: "ALSA lib pcm_route.c:877:(find_matching_chmap) Found no matching channel map"
stderr: "ALSA lib pcm_route.c:877:(find_matching_chmap) Found no matching channel map"
>> ALSA playback/capture errors: Some(Error("snd_pcm_open", EINVAL)) Some(Error("snd_pcm_open", EINVAL)) in surround41:CARD=PCH,DEV=0
> An error has occurred on both playback/capture: ALSA function 'snd_pcm_open' failed with error 'EINVAL: Invalid argument' in surround41:CARD=PCH,DEV=0
stderr: "ALSA lib pcm_route.c:877:(find_matching_chmap) Found no matching channel map"
stderr: "ALSA lib pcm_route.c:877:(find_matching_chmap) Found no matching channel map"
>> ALSA playback/capture errors: Some(Error("snd_pcm_open", EINVAL)) Some(Error("snd_pcm_open", EINVAL)) in surround50:CARD=PCH,DEV=0
> An error has occurred on both playback/capture: ALSA function 'snd_pcm_open' failed with error 'EINVAL: Invalid argument' in surround50:CARD=PCH,DEV=0
>> ALSA playback/capture errors: Some(Error("snd_pcm_open", EACCES)) Some(Error("snd_pcm_open", EACCES)) in usbstream:CARD=PCH
> An error has occurred on both playback/capture: ALSA function 'snd_pcm_open' failed with error 'EACCES: Permission denied' in usbstream:CARD=PCH
>> ALSA playback/capture errors: None Some(Error("snd_pcm_open", ENOENT)) in hdmi:CARD=NVidia,DEV=0
>> ALSA playback/capture errors: None Some(Error("snd_pcm_open", ENOENT)) in hdmi:CARD=NVidia,DEV=1
>> ALSA playback/capture errors: None Some(Error("snd_pcm_open", ENOENT)) in hdmi:CARD=NVidia,DEV=2
>> ALSA playback/capture errors: None Some(Error("snd_pcm_open", ENOENT)) in hdmi:CARD=NVidia,DEV=3
>> ALSA playback/capture errors: Some(Error("snd_pcm_open", EACCES)) Some(Error("snd_pcm_open", EACCES)) in usbstream:CARD=NVidia
> An error has occurred on both playback/capture: ALSA function 'snd_pcm_open' failed with error 'EACCES: Permission denied' in usbstream:CARD=NVidia
["lavrate", "samplerate", "speexrate", "pipewire", "pulse", "speex", "upmix", "vdownmix", "default", "sysdefault:CARD=PCH", "front:CARD=PCH,DEV=0", "surround40:CARD=PCH,DEV=0", "surro
und51:CARD=PCH,DEV=0", "surround71:CARD=PCH,DEV=0", "hdmi:CARD=NVidia,DEV=0", "hdmi:CARD=NVidia,DEV=1", "hdmi:CARD=NVidia,DEV=2", "hdmi:CARD=NVidia,DEV=3"]
In fact, the errors are thrown when cpal cannot open a device with neither playback or capture. However, it's also possible to catch the individual errors when either one of those do not work, from what I remember.
I've learnt some new things, documented the whole process, and fixed an issue (or two), didn't hurt, right?
[^1]: As mentioned, this is becuase JACK seems to create an audio device that is detected by ALSA, as shown by output of the errors in Err?.