Search code examples
calsalibsndfile

Alsa, unable to play the same track twice in sequence


I have developed some code to play audio (wave files). When I try to play the same file twice, it plays fine the first time but always fails when called the second time.

Below is the one-time audio init function.

/* PCM interface objects */
static snd_pcm_t *pcm_handle;
static snd_pcm_uframes_t frames;
static int rate = 44100, channels = 2;

void audio_init(void)
{
    snd_pcm_hw_params_t *params;
    unsigned int pcm = 0;

    /* 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));

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

    /* Interleaved mode */
    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));

    /* Signed 16 bit little-endian format */
    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));

    /* Two channels, stereo */
    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));

    /* 44100 bits/second sampling rate (CD quality) */
    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));

    /* Write parameters to the driver */
    if ((pcm = snd_pcm_hw_params(pcm_handle, params) < 0))
        printf("ERROR: Can't set harware parameters. %s\n", snd_strerror(pcm));

    /* 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)));

    int tmp = 0;
    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 bps\n", tmp);

    /* Allocate large enough buffer to hold single period (No. of samples) */
    snd_pcm_hw_params_get_period_size(params, &frames, 0);
    printf("period size = %d frames\n", (int)frames);

    /* Get the period time */
    snd_pcm_hw_params_get_period_time(params, &tmp, NULL);
    printf("period time = %d us\n", tmp);

    audio_playTrack(AUDIO_HOMING_TRACK);
    sleep(1);
    printf("PCM state: %s\n", snd_pcm_state_name(snd_pcm_state(pcm_handle)));
    audio_playTrack(AUDIO_ARRIVE_TRACK);
    sleep(1);
    printf("PCM state: %s\n", snd_pcm_state_name(snd_pcm_state(pcm_handle)));
}

Below is the function I call twice at the end of the audio_init( ).

void audio_playTrack(char *audioFile)
{
    short int* buf = NULL;
    int readcount = 0;
    int pcmrc;
    SNDFILE *infile = NULL;
    SF_INFO sfinfo;

    infile = sf_open(audioFile, SFM_READ, &sfinfo);

    printf("\r\n");
    printf("[FILE]Channels      : %d\n", sfinfo.channels);
    printf("[FILE]Sample rate   : %d\n", sfinfo.samplerate);
    printf("[FILE]Sections      : %d\n", sfinfo.sections);
    printf("[FILE]Format        : %d\n", sfinfo.format);
    printf("\r\n");

    printf("+------------------\n");
    printf("### AUDIO BEGIN ###\n");
    printf("+------------------\n\n");
    buf = malloc(frames * sfinfo.channels * sizeof(int));
    while ((readcount = sf_readf_short(infile, buf, frames))>0)
    {
        pcmrc = snd_pcm_writei(pcm_handle, buf, readcount);
        if (pcmrc == -EPIPE)
        {
            printf("Underrun!\n");
            snd_pcm_prepare(pcm_handle);
        }
        else if (pcmrc < 0)
        {
            printf("Error writing to PCM device: %s\n", snd_strerror(pcmrc));
        }
        else if (pcmrc != readcount)
        {
            printf("PCM write differs from PCM read.\n");
        }
    }
    printf("+------------------\n");
    printf("### AUDIO END ###\n");
    printf("+------------------\n\n");
    free(buf);
    sf_close (infile);
}

The call to first audio_playTrack() works. The second one does not work as the sf_readf_short returns 0 frames and does not even enter the while loop.

I'd like to be able to stop audio and re-start audio anytime from within the code. Can someone help me understand why sf_readf_short returns 0 and audio_playTrack() fails when invoked the second time?

Below is console prints.

PCM name: 'default'
PCM state: PREPARED
channels: 2 (stereo)
rate: 44100 bps
period size = 940 frames
period time = 21333 us

[FILE]Channels          : 2
[FILE]Sample rate       : 44100
[FILE]Sections          : 1
[FILE]Format            : 65538

+------------------
### AUDIO BEGIN ###
+------------------

+------------------
### AUDIO END ###
+------------------

PCM state: RUNNING

[FILE]Channels          : 2
[FILE]Sample rate       : 44100
[FILE]Sections          : 1
[FILE]Format            : 65538

+------------------
### AUDIO BEGIN ###
+------------------

Underrun!
+------------------
### AUDIO END ###
+------------------

Edit: Removed snd_pcm_drain( ) from audio_playTrack( ) and made snd_pcm_hw_params_t params local to audio_init( )


Solution

  • The file object infile is the same one for both calls. So you have reached the end of the file the first time, then the second time you try to play, you are still at the end of the file. You either need to close and reopen the file, or rewind to the beginning before trying to play it again.