RustAudio / cpal

Cross-platform audio I/O library in pure Rust
Apache License 2.0
2.75k stars 364 forks source link

[alsa] Sample rate could not be set: Invalid argument on ARM SBC #422

Open XANi opened 4 years ago

XANi commented 4 years ago

On ARM SBC (allwinner-h3 based platform, armv7l so 32 bit) I get err: BackendSpecificError { description: "sample rate could not be set: Invalid argument" } when trying to run basically example code from docs. Kernel 5.4, distro is just armbian/debian buster.

The sound is playing fine (tested from mpv at least), and code works on my desktop, so far only difference I spotted is that working config have hw_params:

access: MMAP_INTERLEAVED
format: S16_LE
subformat: STD
channels: 2
rate: 48000 (48000/1)
period_size: 6000
buffer_size: 18000

while the error shows with one that (when played thru mpv) shows

access: MMAP_INTERLEAVED
format: S32_LE
subformat: STD
channels: 2
rate: 44100 (44100/1)
period_size: 882
buffer_size: 4410

(and 48000 rate when I play file using that sample rate) Now it is not that card does not support S16_LE; when I force audio thrudmix plugin I get:

 cat /proc/asound/card0/pcm0p/sub0/hw_params 
access: RW_INTERLEAVED
format: S16_LE
subformat: STD
channels: 2
rate: 44100 (44100/1)
period_size: 448
buffer_size: 11200

Any ideas what might be the cause of that ?

sniperrifle2004 commented 4 years ago

Hmm it's unlikely that the first working config you posted is actually directly coming from cpal, since it doesn't actually do any MMAP_INTERLEAVED output. The same can be said for the working parameters mpv ends up with. So what exactly are the properties of your audio source? Can you play it using aplay?

XANi commented 4 years ago

Hmm it's unlikely that the first working config you posted is actually directly coming from cpal, since it doesn't actually do any MMAP_INTERLEAVED output.

First one is just my desktop via dmix, some bog standard onboard audio

aplay -l:

# aplay -l
**** List of PLAYBACK Hardware Devices ****
card 0: Codec [H3 Audio Codec], device 0: CDC PCM Codec-0 [CDC PCM Codec-0]
  Subdevices: 1/1
  Subdevice #0: subdevice #0
card 1: allwinnerhdmi [allwinner-hdmi], device 0: 1c22800.i2s-i2s-hifi i2s-hifi-0 [1c22800.i2s-i2s-hifi i2s-hifi-0]
  Subdevices: 1/1
  Subdevice #0: subdevice #0

with aplay and empty asound.conf:

# aplay beethoven-5th.wav 
Playing WAVE 'beethoven-5th.wav' : Signed 16 bit Little Endian, Rate 44100 Hz, Stereo
# cat /proc/asound/card0/pcm0p/sub0/hw_params 
access: RW_INTERLEAVED
format: S16_LE
subformat: STD
channels: 2
rate: 44100 (44100/1)
period_size: 5513
buffer_size: 22052

and plays just fine on headphone out. On same setup I get:

Supported hosts:
  [Alsa]
Available hosts:
  [Alsa]
ALSA
  Default Input Device:
    Some("default")
  Default Output Device:
    Some("default")
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: BackendSpecific { err: BackendSpecificError { description: "sample rate could not be set: Invalid argument" } }', src/synth/mod.rs:33:21

code causing that is pretty much copied from docs:

    enumerate();
    let host = cpal::default_host();

    let event_loop = host.event_loop();
    let device = host
        .default_output_device()
        .expect("no output device available");
    let mut supported_formats_range = device
        .supported_output_formats()
        .expect("error while querying formats");
    let format = supported_formats_range
        .next()
        .expect("no supported format?!")
        .with_max_sample_rate();
    let device = host
        .default_output_device()
        .expect("no output device available");
    let stream_id = event_loop.build_output_stream(&device, &format).unwrap(); // this is the src/synth/mod.rs:33:21
    event_loop
        .play_stream(stream_id)
        .expect("failed to play_stream");

