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

alsa_snd_pcm_hw_params() on Ubuntu 20.04 started returning an unexpected -ENOMEM #125

Closed
philburk opened this issue Mar 13, 2021 · 11 comments
Assignees
Labels
bug Something isn't working

Comments

@philburk
Copy link

This seems to be a regression in a recent release.

It was reported on the PortAudio GitHub repo. See here for more details:

PortAudio/portaudio#526

@takaswie
Copy link
Member

Hi,

I can regenerate the same bug in Ubuntu 20.04 (amd64).

With strace(1), ENOMEM comes from a call of ioctl(2) with SNDRV_PCM_IOCTL_HW_PARAMS. However it depends on sound card. In short, I have a device in which I got no ENOMEM in the call with my FireOne (audio and music unit on IEEE 1394 bus, handled by ALSA OXFW driver).

$ cat /proc/asound/cards
 0 [Generic        ]: HDA-Intel - HD-Audio Generic
                      HD-Audio Generic at 0xfcc88000 irq 79
 1 [Generic_1      ]: HDA-Intel - HD-Audio Generic
                      HDAudio-Gigabyte-ALC1220DualCodecs
 2 [FireOne        ]: OXFW - FireOne
                      TASCAM FireOne (OXFW971 0105), GUID 00022e0200000007 at fw1.0, S400

I guess that the issue is specific to ALSA Intel HDA driver, at least.

I write another test program, referring to source of PortAudio19 (portaudio19-19.6.0):

$ cat /tmp/test.c
#include <stdio.h>
#include <stdlib.h>

#include <alsa/asoundlib.h>

