Воспроизведение звука PCM с использованием alsa в RHEL6

Я пытаюсь воспроизвести волновой файл в RHEL6, используя вызовы библиотеки alsa в моем коде C в Qt. Я читаю волновой файл ("t15.wav") в буфере (wave_buffer). Заголовок волны был удален, так как библиотека alsa требует для воспроизведения необработанных сэмплов PCM. Далее я настроил параметры аппаратного и программного обеспечения PCM, используя snd_pcm_hw_params (PCM, params) и snd_pcm_sw_params_current (PCM, swparams) и многие другие вызовы. Я пишу образцы PCM на дескрипторе PCM, используя команду snd_pcm_writei. Для этого я читаю кусок (32, 1024, 2048, 4096 или 8192 байта) данных из wave_buffer и отправляю его для воспроизведения с помощью команды snd_pcm_writei. Если я выберу небольшой фрагмент, качество звука ухудшится, но воспроизведение будет непрерывным. Если я использую больший фрагмент (больше 4096, т.е. 8192), я получаю идеальное качество звука, но оно прерывается (когда для воспроизведения требуется следующий фрагмент данных). Мое ограничение заключается в том, что я могу иметь доступ к данным только по частям, а не как к файлу или целому буферу. Может ли кто-нибудь помочь мне в устранении перерывов в воспроизведении волновых данных, чтобы я мог получить непрерывное воспроизведение звука. Ниже приведен мой код: две переменные buffer_time и period_time возвращают размер периода, который является размером блока. Если buffer_time = 5000 & period_time = 1000, period_size, возвращаемый библиотекой alsa, равен 32 байтам // качество звука падает, но нет прерываний. Если buffer_time = 500000 & period_time = 100000, period_size, возвращаемый библиотекой alsa, составляет 8192 байта // хорошее качество звука, но прерванная настройка эти параметры кажутся бесполезными, поскольку я потратил на это много времени. Пожалуйста, помогите мне решить эту проблему -----

Структура волнового файла: Частота дискретизации: 44100 бит на выборку: 16 каналов: 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;
}

У меня есть аналогичный C-код библиотеки alsa, которая генерирует образцы синусоидальной волны во время выполнения и воспроизводит их с помощью той же команды snd_pcm_writei, и она отлично воспроизводится без каких-либо перерывов .... Это код библиотеки alsa ---

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

person Pratyush    schedule 15.03.2013    source источник
comment
Любое отрицательное возвращаемое значение snd_pcm_writei является ошибкой; вы должны проверить это.   -  person CL.    schedule 15.03.2013
comment
Спасибо за ответ CL .. Я проверил, что snd_pcm_writei не возвращает отрицательного значения ...   -  person Pratyush    schedule 15.03.2013
comment
Почему вы увеличиваете ptr, а затем используете play_buffer?   -  person CL.    schedule 15.03.2013
comment
Как и во втором коде библиотеки alsa, они воспроизводят оставшиеся образцы во второй итерации цикла ... Я также пробовал протестировать то же самое в своем коде ... но я заметил, что второй цикл while никогда не выполнялся более одного раза, потому что возвращаемое значение writei в val всегда равно buff_size .... таким образом, не было никакого смысла увеличивать ptr ... поэтому я снова переключился на play_buffer ... пожалуйста, игнорируйте оператор ....   -  person Pratyush    schedule 16.03.2013
comment
Я решил проблему, изменив аргумент длины snd_pcm_writei ... раньше я давал его равным данным, содержащимся в play_buffer ... теперь я изменил его на buff_size / 4, и звук воспроизводится отлично, без перерывов. Фактически, это размер, после которого система должна начать буферизацию для новых образцов pcm, как я понимаю. Раньше буферизация выполнялась после воспроизведения всей длины buff_size, что приводило к перерывам в выводе звука ...   -  person Pratyush    schedule 04.04.2013


Ответы (1)


Я решил проблему, изменив аргумент длины snd_pcm_writei ... раньше я давал его равным данным, содержащимся в play_buffer ... теперь я изменил его на "buff_size / 4", и звук воспроизводится отлично, без перерывов. Фактически, это размер, после которого система должна начать буферизацию для новых образцов pcm, как я понимаю. Раньше буферизация выполнялась после воспроизведения всей длины buff_size, что приводило к перерывам в выводе звука ...

person Pratyush    schedule 17.10.2013