and enumerate() is just a helper

fn enumerate() {
    println!("Supported hosts:\n  {:?}", cpal::ALL_HOSTS);
    let available_hosts = cpal::available_hosts();
    println!("Available hosts:\n  {:?}", available_hosts);

    for host_id in available_hosts {
        println!("{}", host_id.name());
        let host = cpal::host_from_id(host_id).unwrap();
        let default_in = host.default_input_device().map(|e| e.name().unwrap());
        let default_out = host.default_output_device().map(|e| e.name().unwrap());

        println!("  Default Input Device:\n    {:?}", default_in);
        println!("  Default Output Device:\n    {:?}", default_out);
    }
}
sniperrifle2004 commented 4 years ago

Thank you. That's really helpful. The fact that aplay is able to play it is telling.

If possible, could you try swapping hw_params.set_channels and hw_params.set_rate here: https://github.com/RustAudio/cpal/blob/b177bcdf22be548763c6bab2ffc83415eb343151/src/host/alsa/mod.rs#L890 It sounds ridiculous, but that might actually make a difference. If that doesn't work could you show the exact format being passed to build_output_stream?

EDIT: Looks like you are on the release version. Well that slightly complicates matters since the event loop is no more. I'll try and come up with a solution. I think swapping those lines is worth trying, but I don't have a device for which the order matters or, more accurately, might matter.

sniperrifle2004 commented 4 years ago

I wrote a quick crude little utility to track this down: https://github.com/sniperrifle2004/alsa-refinement It's focussed on the problem area working with alsa directly. Try and play around with it a bit. Put the parameters cpal is using into it. Play with the order of the function calls and see what happens. If my suspicion is correct we will know and if it isn't this will surely be helpful in pinning down the true cause.

XANi commented 4 years ago

