Search code examples
pcmalsa

PCM audio playback using alsa in RHEL6


I am trying to play a wave file in RHEL6 using alsa library calls in my C Code in Qt. I am reading the wave file ("t15.wav") in a buffer(wave_buffer). The wave header has been stripped off since the alsa library requires raw PCM samples to be played. Further I have set up the PCM hardware & Software params using 'snd_pcm_hw_params(PCM, params)' & 'snd_pcm_sw_params_current(PCM, swparams)' and many other calls. I am writing the PCM samples on the PCM handle using 'snd_pcm_writei' command. For this purpose i am reading a chunk(32 or 1024 or 2048 or 4096 or 8192 bytes) of data from the wave_buffer and sending it for playing using the snd_pcm_writei command. If I choose a small chunk the audio quality falters but playback is uninterrupted. If I use a bigger chunk(greater than 4096 i.e. 8192) I get perfect audio quality but it is interrupted( When next chunk of data is required for playing ). My constraint is that I can have access to data in chunks only and not as a file or entire buffer. Can anybody help me in removing the interruptions in playing the wave data so that I can get uninterrupted audio playback. Following is my code : The two variables buffer_time & period_time return the period size which is the size of chunk. If buffer_time = 5000 & period_time=1000 the period_size returned by alsa library is 32 bytes //audio quality falters but no interruptions If buffer_time = 500000 & period_time=100000 the period_size returned by alsa library is 8192 bytes //good audio quality but interrupted Tuning these parameters seems useless as I have wasted a lot of time doing this. Please help me get through this problem-----

Stucture of Wave File : Sample Rate : 44100 Bits per Sample : 16 Channels : 2

mainwindow.h----

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <alsa/asoundlib.h>
#define BLOCKSIZE 44100 * 2 * 2 // Sample Rate * Channels * Byte per Sample(Bits per sample / 8)
namespace Ui {
    class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    int init_alsa();
    int play_snd();
    ~MainWindow();
    snd_pcm_t *PCM;
    snd_pcm_sframes_t delayp;
    snd_pcm_sframes_t availp;
    snd_pcm_sw_params_t *swparams;
    snd_pcm_hw_params_t *params;
    static snd_pcm_sframes_t period_size;
    static snd_pcm_sframes_t buffer_size;
    unsigned char wave_buffer[900000];
    unsigned char play_buffer[BLOCKSIZE];
    int filesize;
    FILE *fp;

private:
    Ui::MainWindow *ui;
};


#endif // MAINWINDOW_H


mainwindow.cpp---

#include "mainwindow.h"
#include "ui_mainwindow.h"

snd_pcm_sframes_t MainWindow::period_size;
snd_pcm_sframes_t MainWindow::buffer_size;
MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    if((fp = fopen("t15.wav","rb"))==NULL)
        printf("Error Opening File");
    fseek(fp,0L,SEEK_END);
    filesize = ftell(fp)-44;
    fseek(fp,0L,SEEK_SET);
    fseek(fp,44,SEEK_SET);
    fread(wave_buffer,filesize,1,fp);
    fclose(fp);
    delayp = 0;
    init_alsa();
    play_snd();
}


MainWindow::~MainWindow()
{
    delete ui;
}


