如何正确设置 ALSA 设备

How to properly set up ALSA device

编辑:这个问题与提议的重复问题不同,因为我问的是 How do you set the period/buffer size that will work with multiple targets each with different sound hardware?

我创建了一些代码,试图在播放 OGG 文件之前设置 ALSA。下面的代码在一个嵌入式 Linux 平台上运行,但在另一个平台上运行失败,输出如下:

Error setting buffersize.
Playback open error: Operation not permitted

我只包含了演示该问题的代码。 setup_alsa() 不完整,无法完全配置 alsa 设备。

#include <alsa/asoundlib.h>

char *buffer;
static char *device = "default";
snd_pcm_uframes_t periodsize = 8192;    /* Periodsize (bytes) */    


int setup_alsa(snd_pcm_t *handle)
{
    int rc; 
    int dir = 0;    
    snd_pcm_uframes_t periods;          /* Number of fragments/periods */
    snd_pcm_hw_params_t *params;
    snd_pcm_sw_params_t *sw_params;
    int rate = 44100;
    int exact_rate;

    int i = 0;

    /* Allocate a hardware parameters object. */
    snd_pcm_hw_params_alloca(&params);

    /* Fill it in with default values. */
    if (snd_pcm_hw_params_any(handle, params) < 0)
    {
        fprintf(stderr, "Can not configure this PCM device.\n");
        snd_pcm_close(handle);    
        return(-1);
    }

    /* Set number of periods. Periods used to be called fragments. */ 
    periods = 4;
    if ( snd_pcm_hw_params_set_periods(handle, params, periods, 0) < 0 )
    {
        fprintf(stderr, "Error setting periods.\n");
        snd_pcm_close(handle);    
        return(-1);
    }


    /* Set buffer size (in frames). The resulting latency is given by */
    /* latency = periodsize * periods / (rate * bytes_per_frame)     */
    if (snd_pcm_hw_params_set_buffer_size(handle, params, (periodsize * periods)>>2) < 0)
    {
        fprintf(stderr, "Error setting buffersize.\n");
        snd_pcm_close(handle);    
        return(-1);
    }

    /* 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));
        snd_pcm_close(handle);    
        return -1;
    }

    snd_pcm_hw_params_free(params);

无需设置特定 buffer/period 大小即可提供流畅音频播放的 ALSA 正常设置方法是什么?**

事实证明,我可以编写我的 ALSA 安装例程,让 ALSA 通过使用 snd_pcm_hw_params_set_buffer_size_near() 而不是 [来确定最近的工作 period/buffer 尺寸是多少=14=].

以下代码现在适用于两个平台:

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <vorbis/vorbisfile.h>
#include <alsa/asoundlib.h>

char *buffer;
//static char *device = "default";
static char *device = "plughw:0,0";

snd_pcm_uframes_t periodsize = 4096;    /* Periodsize (bytes) */    

int setup_alsa(snd_pcm_t *handle)
{
    int rc; 
    int dir = 0;    
    snd_pcm_uframes_t periods;          /* Number of fragments/periods */
    snd_pcm_hw_params_t *params;
    snd_pcm_sw_params_t *sw_params;
    int rate = 44100;
    int exact_rate;
    int i = 0;

    /* Allocate a hardware parameters object. */
    snd_pcm_hw_params_malloc(&params);

    /* Fill it in with default values. */
    if (snd_pcm_hw_params_any(handle, params) < 0)
    {
        fprintf(stderr, "Can not configure this PCM device.\n");
        snd_pcm_close(handle);    
        return(-1);
    }

    /* Set the desired hardware parameters. */
    /* Non-Interleaved mode */
    snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_NONINTERLEAVED);
    snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE);

    /* 44100 bits/second sampling rate (CD quality) */
    /* Set sample rate. If the exact rate is not supported */
    /* by the hardware, use nearest possible rate.         */ 
    exact_rate = rate;
    if (snd_pcm_hw_params_set_rate_near(handle, params, &exact_rate, 0) < 0)
    {
        fprintf(stderr, "Error setting rate.\n");
        snd_pcm_close(handle);    
        return(-1);
    }

    if (rate != exact_rate)
    {
        fprintf(stderr, "The rate %d Hz is not supported by your hardware.\n==> Using %d Hz instead.\n", rate, exact_rate);
    }

    /* Set number of channels to 1 */
    if( snd_pcm_hw_params_set_channels(handle, params, 1 ) < 0 )
    {
        fprintf(stderr, "Error setting channels.\n");
        snd_pcm_close(handle);    
        return(-1);
    }

    /* Set number of periods. Periods used to be called fragments. */ 
    periods = 4;
    if ( snd_pcm_hw_params_set_periods(handle, params, periods, 0) < 0 )
    {
        fprintf(stderr, "Error setting periods.\n");
        snd_pcm_close(handle);    
        return(-1);
    }

    snd_pcm_uframes_t size = (periodsize * periods) >> 2;
    if( (rc = snd_pcm_hw_params_set_buffer_size_near( handle, params, &size )) < 0)
    {
        fprintf(stderr, "Error setting buffersize: [%s]\n", snd_strerror(rc) );
        snd_pcm_close(handle);    
        return(-1);
    }
    else
    {
        printf("Buffer size = %lu\n", (unsigned long)size);
    }

    /* 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));
        snd_pcm_close(handle);    
        return -1;
    }

    snd_pcm_hw_params_free(params);

    /* Allocate a software parameters object. */
    rc = snd_pcm_sw_params_malloc(&sw_params);
    if( rc < 0 )
    {
        fprintf (stderr, "cannot allocate software parameters structure (%s)\n", snd_strerror(rc) );
        return(-1);
    }

    rc = snd_pcm_sw_params_current(handle, sw_params);
    if( rc < 0 )
    {
        fprintf (stderr, "cannot initialize software parameters structure (%s)\n", snd_strerror(rc) );
        return(-1);
    }

    if((rc = snd_pcm_sw_params_set_avail_min(handle, sw_params, 1024)) < 0)
    {
        fprintf (stderr, "cannot set minimum available count (%s)\n", snd_strerror (rc));
        return(-1);
    }

    rc = snd_pcm_sw_params_set_start_threshold(handle, sw_params, 1);
    if( rc < 0 )
    {
        fprintf(stderr, "Error setting start threshold\n");
        snd_pcm_close(handle);    
        return -1;
    }

    if((rc = snd_pcm_sw_params(handle, sw_params)) < 0)
    {
        fprintf (stderr, "cannot set software parameters (%s)\n", snd_strerror (rc));
        return(-1);
    }

    snd_pcm_sw_params_free(sw_params);

    return 0;
}

