Alsa-Devel Archive on lore.kernel.org
 help / color / mirror / Atom feed
From: Zhang Boyang <zhangboyang.id@gmail.com>
To: Geraldo Nascimento <geraldogabriel@gmail.com>
Cc: alsa-devel@alsa-project.org
Subject: Re: [BUG?] Setting (start_threshold > stop_threshold) makes snd_pcm_writei(a_small_buffer) XRUN immediately
Date: Mon, 4 Oct 2021 17:48:49 +0800	[thread overview]
Message-ID: <9bd7f88a-d2a3-b064-2ca7-7305012ef679@gmail.com> (raw)
In-Reply-To: <YVfj7SA4uhYKWTH6@geday>

[-- Attachment #1: Type: text/plain, Size: 3395 bytes --]

On 2021/10/2 12:45, Geraldo Nascimento wrote:
> On Fri, Oct 01, 2021 at 10:01:34PM -0300, Geraldo Nascimento wrote:
>> On Fri, Oct 01, 2021 at 08:33:23PM -0300, Geraldo Nascimento wrote:
>>> On Sat, Oct 02, 2021 at 01:31:12AM +0800, Zhang Boyang wrote:
>>>> Hello,
>>>
>>> Hello, Zhang!
>>>
>>>>
>>>>     I'm using ALSA to develop an audio-streaming application. I try to
>>>> use start_threshold and stop_threshold in combination with small
>>>> buffers. However, I think I probably found a bug about this.
>>>>     I'm setting start_threshold=100 and stop_threshold=50. I'm also using
>>>> a buffer of 44 frames. When I call
>>>> snd_pcm_writei(the_small_44_frames_buffer), pcm state came to XRUN from
>>>> PREPARED directly. I think this is a bug because the stream hasn't
>>>> started. It's hard to say a xrun occurred while stream not started.
>>>>     I'm wondering if this is a ALSA-bug or a misuse of ALSA. A simple bug
>>>> test program is attached.
>>>
>>> No, I don't think it's a bug. You're bound to run into problems with a
>>> period size of only 44 frames.
>>>
>>> Moreover, working with the code you provided, I was able to get a RUNNING
>>> state without XRUNs with a period size of 4410 frames (100 milliseconds of
>>> audio) but I had to comment out snd_pcm_sw_params_set_stop_threshold() for
>>> it to work or I'd have those instant XRUNs.
>>>
>>> That's how snd_pcm_sw_params_set_stop_threshold() is supposed to work by
>>> the way. It creates a XRUN once the threshold is hit.
>>
>> Oh, and snd_pcm_sw_params_set_stop_threshold() is a little
>> counter-intuitive. The threshold you give the function is compared to
>> the available free space in the ring buffer, not the filled space.
>>
>> That's why lowering the stop threshold makes XRUNs more likely...
>>
>> Thanks,
>> Geraldo Nascimento
> 
> Zhang,
> 
> I was able to make your code work in the end, even with 44 frames
> writes, which is unadvisable since it eats too much CPU specially if
> they are low-end like the ARM64 box I'm writing you from.
> 
> The trick was to use high values such as 500000. Lower values such as
> 250000 were enough to trigger XRUNs in my machine. Of course you can set
> it to -1 and it will ignore XRUNs but that's a bit like setting it to
> 16777216 or 4096 * 4096 which is a hardcoded limit in alsa-lib, I
> believe.
> 
> And plainly ignoring XRUNs may sound bad under load, believe me.
> 
> Thank you,
> Geraldo Nascimento
> 
> 

Hi,

Thank you for your reply.

I realized my mistake about the meaning of stop_threshold. The 
stop_threshold should be (bufsz-50), where (bufsz = alsa buffer size). I 
fixed my code and new code is attached.

However, the problem still exists. I'm trying to minimize audio latency, 
so I must use a small buffer. In fact, the problem is not related to the 
absoulte size of buffer, but the relationship between the size of 
start_threshold, stop_threshold and the buffer. The problem still exists 
even using manual start mode (i.e. start_threshold>bufsz).

Currently, I'm using a workaround. After PREPARED or XRUN'd, I manually 
buffer incoming audio frames, and when (n_buffered_frames >= 
start_threshold && n_buffered_frames >= bufsz-stop_threshold), I write 
them in a single write() call. Note that the write must in whole, any 
short write may result in immediate XRUN.

Anyway, Thank you for your advice. Have a nice day. :)