int MainWindow::init_alsa()
{
    unsigned int rate = 44100;
    int err,dir;

    unsigned int rrate  = 44100;
    snd_pcm_uframes_t size;
    static unsigned int buffer_time = 500000;
    static unsigned int period_time = 100000;

    static int period_event = 0;

    if ((err=snd_pcm_open(&PCM,"plughw:0,0",SND_PCM_STREAM_PLAYBACK, 0)) < 0)
    {
        fprintf(stderr, "Can't use sound: %s\n", snd_strerror(err));
        return err;
    }


            snd_pcm_hw_params_alloca(&params);
            snd_pcm_sw_params_alloca(&swparams);
            //snd_pcm_nonblock(PCM,0);
        /* choose all parameters */
        err = snd_pcm_hw_params_any(PCM, params);
        if (err < 0) {
                printf("Broken configuration for playback: no configurations available: %s\n", snd_strerror(err));
                return err;
        }
        /* set hardware resampling */
        err = snd_pcm_hw_params_set_rate_resample(PCM, params, 1);
        if (err < 0) {
                printf("Resampling setup failed for playback: %s\n", snd_strerror(err));
                return err;
        }
        /* set the interleaved read/write format */
        err = snd_pcm_hw_params_set_access(PCM, params, SND_PCM_ACCESS_RW_INTERLEAVED);
        if (err < 0) {
                printf("Access type not available for playback: %s\n", snd_strerror(err));
                return err;
        }
        /* set the sample format */
        err = snd_pcm_hw_params_set_format(PCM, params, SND_PCM_FORMAT_S16_LE);
        if (err < 0) {
                printf("Sample format not available for playback: %s\n", snd_strerror(err));
                return err;
        }
        /* set the count of channels */
        err = snd_pcm_hw_params_set_channels(PCM, params, 2);
        if (err < 0) {
                printf("Channels count (%i) not available for playbacks: %s\n", 2, snd_strerror(err));
                return err;
        }
        /* set the stream rate */
        rrate = rate;
        err = snd_pcm_hw_params_set_rate_near(PCM, params, &rrate, 0);
        if (err < 0) {
                printf("Rate %iHz not available for playback: %s\n", 44100, snd_strerror(err));
                return err;
        }
        if (rrate != 44100) {
                printf("Rate doesn't match (requested %iHz, get %iHz)\n", rrate, err);
                return -EINVAL;
        }
        /* set the buffer time */
        err = snd_pcm_hw_params_set_buffer_time_near(PCM, params, &buffer_time, &dir);
        if (err < 0) {
                printf("Unable to set buffer time %i for playback: %s\n", buffer_time, snd_strerror(err));
                return err;
        }
        err = snd_pcm_hw_params_get_buffer_size(params, &size);
        if (err < 0) {
                printf("Unable to get buffer size for playback: %s\n", snd_strerror(err));
                return err;
        }
        buffer_size = size;

        /* set the period time */
        err = snd_pcm_hw_params_set_period_time_near(PCM, params, &period_time, &dir);
        if (err < 0) {
                printf("Unable to set period time %i for playback: %s\n", period_time, snd_strerror(err));
                return err;
        }
        err = snd_pcm_hw_params_get_period_size(params, &size, &dir);
        if (err < 0) {
                printf("Unable to get period size for playback: %s\n", snd_strerror(err));
                return err;
        }
        period_size = size;
        /* write the parameters to device */
        err = snd_pcm_hw_params(PCM, params);
        if (err < 0) {
                printf("Unable to set hw params for playback: %s\n", snd_strerror(err));
                return err;
        }
        printf("Size = %ld",period_size);

        snd_pcm_sw_params_current(PCM, swparams);                /* get the current swparams */

                                    /* start the transfer when the buffer is almost full: */
                                    /* (buffer_size / avail_min) * avail_min */
        snd_pcm_sw_params_set_start_threshold(PCM, swparams, (buffer_size / period_size) * period_size);

                                    /* allow the transfer when at least period_size samples can be processed */
                                    /* or disable this mechanism when period event is enabled (aka interrupt like style processing) */
        snd_pcm_sw_params_set_avail_min(PCM, swparams, period_event ? buffer_size : period_size);
        snd_pcm_sw_params(PCM, swparams);/* write the parameters to the playback device */
        return 1;
}

int MainWindow::play_snd()
{
int curr_pos = 0;
int buff_size = 0;
long val = 0;

while(curr_pos < filesize)
{
    if(filesize-curr_pos >= period_size)
    {
        memcpy(play_buffer,wave_buffer+curr_pos,period_size);

        buff_size = period_size;
        curr_pos += buff_size;
    }
    else
    {
        memcpy(play_buffer,wave_buffer+curr_pos,filesize-curr_pos);

        buff_size = filesize - curr_pos;
        curr_pos += buff_size;
    }

    int i=1;
    unsigned char *ptr = play_buffer;
    while(buff_size > 0)
    {
        val = snd_pcm_writei(PCM,&play_buffer,buff_size);
        if (val == -EAGAIN)
           continue;
        ptr += val * 2;
        buff_size -= val;
    }
}
return 0;
}

