【问题标题】:How can I get the buffer from a PCM?如何从 PCM 获取缓冲区?
【发布时间】:2013-06-24 16:59:51
【问题描述】:

我想在 Linux 上使用 ALSA 播放 PCM 文件,这是我的代码:

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

main (int argc, char *argv[])
{
    int i;
    int err;
    short buf[128];
    snd_pcm_t *playback_handle;
    snd_pcm_hw_params_t *hw_params;

    if ((err = snd_pcm_open (&playback_handle, argv[1], SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
        fprintf (stderr, "cannot open audio device %s (%s)\n", 
             argv[1],
             snd_strerror (err));
        exit (1);
    }

    if ((err = snd_pcm_hw_params_malloc (&hw_params)) < 0) {
        fprintf (stderr, "cannot allocate hardware parameter structure (%s)\n",
             snd_strerror (err));
        exit (1);
    }

    if ((err = snd_pcm_hw_params_any (playback_handle, hw_params)) < 0) {
        fprintf (stderr, "cannot initialize hardware parameter structure (%s)\n",
             snd_strerror (err));
        exit (1);
    }

    if ((err = snd_pcm_hw_params_set_access (playback_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
        fprintf (stderr, "cannot set access type (%s)\n",
             snd_strerror (err));
        exit (1);
    }

    if ((err = snd_pcm_hw_params_set_format (playback_handle, hw_params, SND_PCM_FORMAT_S16_LE)) < 0) {
        fprintf (stderr, "cannot set sample format (%s)\n",
             snd_strerror (err));
        exit (1);
    }

    if ((err = snd_pcm_hw_params_set_rate_near (playback_handle, hw_params, 44100, 0)) < 0) {
        fprintf (stderr, "cannot set sample rate (%s)\n",
             snd_strerror (err));
        exit (1);
    }

    if ((err = snd_pcm_hw_params_set_channels (playback_handle, hw_params, 2)) < 0) {
        fprintf (stderr, "cannot set channel count (%s)\n",
             snd_strerror (err));
        exit (1);
    }

    if ((err = snd_pcm_hw_params (playback_handle, hw_params)) < 0) {
        fprintf (stderr, "cannot set parameters (%s)\n",
             snd_strerror (err));
        exit (1);
    }

    snd_pcm_hw_params_free (hw_params);

    if ((err = snd_pcm_prepare (playback_handle)) < 0) {
        fprintf (stderr, "cannot prepare audio interface for use (%s)\n",
             snd_strerror (err));
        exit (1);
    }

    for (i = 0; i < 10; ++i) {
        if ((err = snd_pcm_writei (playback_handle, buf, 128)) != 128) {
            fprintf (stderr, "write to audio interface failed (%s)\n",
                 snd_strerror (err));
            exit (1);
        }
    }

    snd_pcm_close (playback_handle);
    exit (0);
}

我现在的问题是,我想播放一个解码的 MP3 文件,它现在是 PCM 格式。但是函数snd_pcm_writei的参数是“pcm句柄、缓冲区、大小”。 PCM给了,size就是buffer的大小,但是怎么来buffer参数呢?

【问题讨论】:

    标签: c linux buffer pcm alsa


    【解决方案1】:

    您必须先将文件加载到内存中才能播放。

    使用libsndfileaudiofile 之类的库是最简单的。

    【讨论】:

    • 所以你的意思是当我用 FILE *f = argv[1]; 将文件加载到内存中时我必须逐行阅读?
    【解决方案2】:

    snd_pcm_writei 有 3 个参数,你已经知道了:

    • PCM 句柄
    • 包含缓冲区的帧
    • 要写入的帧

    您必须将 PCM 中的帧read() 放入缓冲区,然后播放它们

    这是一个播放原始 PCM 数据的短程序(从 here 重新格式化,清单 3):

    /* Use the newer ALSA API */
    #define ALSA_PCM_NEW_HW_PARAMS_API
    
    #include <alsa/asoundlib.h>
    
    int main() {
        long loops;
        int rc;
        int size;
        snd_pcm_t *handle;
        snd_pcm_hw_params_t *params;
        unsigned int val;
        int dir;
        snd_pcm_uframes_t frames;
        char *buffer;
    
        /* Open PCM device for playback. */
        rc = snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0);
        if (rc < 0) 
        {
            fprintf(stderr, "Unable to open PCM device: %s\n", snd_strerror(rc));
            exit(1);
        }
    
        /* Allocate a hardware parameters object. */
        snd_pcm_hw_params_alloca(&params);
    
        /* Fill it in with default values. */
        snd_pcm_hw_params_any(handle, params);
    
        /* Set the desired hardware parameters. */
    
        /* Interleaved mode */
        snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);
    
        /* Signed 16-bit little-endian format */
        snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE);
    
        /* Two channels (stereo) */
        snd_pcm_hw_params_set_channels(handle, params, 2);
    
        /* 44100 bits/second sampling rate (CD quality) */
        val = 44100;
        snd_pcm_hw_params_set_rate_near(handle, params, &val, &dir);
    
        /* Set period size to 32 frames. */
        frames = 32;
        snd_pcm_hw_params_set_period_size_near(handle, params, &frames, &dir);
    
        /* Write the parameters to the driver */
        rc = snd_pcm_hw_params(handle, params);
        if (rc < 0) 
        {
            fprintf(stderr, "Unable to set HW parameters: %s\n", snd_strerror(rc));
            exit(1);
        }
    
        /* Use a buffer large enough to hold one period */
        snd_pcm_hw_params_get_period_size(params, &frames, &dir);
        size = frames * 4; /* 2 bytes/sample, 2 channels */
        buffer = (char *) malloc(size);
    
        /* We want to loop for 5 seconds */
        snd_pcm_hw_params_get_period_time(params, &val, &dir);
        /* 5 seconds in microseconds divided by period time */
        loops = 5000000 / val;
    
        while (loops > 0) 
        {
            loops--;
            rc = read(0, buffer, size);  /* ### Retrieve the buffer ### */
            if (rc == 0) 
            {
                fprintf(stderr, "End of file on input\n");
                break;
            } else if (rc != size) 
            {
                fprintf(stderr, "Short read: read %d bytes\n", rc);
            }
            rc = snd_pcm_writei(handle, buffer, frames);
            if (rc == -EPIPE) 
            {
                /* EPIPE means underrun */
                fprintf(stderr, "Underrun occurred\n");
                snd_pcm_prepare(handle);
            } else if (rc < 0) 
            {
                fprintf(stderr, "Error from writei: %s\n", snd_strerror(rc));
            }  else if (rc != (int)frames) 
            {
               fprintf(stderr, "Short write, write %d frames\n", rc);
            }
        }
    
        snd_pcm_drain(handle);
        snd_pcm_close(handle);
        free(buffer);
    
        return 0;
    }
    

    【讨论】:

    • 但我想播放整个 pcm 文件,而不仅仅是 5 秒。我应该如何处理 while 条件?
    • 好的,谢谢,我知道了。我不使用参数循环并将'rc = read(0,缓冲区,大小)'放入我的while条件中。所以文件播放到最后。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-01-05
    • 1970-01-01
    • 2011-05-28
    • 2011-03-16
    • 1970-01-01
    • 2021-04-04
    • 2012-12-03
    相关资源
    最近更新 更多