int main()
{
    snd_output_t *logger;
    snd_pcm_t *handle;
    snd_pcm_hw_params_t *hw_params;
    unsigned int rate;
    int err;

    err = snd_output_stdio_attach(&logger, stderr, 0);
    if (err < 0)
        return EXIT_FAILURE;

    err = snd_pcm_open(&handle, "hw:0,3", SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
    if (err < 0)
        goto error_logger;

    snd_pcm_hw_params_alloca(&hw_params);
    err = snd_pcm_hw_params_any(handle, hw_params);
    if (err < 0)
        goto error_handle;

    rate = 48000;
    err = snd_pcm_hw_params_set_rate_near(handle, hw_params, &rate, NULL);
    if (err < 0)
        goto error_handle;

    err = snd_pcm_hw_params_set_channels(handle, hw_params, 2);
    if (err < 0)
        goto error_handle;

    err = snd_pcm_hw_params_set_format(handle, hw_params, SND_PCM_FORMAT_S16_LE);
    if (err < 0)
        goto error_handle;

    fprintf(stderr, "Before:\n");
    snd_pcm_hw_params_dump(hw_params, logger);

    err = snd_pcm_hw_params(handle, hw_params);
    if (err < 0)
        fprintf(stderr, "snd_pcm_hw_params(): %d\n", err);

    fprintf(stderr, "After:\n");
    snd_pcm_hw_params_dump(hw_params, logger);

    if (err < 0)
        goto error_handle;

    snd_pcm_close(handle);

    return EXIT_SUCCESS;
error_handle:
    snd_pcm_close(handle);
error_logger:
    snd_output_close(logger);
    return EXIT_FAILURE;
}

The hw:0,3 is pcm device in which I got ENOMEM in my environment. The result:

$ gcc -o /tmp/test /tmp/test.c -lasound
$ /tmp/test
Before:
ACCESS:  MMAP_INTERLEAVED RW_INTERLEAVED
FORMAT:  S16_LE
SUBFORMAT:  STD
SAMPLE_BITS: 16
FRAME_BITS: 32
CHANNELS: 2
RATE: 48000
PERIOD_TIME: (666 89000000]
PERIOD_SIZE: [32 4272000]
PERIOD_BYTES: [128 17088000]
PERIODS: [2 32]
BUFFER_TIME: (1333 178000000]
BUFFER_SIZE: [64 8544000]
BUFFER_BYTES: [256 34176000]
TICK_TIME: ALL
snd_pcm_hw_params(): -12
After:
ACCESS:  MMAP_INTERLEAVED
FORMAT:  S16_LE
SUBFORMAT:  STD
SAMPLE_BITS: 16
FRAME_BITS: 32
CHANNELS: 2
RATE: 48000
PERIOD_TIME: (5933333 5933334)
PERIOD_SIZE: 284800
PERIOD_BYTES: 1139200
PERIODS: 30
BUFFER_TIME: 178000000
BUFFER_SIZE: 8544000
BUFFER_BYTES: 34176000
TICK_TIME: 0

In the hardware parameter, the size of buffer is decided to 34176000 bytes (34MB). This is too large and seems to be a bug of ALSA Intel HDA driver since usually drivers should limit the maximum size of buffer to avoid allocation error. I'll contact to developer of Intel HDA driver in alsa-devel.

The request to limit the maximum size of buffer from userspace application is better workaround at present:

$ diff -u src/hostapi/alsa/pa_linux_alsa.c.orig src/hostapi/alsa/pa_linux_alsa.c 
--- src/hostapi/alsa/pa_linux_alsa.c.orig	2021-03-13 17:54:54.520167673 +0900
+++ src/hostapi/alsa/pa_linux_alsa.c	2021-03-13 17:55:52.304748119 +0900
@@ -1754,6 +1754,7 @@
     unsigned int numHostChannels;
     PaSampleFormat hostFormat;
     snd_pcm_hw_params_t *hwParams;
+    snd_pcm_uframes_t limitation;
     alsa_snd_pcm_hw_params_alloca( &hwParams );
 
     if( !parameters->hostApiSpecificStreamInfo )
@@ -1788,6 +1789,19 @@
     /* Some specific hardware (reported: Audio8 DJ) can fail with assertion during this step. */
     ENSURE_( alsa_snd_pcm_hw_params_set_format( pcm, hwParams, Pa2AlsaFormat( hostFormat ) ), paUnanticipatedHostError );
 
+    /*
+     * Intel HDA driver doesn't set PCM rule to limit maximum size of buffer.
+     * This brings request for too large buffer size and causes memory allocation
+     * error in ALSA PCM core, at least Linux kernel 5.8. As a workaround, limit
+     * buffer size up to 10 msec.
+     */
+    limitation = alsa_snd_pcm_format_size(SND_PCM_FORMAT_S16_LE, 48000 * 2 / 100);
+    if( alsa_snd_pcm_hw_params_set_buffer_size_near( pcm, hwParams, limitation ) < 0 )
+    {
+        result = paBufferTooBig;
+        goto error;
+    }
+
     {
         /* It happens that this call fails because the device is busy */
         int ret = 0;

Thanks

@tiwai
Copy link
Contributor

tiwai commented Mar 13, 2021

Does the kernel set CONFIG_SND_HDA_PREALLOC_SIZE to non-zero? If it's set to zero, it means unlimited, hence such a problem can occur. The default value is 2048 for x86.

@takaswie
Copy link
Member

Does the kernel set CONFIG_SND_HDA_PREALLOC_SIZE to non-zero? If it's set to zero, it means unlimited, hence such a problem can occur. The default value is 2048 for x86.

$ grep CONFIG_SND_HDA_PREALLOC_SIZE /boot/config-5.8.0-44-generic 
CONFIG_SND_HDA_PREALLOC_SIZE=0

Oh, exactly... It seems to be an issue in distro side. I'll report it to the project.

(I miss David Henningsson...)

@takaswie
Copy link
Member

@philburk In the above message I propose a patch to portaudio as workaround, however we have another workaround. The preallocation size can be confgured via procfs node and we can suppress the issue by configure non-zero value.

In my environment, your test runs:

$ ./test1
...
ALSA:

HD-Audio Generic: HDMI 0 (hw:0,3):
  inputs:  0
  outputs: 2
Expression 'ret' failed in 'src/hostapi/alsa/pa_linux_alsa.c', line: 1812
  480000Hz 2ch: NOPE

HD-Audio Generic: HDMI 1 (hw:0,7):
  inputs:  0
  outputs: 8
Expression 'ret' failed in 'src/hostapi/alsa/pa_linux_alsa.c', line: 1812
  480000Hz 2ch: NOPE

HD-Audio Generic: HDMI 2 (hw:0,8):
  inputs:  0
  outputs: 8
Expression 'ret' failed in 'src/hostapi/alsa/pa_linux_alsa.c', line: 1812
  480000Hz 2ch: NOPE

HD-Audio Generic: ALC1220 Analog (hw:1,0):
  inputs:  2
  outputs: 6
Expression 'ret' failed in 'src/hostapi/alsa/pa_linux_alsa.c', line: 1812
  480000Hz 2ch: NOPE

HD-Audio Generic: ALC1220 Digital (hw:1,1):
  inputs:  0
  outputs: 2
Expression 'ret' failed in 'src/hostapi/alsa/pa_linux_alsa.c', line: 1812
  480000Hz 2ch: NOPE

HD-Audio Generic: ALC1220 Analog (hw:1,4):
  inputs:  2
  outputs: 2
Expression 'ret' failed in 'src/hostapi/alsa/pa_linux_alsa.c', line: 1812
  480000Hz 2ch: NOPE

FireOne: - (hw:2,0):
  inputs:  2
  outputs: 2
  480000Hz 2ch: OK
...

Applying the workaround:

$ for i in $(find /proc/asound -name sub0 | xargs find  | grep prealloc | grep -v max); do echo $i; cat $i; done
/proc/asound/card0/pcm3p/sub0/prealloc
0
/proc/asound/card0/pcm7p/sub0/prealloc
0
/proc/asound/card0/pcm8p/sub0/prealloc
0
/proc/asound/card1/pcm0c/sub0/prealloc
0
/proc/asound/card1/pcm0p/sub0/prealloc
0
/proc/asound/card1/pcm1p/sub0/prealloc
0
/proc/asound/card1/pcm2c/sub0/prealloc
0
/proc/asound/card1/pcm4c/sub0/prealloc
0
/proc/asound/card1/pcm4p/sub0/prealloc
0
$ sudo -i
# for i in $(find /proc/asound -name sub0 | xargs find  | grep prealloc | grep -v max); do echo 64 > $i; done
# exit
$ for i in $(find /proc/asound -name sub0 | xargs find  | grep prealloc | grep -v max); do echo $i; cat $i; done
/proc/asound/card0/pcm3p/sub0/prealloc
64
/proc/asound/card0/pcm7p/sub0/prealloc
64
/proc/asound/card0/pcm8p/sub0/prealloc
64
/proc/asound/card1/pcm0c/sub0/prealloc
64
/proc/asound/card1/pcm0p/sub0/prealloc
64
/proc/asound/card1/pcm1p/sub0/prealloc
64
/proc/asound/card1/pcm2c/sub0/prealloc
64
/proc/asound/card1/pcm4c/sub0/prealloc
64
/proc/asound/card1/pcm4p/sub0/prealloc
64

Then your test runs successfully:

$ ./test1
...
ALSA:

HD-Audio Generic: HDMI 0 (hw:0,3):
  inputs:  0
  outputs: 2
  480000Hz 2ch: OK

HD-Audio Generic: HDMI 1 (hw:0,7):
  inputs:  0
  outputs: 8
  480000Hz 2ch: OK

HD-Audio Generic: HDMI 2 (hw:0,8):
  inputs:  0
  outputs: 8
  480000Hz 2ch: OK

HD-Audio Generic: ALC1220 Analog (hw:1,0):
  inputs:  2
  outputs: 6
  480000Hz 2ch: OK

HD-Audio Generic: ALC1220 Digital (hw:1,1):
  inputs:  0
  outputs: 2
  480000Hz 2ch: OK

HD-Audio Generic: ALC1220 Analog (hw:1,4):
  inputs:  2
  outputs: 2
  480000Hz 2ch: OK

FireOne: - (hw:2,0):
  inputs:  2
  outputs: 2
  480000Hz 2ch: OK
...

Regards

@philburk
Copy link
Author

Great work. Thanks for the quick analysis and workaround. I will ask the PortAudio developer to try that procfs trick.

It may not be practical to ask all the affected users to apply that procfs command. Most are just using apps and are not programmers.

Do you think it might be worth calling alsa_snd_pcm_hw_params_set_buffer_size_near() in PortAudio as a workaround? I think it just sets the upper limit so should not have any effect in most cases.

@takaswie
Copy link
Member

@philburk

Do you think it might be worth calling alsa_snd_pcm_hw_params_set_buffer_size_near() in PortAudio as a workaround? I think it just sets the upper limit so should not have any effect in most cases.

In my opinion, yes.

The TestParameters() function in src/hostapi/alsa/pa_linux_alsa.c just tests availability of
the set of rate, channel, and sample format parameters. It doesn't handle PCM frame actually,
and closes handle for PCM device immediately. It's reasonable to limit the maximum
size of buffer to 10msec or so, to avoid any trouble about large buffer allocation regardless
of target device.

Regards

@takaswie takaswie self-assigned this Mar 14, 2021
@takaswie takaswie added the bug Something isn't working label Mar 14, 2021
@theoveenker
Copy link

The workaround works on my test system (tested 15 frequent sample rates with all supported channel combinations, input and output). I first got a core dump after applying the fix. Apparently the 3rd arg in alsa_snd_pcm_hw_params_set_buffer_size_near() must be an address, so please insert an ampersand there. Then it works.

The procfs solution works too. Maybe one should only set this for [sub]devices that have a current prealloc size of 0.

@takaswie
Copy link
Member

@theoveenker

Apparently the 3rd arg in alsa_snd_pcm_hw_params_set_buffer_size_near() must be an address, so please insert an ampersand there. Then it works.

Oops. I'm sorry for the bug. My original patch is cleared once dpkg-buildpackage run, then I needed to rewrite it and put the mistake...

I note that just for limitation of maximum size of PCM buffer snd_pcm_hw_params_set_buffer_time_max() is available in micro seconds unit as well:

    limitation = 10 * 1000;	// 10 milli-seconds by micro-second unit.
    err = snd_pcm_hw_params_set_buffer_time_max(handle, hw_params, &limitation, NULL);
    if (err < 0)
	    goto error_handle;

@takaswie
Copy link
Member

I note that fix is proposed[1] for ALSA Intel HDA driver.

[1] https://mailman.alsa-project.org/pipermail/alsa-devel/2021-March/182424.html

@takaswie
Copy link
Member

takaswie commented Mar 20, 2021

The fix is now applied, and goes to Linux kernel v5.13.

https://mailman.alsa-project.org/pipermail/alsa-devel/2021-March/182480.html

@perexg
Copy link
Member

perexg commented Apr 7, 2021

It seems all resolved now.

@perexg perexg closed this as completed Apr 7, 2021
kleinerm added a commit to kleinerm/Psychtoolbox-3 that referenced this issue Oct 31, 2021
…13+ again.

The relevant bug has been fixed in Linux 5.13 and later, so the issue is
resolved for Ubuntu 21.10 (Linux 5.13), and future Ubuntu 20.04.4-LTS
(Linux 5.13) with eta around Jan/Feb 2022, and future Ubuntu 22.04-LTS
and other autumn 2021 and later distributions.

While it would be possible to also add a workaround to a future Portaudio
v19.8.0 release, implementing the following linked proposal, which would be identical
to the fix i had in mind...

alsa-project/alsa-lib#125 (comment)

... it probably doesn't make much sense wrt. distro release timing.
A portaudio 19.8 release would end up in Ubuntu 22.04-LTS the earliest,
but that one will ship with Linux 5.14 or later, so the workaround would
not be needed. And even 20.04.4-LTS will be released early 2022 before
a fix could be backported the earliest.

Therefore, we let time solve this problem for us soon. If running on
Linux 5.13+ we enable the check again, otherwise we skip the check,
just as in the past.

-> Tested on Linux 5.13 and Linux 5.11 on Ubuntu 20.04.3-LTS.
colugomusic added a commit to colugomusic/portaudio that referenced this issue Mar 12, 2022
This is apparently fixed in Linux kernel 5.13
alsa-project/alsa-lib#125
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

5 participants