Zhang Boyang

[-- Attachment #2: start_stop_threshold_bug.c --]
[-- Type: text/x-csrc, Size: 5350 bytes --]

#define PCM_DEVICE "hw:0,0"

/*

gcc -Wall -o bug start_stop_threshold_bug.c -lasound
./bug

*/

/*
 * Simple sound playback using ALSA API and libasound.
 *
 * Compile:
 * $ cc -o play sound_playback.c -lasound
 *
 * Usage:
 * $ ./play <sample_rate> <channels> <seconds> < <file>
 *
 * Examples:
 * $ ./play 44100 2 5 < /dev/urandom
 * $ ./play 22050 1 8 < /path/to/file.wav
 *
 * Copyright (C) 2009 Alessandro Ghedini <al3xbio@gmail.com>
 * --------------------------------------------------------------
 * "THE BEER-WARE LICENSE" (Revision 42):
 * Alessandro Ghedini wrote this file. As long as you retain this
 * notice you can do whatever you want with this stuff. If we
 * meet some day, and you think this stuff is worth it, you can
 * buy me a beer in return.
 * --------------------------------------------------------------
 */

#include <alsa/asoundlib.h>
#include <stdio.h>

int main(int argc, char **argv) {
    unsigned int pcm, tmp;
    unsigned rate;
    int channels, seconds;
    snd_pcm_t *pcm_handle;
    snd_pcm_hw_params_t *params;
    snd_pcm_uframes_t frames, bufsz;
    char *buff;
    int buff_size, loops;


    rate     = 44100;
    channels = 2;
    seconds  = 10;

    /* Open the PCM device in playback mode */
    if ((pcm = snd_pcm_open(&pcm_handle, PCM_DEVICE,
                    SND_PCM_STREAM_PLAYBACK, 0) < 0)) {
        printf("ERROR: Can't open \"%s\" PCM device. %s\n",
                    PCM_DEVICE, snd_strerror(pcm));
        abort();
    }

    /* Allocate parameters object and fill it with default values*/
    snd_pcm_hw_params_alloca(&params);

    snd_pcm_hw_params_any(pcm_handle, params);

    /* Set parameters */
    snd_pcm_hw_params_set_rate_resample(pcm_handle, params, 0);

    if ((pcm = snd_pcm_hw_params_set_access(pcm_handle, params,
                    SND_PCM_ACCESS_RW_INTERLEAVED) < 0)) {
        printf("ERROR: Can't set interleaved mode. %s\n", snd_strerror(pcm));
        abort();
    }

    if ((pcm = snd_pcm_hw_params_set_format(pcm_handle, params,
                        SND_PCM_FORMAT_S16_LE) < 0)) {
        printf("ERROR: Can't set format. %s\n", snd_strerror(pcm));
        abort();
    }

    if ((pcm = snd_pcm_hw_params_set_channels(pcm_handle, params, channels) < 0)) {
        printf("ERROR: Can't set channels number. %s\n", snd_strerror(pcm));
        abort();
    }

    if ((pcm = snd_pcm_hw_params_set_rate_near(pcm_handle, params, &rate, 0) < 0)) {
        printf("ERROR: Can't set rate. %s\n", snd_strerror(pcm));
        abort();
    }

    /* Write parameters */
    if ((pcm = snd_pcm_hw_params(pcm_handle, params) < 0)) {
        printf("ERROR: Can't set harware parameters. %s\n", snd_strerror(pcm));
        abort();
    }
    
    if ((pcm = snd_pcm_hw_params_get_buffer_size(params, &bufsz) < 0)) {
        printf("ERROR: Can't get buffer size. %s\n", snd_strerror(pcm));
        abort();
    }


    int start_threshold = 100;
    int stop_threshold = bufsz - 50;

    snd_pcm_sw_params_t *swparams;
    snd_pcm_sw_params_alloca(&swparams);
    pcm = snd_pcm_sw_params_current(pcm_handle, swparams);
    if (pcm < 0) {
        printf("Unable to determine current swparams: %s", snd_strerror(pcm));
        abort();
    }
    pcm = snd_pcm_sw_params_set_start_threshold(pcm_handle, swparams, start_threshold);
    if (pcm < 0) {
        printf("Unable to set start threshold mode: %s", snd_strerror(pcm));
        abort();
    }
    pcm = snd_pcm_sw_params_set_stop_threshold(pcm_handle, swparams, stop_threshold);
    if (pcm < 0) {
        printf("Unable to set stop threshold mode: %s", snd_strerror(pcm));
        abort();
    }
    pcm = snd_pcm_sw_params(pcm_handle, swparams);
    if (pcm < 0) {
        printf("Unable to set sw params: %s", snd_strerror(pcm));
        abort();
    }

    /* Resume information */
    printf("PCM name: '%s'\n", snd_pcm_name(pcm_handle));

    printf("PCM state: %s\n", snd_pcm_state_name(snd_pcm_state(pcm_handle)));

    snd_pcm_hw_params_get_channels(params, &tmp);
    printf("channels: %i ", tmp);

    if (tmp == 1)
        printf("(mono)\n");
    else if (tmp == 2)
        printf("(stereo)\n");

    snd_pcm_hw_params_get_rate(params, &tmp, 0);
    printf("rate: %d\n", tmp);

    printf("seconds: %d\n", seconds);

    printf("bufsz: %ld\n", bufsz);
    printf("start_threshold: %d\n", start_threshold);
    printf("stop_threshold: %d\n", stop_threshold);

    frames = 44;
    buff_size = frames * channels * 2 /* 2 -> sample size */;
    buff = (char *) malloc(buff_size);
    memset(buff, 0, buff_size);

    snd_pcm_hw_params_get_period_time(params, &tmp, NULL);

    for (loops = (seconds * 1000000) / tmp; loops > 0; loops--) {
        printf("==== loop ====\n");
        printf("(before write) PCM state: %s\n", snd_pcm_state_name(snd_pcm_state(pcm_handle)));
        pcm = snd_pcm_writei(pcm_handle, buff, frames);
        printf("written %d\n", pcm);
        printf("(after write) PCM state: %s\n", snd_pcm_state_name(snd_pcm_state(pcm_handle)));
        if (pcm == -EPIPE) {
            printf("XRUN.\n");
            snd_pcm_prepare(pcm_handle);
            abort();
        } else if (pcm < 0) {
            printf("ERROR. Can't write to PCM device. %s\n", snd_strerror(pcm));
        }
    }

    snd_pcm_drain(pcm_handle);
    snd_pcm_close(pcm_handle);
    free(buff);

    return 0;
}

  reply	other threads:[~2021-10-04  9:49 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-10-01 17:31 [BUG?] Setting (start_threshold > stop_threshold) makes snd_pcm_writei(a_small_buffer) XRUN immediately Zhang Boyang
2021-10-01 23:33 ` Geraldo Nascimento
2021-10-02  1:01   ` Geraldo Nascimento
2021-10-02  4:45     ` Geraldo Nascimento
2021-10-04  9:48       ` Zhang Boyang [this message]
2021-10-04 21:24         ` Geraldo Nascimento

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=9bd7f88a-d2a3-b064-2ca7-7305012ef679@gmail.com \
    --to=zhangboyang.id@gmail.com \
    --cc=alsa-devel@alsa-project.org \
    --cc=geraldogabriel@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox