0

I am trying to play this sound using the program below, but the sound is a little fast and it skips. The sound file's sample rate is 11025 Hz, stereo, the sample size is 16 bit. The issue appears to be snd_pcm_hw_params_set_buffer_size() and snd_pcm_hw_params_set_period_size() (both are functions I am studying) due to invalid arguments, but I don't know why they are invalid and commenting them out doesn't address the skipping effect. What am I doing wrong?

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

#define STEREO              2
#define BITS            16 / 8
#define FRAMEBUFFERSIZE   512 // in samples
#define PERIODS             2
#define SAMPLERATE      11025

void snd_error_checker(int error, char *function_name)
{
    if (error)
    {
        printf("Error (%s): %s\n", function_name, snd_strerror(error));
        // exit(EXIT_FAILURE);
    }
}

int main(void)
{
    snd_pcm_t *handle;
    uint32_t channels                   = STEREO;
    uint32_t sample_size                = BITS; // 16 bit
    uint32_t frame_size                 = sample_size * channels;
    snd_pcm_uframes_t frames            = FRAMEBUFFERSIZE / frame_size;
    snd_pcm_uframes_t frames_per_period = frames / PERIODS;
    int32_t dir = 0; // No idea what this does.
    snd_pcm_hw_params_t *params;
    int16_t *buffer;
    FILE *wav;
    int32_t size;
    int error;

    error = snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0);
    snd_error_checker(error, "snd_pcm_open()");
    snd_pcm_hw_params_alloca(&params);
    error = snd_pcm_hw_params_any(handle, params);
    snd_error_checker(error, "snd_pcm_hw_params_any()");
    error = snd_pcm_hw_params_set_buffer_size(handle, params, frames);
    snd_error_checker(error, "snd_pcm_hw_params_set_buffer_size()");
    error = snd_pcm_hw_params_set_period_size(handle, params, frames_per_period, dir);
    snd_error_checker(error, "snd_pcm_hw_params_set_period_size()");
    error = snd_pcm_hw_params_set_rate(handle, params, SAMPLERATE, dir);
    snd_error_checker(error, "snd_pcm_hw_params_set_rate()");
    error = snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);
    snd_error_checker(error, "snd_pcm_hw_params_set_access()");
    error = snd_pcm_hw_params_set_channels(handle, params, STEREO);
    snd_error_checker(error, "snd_pcm_hw_params_set_channels()");
    error = snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE);
    snd_error_checker(error, "snd_pcm_hw_params_set_format()");
    error = snd_pcm_hw_params(handle, params);
    snd_error_checker(error, "snd_pcm_hw_params()");

    // Insert samples to framebuffer.
    wav = fopen("pcm1611s.wav", "r");
    fseek(wav, 0L, SEEK_END);
    size = ftell(wav);
    fseek(wav, 0L, SEEK_SET);
    buffer = malloc(size);
    fread(buffer, sizeof(int16_t), size, wav);
    fclose(wav);
    size /= sizeof(int16_t);

    // Set ptrbuffer 46 bytes ahead to skip the header.
    for (int16_t *ptrbuffer = buffer + 46; size > ptrbuffer - buffer; ptrbuffer += FRAMEBUFFERSIZE * STEREO * BITS)
    {
        error = snd_pcm_writei(handle, ptrbuffer, frames);
        if (error)
        {
            snd_pcm_recover(handle, error, 0);
        }
    }
    snd_pcm_drain(handle);
    snd_pcm_close(handle);
    exit(EXIT_SUCCESS);
}

stare
  • 150
  • 8
  • Where do you decode the wav-file header? – Jens Harms Jun 24 '20 at 10:17
  • I have moved `fread()` out of the loop, added error checking to all `snd_pcm_*` functions, made it skip what I believe to be the header which appears to be 44-46 bytes long. I feel like the question will be a lot harder to read because of these changes. – stare Jun 24 '20 at 18:02

2 Answers2

1

Three points need to be consider for proper playback

  1. Check return value of snd_pcm_writei , will give a clear idea , whats going wrong if any error. Referalsa-project

  2. Reading file in while() loop and giving to snd_pcm_writei can cause skip in audio with under run (-EPIPE error), since fread is time consuming call.

  3. Check return values of all of your library calls and make sure all are success. Also make sure the values are setting properly in driver . For example, after snd_pcm_hw_params_set_rate verify it with snd_pcm_hw_params_get_rate, if your driver is not supporting sampling rate 11025KHz, we can find out it with return values and rechecking with snd_pcm_hw_params_get_rate

jjm
  • 431
  • 3
  • 19
  • I have used return values to locate the error and I have found that `snd_pcm_hw_params_set_buffer_size()` and `snd_pcm_hw_params_set_period_size()` returns "Invalid argument" as error. Not very descriptive or clarifying but at least I know where the error is. – stare Jun 24 '20 at 17:46
1
#define FRAMEBUFFERSIZE   512 // in samples

The comment is lying; the value is used as a byte count.

uint32_t sample_size                = BITS; // 16 bit

The comment is lying; the value is actualy measured in bytes. (And BITS is named wrong.)

fread(buffer, sizeof(int16_t), size, wav);

size is measured in bytes, but you tell fread() to read 16-bit words. (This does not actually hurt because it stops reading at the end of the file. But you should have checked for errors.)

int16_t *ptrbuffer = ...

You are telling the compiler that ptrbuffer points to 16-bit values.

ptrbuffer += FRAMEBUFFERSIZE * STEREO * BITS

You are telling the compiler to step over 2048 16-bit values, i.e., over 4096 bytes. You actually want to step over all the sample in a buffer, that is, frames * STEREO.

error = snd_pcm_writei(...);
if (error)

snd_pcm_writei() returns a positive number on success; errors are negative.

CL.
  • 173,858
  • 17
  • 217
  • 259