/* copied from libvorbis source */
int ov_fopen(const char *path, OggVorbis_File *vf)
{
    int ret = 0;
    FILE *f = fopen(path, "rb");

    if( f )
    {
        ret = ov_open(f, vf, NULL, 0);
        if( ret ) 
        {
            fclose(f);
        }
    }
    else
    {
        ret = -1;
    }
    return( ret );
}


int main(int argc, char *argv[])
{
    // sample rate * bytes per sample * channel count * seconds
    //int bufferSize = 44100 * 2 * 1 * 2;

    int err;
    snd_pcm_t *handle;
    snd_pcm_sframes_t frames;


    buffer = (char *) malloc( periodsize );
    if( buffer )
    {
        if((err = snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0)) < 0)
        {
            printf("Playback open error #1: %s\n", snd_strerror(err));
            exit(EXIT_FAILURE);
        }

        if(err = setup_alsa(handle))
        {
            printf("Playback open error #2: %s\n", snd_strerror(err));
            exit(EXIT_FAILURE);
        }

        OggVorbis_File vf;
        int eof = 0;
        int current_section;

        err = ov_fopen(argv[1], &vf);

        if(err != 0)
        {
            perror("Error opening file");
        }
        else
        {
            vorbis_info *vi = ov_info(&vf, -1);
            fprintf(stderr, "Bitstream is %d channel, %ldHz\n", vi->channels, vi->rate);
            fprintf(stderr, "Encoded by: %s\n\n", ov_comment(&vf, -1)->vendor);

            while(!eof)
            {
                long ret = ov_read(&vf, buffer, periodsize, 0, 2, 1, &current_section);
                if(ret == 0)
                {
                    /* EOF */
                    eof = 1;
                }
                else if(ret < 0)
                {
                    /* error in the stream. */
                    fprintf( stderr, "ov_read error %l", ret );
                }
                else
                {
                    frames = snd_pcm_writen(handle, (void *)&buffer, ret/2);
                    if(frames < 0)
                    {
                        printf("snd_pcm_writen failed: %s\n", snd_strerror(frames));
                        if( frames == -EPIPE )
                        {
                            snd_pcm_prepare(handle);
                            //frames = snd_pcm_writen(handle, (void *)&buffer, ret/2);
                        }
                        else
                        {       
                            break;
                        }
                    }
                }
            }
            ov_clear(&vf);
        }
        free( buffer );
        snd_pcm_drain(handle);
        snd_pcm_close(handle);
    }
    return 0;
}