Skip to content
Advertisement

How to properly set up ALSA device

Edit: This question is different than the proposed duplicate because I’m asking How do you set the period/buffer size that will work with multiple targets each with different sound hardware?.

I have created some code that attempts to set up ALSA before playback of an OGG file. The code below works on one embedded Linux platform, but on another it fails with the following output:

Error setting buffersize.
Playback open error: Operation not permitted

I’ve included only the code that demonstrates the issue. setup_alsa() is not complete and won’t completely configure an alsa device.

#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: %sn", snd_strerror(rc));
        snd_pcm_close(handle);    
        return -1;
    }

    snd_pcm_hw_params_free(params);

What is the normal way to setup ALSA that doesn’t require a specific buffer/period size be set that provides smooth audio playback?**

Advertisement

Answer

As it turns out, I can program my ALSA setup routine to let ALSA determine what the nearest working period/buffer size is by using snd_pcm_hw_params_set_buffer_size_near() instead of snd_pcm_hw_params_set_buffer_size().

The following code now works on both platforms:

#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 = %lun", (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: %sn", 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 thresholdn");
        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: %sn", snd_strerror(err));
            exit(EXIT_FAILURE);
        }

        if(err = setup_alsa(handle))
        {
            printf("Playback open error #2: %sn", 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, %ldHzn", vi->channels, vi->rate);
            fprintf(stderr, "Encoded by: %snn", 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: %sn", 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;
}
User contributions licensed under: CC BY-SA
10 People found this is helpful
Advertisement