I have a similar C Code of alsa library which generates sine wave samples at runtime and plays them using same snd_pcm_writei command and it plays perfectly without any interruptions....This is the alsa library code---

/*
 *  This small demo sends a simple sinusoidal wave to your speakers.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sched.h>
#include <errno.h>
#include <getopt.h>
#include "alsa/asoundlib.h"
#include <sys/time.h>
#include <math.h>

static char *device = "plughw:0,0";                     /* playback device */
static snd_pcm_format_t format = SND_PCM_FORMAT_S16_LE; /* sample format */
static unsigned int rate = 44100;                       /* stream rate */
static unsigned int channels = 2;                       /* count of channels */
static unsigned int buffer_time = 5000;               /* ring buffer length in us */
static unsigned int period_time = 1000;               /* period time in us */
static double freq = 440;                               /* sinusoidal wave frequency in Hz */
static int resample = 1;                                /* enable alsa-lib resampling */
static int period_event = 0;                            /* produce poll event after each period */

static snd_pcm_sframes_t buffer_size;
static snd_pcm_sframes_t period_size;
static snd_output_t *output = NULL;
snd_pcm_sframes_t delayp;
snd_pcm_sframes_t availp;

static void generate_sine(const snd_pcm_channel_area_t *areas, 
                          snd_pcm_uframes_t offset,
                          int count, double *_phase)
{
        static double max_phase = 2. * M_PI;
        double phase = *_phase;
        double step = max_phase*freq/(double)rate;
        unsigned char *samples[channels];
        int steps[channels];
        unsigned int chn;
        int format_bits = snd_pcm_format_width(format);
        unsigned int maxval = (1 << (format_bits - 1)) - 1;
        int bps = format_bits / 8;                              /* bytes per sample */
        int phys_bps = snd_pcm_format_physical_width(format) / 8;
        int big_endian = snd_pcm_format_big_endian(format) == 1;
        int to_unsigned = snd_pcm_format_unsigned(format) == 1;
        int is_float = (format == SND_PCM_FORMAT_FLOAT_LE ||
                        format == SND_PCM_FORMAT_FLOAT_BE);

                                                                /* verify and prepare the contents of areas */
        for (chn = 0; chn < channels; chn++) {
                samples[chn] = /*(signed short *)*/(((unsigned char *)areas[chn].addr) + (areas[chn].first / 8));
                steps[chn] = areas[chn].step / 8;
                samples[chn] += offset * steps[chn];
        }
                                                                 /* fill the channel areas */
        while (count-- > 0) {
                union {
                        float f;
                        int i;
                      } fval;
                int res, i;
                if (is_float)
                {
                        fval.f = sin(phase) * maxval;
                        res = fval.i;
                }
                else
                    res = sin(phase) * maxval;
                if (to_unsigned)
                        res ^= 1U << (format_bits - 1);
                for (chn = 0; chn < channels; chn++) {
                                                                  /* Generate data in native endian format */
                        if (big_endian) {
                                for (i = 0; i < bps; i++)
                                        *(samples[chn] + phys_bps - 1 - i) = (res >> i * 8) & 0xff;
                        } else {
                                for (i = 0; i < bps; i++)
                                        *(samples[chn] + i) = (res >>  i * 8) & 0xff;
                        }
                        samples[chn] += steps[chn];
                }
                phase += step;
                if (phase >= max_phase)
                        phase -= max_phase;
        }
        *_phase = phase;
}

static int set_hwparams(snd_pcm_t *handle, snd_pcm_hw_params_t *params, snd_pcm_access_t access)
{
        unsigned int rrate;
        snd_pcm_uframes_t size;
        int dir;
        snd_pcm_hw_params_any(handle, params);                        /* choose all parameters */
        snd_pcm_hw_params_set_rate_resample(handle, params, resample);/* set hardware resampling */
        snd_pcm_hw_params_set_access(handle, params, access);         /* set the interleaved read/write format */
        snd_pcm_hw_params_set_format(handle, params, format);         /* set the sample format */
        snd_pcm_hw_params_set_channels(handle, params, channels);     /* set the count of channels */
        rrate = rate;                                                 /* set the stream rate */
        snd_pcm_hw_params_set_rate_near(handle, params, &rrate, 0);
        snd_pcm_hw_params_set_buffer_time_near(handle, params, &buffer_time, &dir);/* set the buffer time */
        snd_pcm_hw_params_get_buffer_size(params, &size);
        buffer_size = size;
        snd_pcm_hw_params_set_period_time_near(handle, params, &period_time, &dir);/* set the period time */
        snd_pcm_hw_params_get_period_size(params, &size, &dir);
        period_size = size;
        snd_pcm_hw_params(handle, params);                            /* write the parameters to device */
        return 0;
}

static int set_swparams(snd_pcm_t *handle, snd_pcm_sw_params_t *swparams)
{
        snd_pcm_sw_params_current(handle, swparams);                /* get the current swparams */

                                            /* start the transfer when the buffer is almost full: */
                                            /* (buffer_size / avail_min) * avail_min */
        snd_pcm_sw_params_set_start_threshold(handle, swparams, (buffer_size / period_size) * period_size);

                                            /* allow the transfer when at least period_size samples can be processed */
                                            /* or disable this mechanism when period event is enabled (aka interrupt like style processing) */
        snd_pcm_sw_params_set_avail_min(handle, swparams, period_event ? buffer_size : period_size);
        snd_pcm_sw_params(handle, swparams);/* write the parameters to the playback device */
        return 0;
}


/*
 *   Transfer method - write only
 */

static int write_loop(snd_pcm_t *handle, signed short *samples, snd_pcm_channel_area_t *areas)
{
        double phase = 0;
        signed short *ptr;
        int err, cptr;
        int i=0;
        printf("Period Size = %ld",period_size);
        while (1) {
        fflush(stdout);
                generate_sine(areas, 0, period_size, &phase);
                ptr = samples;
                cptr = period_size;
            i=1;
                while (cptr > 0) {

                    err = snd_pcm_writei(handle, ptr, cptr);
                snd_pcm_avail_delay(handle,&availp,&delayp);
               printf("available frames =%ld  delay = %ld  i = %d\n",availp,delayp,i);
                        if (err == -EAGAIN)
                                continue;
                        ptr += err * channels;
                        cptr -= err;
            i++;
                }
        }
}


/*
 *   Transfer method - asynchronous notification
 */

int main()
{
        snd_pcm_t *handle;
        snd_pcm_hw_params_t *hwparams;
        snd_pcm_sw_params_t *swparams;
        signed short *samples;
        unsigned int chn;
        snd_pcm_channel_area_t *areas;

        snd_pcm_hw_params_alloca(&hwparams);
        snd_pcm_sw_params_alloca(&swparams);

        snd_output_stdio_attach(&output, stdout, 0);

        printf("Playback device is %s\n", device);
        printf("Stream parameters are %iHz, %s, %i channels\n", rate, snd_pcm_format_name(format), channels);
        printf("Sine wave rate is %.4fHz\n", freq);

        snd_pcm_open(&handle, device, SND_PCM_STREAM_PLAYBACK, 0);
        set_hwparams(handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED);
        set_swparams(handle, swparams);
        samples = malloc((period_size * channels * snd_pcm_format_physical_width(format)) / 8);
        areas = calloc(channels, sizeof(snd_pcm_channel_area_t));

        for (chn = 0; chn < channels; chn++) {
                areas[chn].addr = samples;
                areas[chn].first = chn * snd_pcm_format_physical_width(format);
                areas[chn].step = channels * snd_pcm_format_physical_width(format);
        }

        write_loop(handle, samples, areas);
        free(areas);
        free(samples);
        snd_pcm_close(handle);
        return 0;
}

Solution

  • I solved the problem by altering the length argument of snd_pcm_writei...perviously i was giving it equal to the data contained in play_buffer...now i changed it to "buff_size/4" and the audio is playing perfectly without breaks. Actually it is the size after which the system should start buffering for new pcm samples as per my understanding. Previously it was buffering after playing the entire length buff_size and that resulted in breaks in audio output...