reordering doesn't help. When I add code to print the rate in error I get

 err: BackendSpecificError { description: "sample rate could not be set: 4294967295 Invalid argument" 

the 4294967295 is value of config.sample_rate.0

When I brute-force the rate (replace config.sample_rate.0 to 44100) it stops generating the error and outputs something but of course rate doesn't match

output of the alsa-refinement:

---------------
ACCESS:  MMAP_INTERLEAVED MMAP_NONINTERLEAVED MMAP_COMPLEX RW_INTERLEAVED RW_NONINTERLEAVED
FORMAT:  S8 U8 S16_LE S16_BE U16_LE U16_BE S24_LE S24_BE U24_LE U24_BE S32_LE S32_BE U32_LE U32_BE FLOAT_LE FLOAT_BE FLOAT64_LE FLOAT64_BE MU_LAW A_LAW IMA_ADPCM S20_LE S20_BE U20_LE U20_BE S24_3LE S24_3BE U24_3LE U24_3BE S20_3LE S20_3BE U20_3LE U20_3BE S18_3LE S18_3BE U18_3LE U18_3BE
SUBFORMAT:  STD
SAMPLE_BITS: [4 64]
FRAME_BITS: [4 640000]
CHANNELS: [1 10000]
RATE: [4000 4294967295)
PERIOD_TIME: (166 4096000]
PERIOD_SIZE: (0 4294967295)
PERIOD_BYTES: (0 4294967295)
PERIODS: (0 4294967295]
BUFFER_TIME: [1 4294967295]
BUFFER_SIZE: [1 4294967294]
BUFFER_BYTES: [1 4294967295]
TICK_TIME: ALL
---------------
ACCESS:  RW_INTERLEAVED
FORMAT:  S8 U8 S16_LE S16_BE U16_LE U16_BE S24_LE S24_BE U24_LE U24_BE S32_LE S32_BE U32_LE U32_BE FLOAT_LE FLOAT_BE FLOAT64_LE FLOAT64_BE MU_LAW A_LAW IMA_ADPCM S20_LE S20_BE U20_LE U20_BE S24_3LE S24_3BE U24_3LE U24_3BE S20_3LE S20_3BE U20_3LE U20_3BE S18_3LE S18_3BE U18_3LE U18_3BE
SUBFORMAT:  STD
SAMPLE_BITS: [4 64]
FRAME_BITS: [4 640000]
CHANNELS: [1 10000]
RATE: [4000 4294967295)
PERIOD_TIME: (166 4096000]
PERIOD_SIZE: (0 4294967295)
PERIOD_BYTES: (0 4294967295)
PERIODS: (0 4294967295]
BUFFER_TIME: [1 4294967295]
BUFFER_SIZE: [1 4294967294]
BUFFER_BYTES: [1 4294967295]
TICK_TIME: ALL
---------------
ACCESS:  RW_INTERLEAVED
FORMAT:  S16_LE
SUBFORMAT:  STD
SAMPLE_BITS: 16
FRAME_BITS: [16 160000]
CHANNELS: [1 10000]
RATE: [4000 4294967295)
PERIOD_TIME: (333 4096000]
PERIOD_SIZE: (1 2147483648)
PERIOD_BYTES: (2 4294967295)
PERIODS: (0 2147483647)
BUFFER_TIME: [1 4294967295]
BUFFER_SIZE: [2 2147483647]
BUFFER_BYTES: [4 4294967295]
TICK_TIME: ALL
---------------
ACCESS:  RW_INTERLEAVED
FORMAT:  S16_LE
SUBFORMAT:  STD
SAMPLE_BITS: 16
FRAME_BITS: 32
CHANNELS: 2
RATE: [4000 4294967295)
PERIOD_TIME: (333 2048000]
PERIOD_SIZE: (1 1073741824)
PERIOD_BYTES: (4 4294967295)
PERIODS: (0 1073741823)
BUFFER_TIME: [1 4294967295]
BUFFER_SIZE: [2 1073741823]
BUFFER_BYTES: [8 4294967292]
TICK_TIME: ALL
---------------
ACCESS:  RW_INTERLEAVED
FORMAT:  S16_LE
SUBFORMAT:  STD
SAMPLE_BITS: 16
FRAME_BITS: 32
CHANNELS: 2
RATE: 44100
PERIOD_TIME: (1451 371520)
PERIOD_SIZE: [64 16384]
PERIOD_BYTES: [256 65536]
PERIODS: (0 2048]
BUFFER_TIME: (2902 2972155)
BUFFER_SIZE: [128 131072]
BUFFER_BYTES: [512 524288]
TICK_TIME: ALL
---------------
ACCESS:  RW_INTERLEAVED
FORMAT:  S16_LE
SUBFORMAT:  STD
SAMPLE_BITS: 16
FRAME_BITS: 32
CHANNELS: 2
RATE: 44100
PERIOD_TIME: (23219 23220)
PERIOD_SIZE: 1024
PERIOD_BYTES: 4096
PERIODS: [2 128]
BUFFER_TIME: (46439 2972155)
BUFFER_SIZE: [2048 131072]
BUFFER_BYTES: [8192 524288]
TICK_TIME: ALL
---------------
ACCESS:  RW_INTERLEAVED
FORMAT:  S16_LE
SUBFORMAT:  STD
SAMPLE_BITS: 16
FRAME_BITS: 32
CHANNELS: 2
RATE: 44100
PERIOD_TIME: (23219 23220)
PERIOD_SIZE: 1024
PERIOD_BYTES: 4096
PERIODS: 4
BUFFER_TIME: (92879 92880)
BUFFER_SIZE: 4096
BUFFER_BYTES: 16384
TICK_TIME: ALL
Refinement succesful
sniperrifle2004 commented 4 years ago

Well that maximum rate is not going to work. It's an upper bound, but not inclusive. Even if such a value would work it's clearly not desirable - Impressive though: 4GHz audio. Looks like fixing this properly will require changes to supported_output_formats and probably its input formats counterpart. Maybe even restricting the sample rates to a reasonable testable range of values all the time. You could try using device.default_output_config() instead, which doesn't wind up actually using the maximum sample rate. This is also worth a go:

let format = ...; // Either default_output_config or what you are doing now
// Set the audio source rate here. You could also append this to the
// above instead of with_max_sample_rate();
format = format.with_sample_rate(44_100); 

EDIT: Perhaps the example code in question should restrict the sample rate to something more reasonable if the maximum is larger than say 48kHz.

XANi commented 4 years ago

Thanks for a hint. So, it is just a bug with sample rate detection ? At the very least it could use better error messages

EDIT: Perhaps the example code in question should restrict the sample rate to something more reasonable if the maximum is larger than say 48kHz.

192 kHz is reasonably common in pro equipment. But yeah, anything above 1MHz is very unlikely to be a valid value for autodetection.

sniperrifle2004 commented 4 years ago

Yes, due to poor assumptions. Admittedly the not inclusive upper bound your device advertises is not a good starting point even if you are aware such a bound may be returned, but as the name implies supported_output_formats should return things that are supported. I agree it would be helpful if the error message at least included the value that caused the failure. I don't yet see a way to easily do that though. I'd prefer to not encounter that error instead ie. the supported_output_formats are truly known to work. That will be hard if not impossible to guarantee though so better error messages should definitely happen at some point.

192 kHz is reasonably common in pro equipment. But yeah, anything above 1MHz is very unlikely to be a valid value for autodetection.

I figured 48kHz was a reasonable example value. It's something that you can see and change with relative ease if necessary. High sample rates have a cost and for most use cases it isn't worth it, but that example value would wind up in all kinds of projects.

XANi commented 4 years ago

The only way to know would probably be to try opening it and retrying with different values if it fails but that's pretty heavy and maybe not something people expect.

192 kHz is reasonably common in pro equipment. But yeah, anything above 1MHz is very unlikely to be a valid value for autodetection.

I figured 48kHz was a reasonable example value. It's something that you can see and change with relative ease if necessary. High sample rates have a cost and for most use cases it isn't worth it, but that example value would wind up in all kinds of projects.

That's a good point, if someone relies on autodetection they probably also do not care about picking highest possible quality mode on the hardware it is running. Might make sense to have .with_default_sample_rate() for the "dont care, play my audio" cases and leave "with_max_sample_rate" to basically do min(192000,max_sample_rate) for those that do want it.

I'm making a toy synth so going for max rate ( = lowest latency) made sense for me so I just kinda copied examples...

sniperrifle2004 commented 4 years ago

I may have misunderstood you, but latency (as I know it ie. https://en.wikipedia.org/wiki/Latency_(audio)) is more related to buffering and processing speed then the sample rate (In alsa those roughly translate to the buffer and period size respectively. Unfortunately it is not currently possible to influence those parameters in cpal, but #401 will change that). I can't really explain the distinction very well, but audacity does have a page about sample rates and what they actually represent: https://manual.audacityteam.org/man/sample_rates.html

I also just noticed that those bounds on the rate exactly match the bounds the plug plugin returns here. And the bcm2835 (HDMI) on my Pi also returns those rate bounds, but inclusive (Also due to the plug plugin). ~It doesn't feel right to place arbitrary restrictions on the maximum rate, but I don't really see another way to ensure that with_max_sample_rate() is actually useful. 768kHz should probably be good enough though if someone really wants to record/play some ultrasound for the use cases audacity mention.~

EDIT: I abandoned "solving" this. #368 contains a good explanation.

hugovdm commented 4 years ago

Not surprisingly, same problem on a Raspberry Pi (config.sample_rate being 2^32-1).