9
votes

Trying to learn the ALSA audio layers to eventually write an ALSA device driver for the Raspberry Pi platform. Starting simple, I glued together various samples from the ALSA project site and other online sources to do the simplest thing: read a WAV file and play it on the default sound device. I cannot get this simple C sample to work.

I am using libsndfile to do all the WAV file reading/header decoding. I verified that the samples I read into the buffer are correct (verified first 400K samples of what the program reads in against the sndfile-to-text application that dumps the sample values to a text file). So I know my buffer contains the right data, the problem must be in the way I pass it to the ALSA APIs.

When run it produces sound only in the right channel, and distorted/muddy - barely recognizable. BTW, the "aplay" application plays the same WAV file perfectly and reports the file is 16 bit signed LE, 44100Hz, stereo which matches what my application reports as well. Running this on a Raspberry Pi.

I stripped the C program down to the minimum here to conserve space, but I verified correct return codes from all API calls. Why does this simple ALSA application not produce the correct sound?

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

#define PCM_DEVICE "default"

int main(int argc, char **argv) {

    snd_pcm_t *pcm_handle;
    snd_pcm_hw_params_t *params;
    snd_pcm_uframes_t frames;
    int dir, pcmrc;

    char *infilename = "/home/pi/shortsample.wav";
    int* buf = NULL;
    int readcount;

    SF_INFO sfinfo;
    SNDFILE *infile = NULL;

    infile = sf_open(infilename, SFM_READ, &sfinfo);
    fprintf(stderr,"Channels: %d\n", sfinfo.channels);
    fprintf(stderr,"Sample rate: %d\n", sfinfo.samplerate);
    fprintf(stderr,"Sections: %d\n", sfinfo.sections);
    fprintf(stderr,"Format: %d\n", sfinfo.format);

    /* Open the PCM device in playback mode */
    snd_pcm_open(&pcm_handle, PCM_DEVICE, SND_PCM_STREAM_PLAYBACK, 0);

    /* 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_access(pcm_handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);
    snd_pcm_hw_params_set_format(pcm_handle, params, SND_PCM_FORMAT_S16_LE);
    snd_pcm_hw_params_set_channels(pcm_handle, params, sfinfo.channels);
    snd_pcm_hw_params_set_rate(pcm_handle, params, sfinfo.samplerate, 0);

    /* Write parameters */
    snd_pcm_hw_params(pcm_handle, params);

    /* Allocate buffer to hold single period */
    snd_pcm_hw_params_get_period_size(params, &frames, &dir);
    fprintf(stderr,"# frames in a period: %d\n", frames);

    fprintf(stderr,"Starting read/write loop\n");
    buf = malloc(frames * sfinfo.channels * sizeof(int));
    while ((readcount = sf_readf_int(infile, buf, frames))>0) {

        pcmrc = snd_pcm_writei(pcm_handle, buf, readcount);
        if (pcmrc == -EPIPE) {
            fprintf(stderr, "Underrun!\n");
            snd_pcm_prepare(pcm_handle);
        }
        else if (pcmrc < 0) {
            fprintf(stderr, "Error writing to PCM device: %s\n", snd_strerror(pcmrc));
        }
        else if (pcmrc != readcount) {
            fprintf(stderr,"PCM write difffers from PCM read.\n");
        }

    }
    fprintf(stderr,"End read/write loop\n");

    snd_pcm_drain(pcm_handle);
    snd_pcm_close(pcm_handle);
    free(buf);

    return 0;
}
1

1 Answers

4
votes

You must check the return values of all snd_ functions that could fail.

The S16_LE format has two bytes per sample, but int has four. Use short instead, and sf_readf_short.