linuxaudioalsalibalsalibasound

Streaming bytes to ALSA playback device


I'm having a lot of trouble writing random bytes to an ALSA playback device using libasound. Eventually, my goal is to be able to route playback stream over the network and have it played on a remote device.

The code presented in this question reads a WAV file into memory and writes it to the driver via snd_pcm_writei and it works. However, a crucial difference between what this code does and what I'm trying to do is the fact that I don't have all the data available to me right away. I'm looking to stream data as it becomes available.

Adapting the above sample code to fit my needs, I end up with this:

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

static snd_pcm_t *PlaybackHandle;

int init_playback(const char *device, int samplerate, int channels) {
    int err;

    printf("Init parameters: %s %d %d\n", device, samplerate, channels);

    if((err = snd_pcm_open(&PlaybackHandle, device, SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
        printf("Can't open audio %s: %s\n", device, snd_strerror(err));
        return -1; 
    }

    if ((err = snd_pcm_set_params(PlaybackHandle, SND_PCM_FORMAT_S16, SND_PCM_ACCESS_RW_INTERLEAVED, channels, samplerate, 1, 500000)) < 0) {
        printf("Can't set sound parameters: %s\n", snd_strerror(err));
        return -1; 
    }
    return 0;
}

int play_bytes(const void *bytes, int len) {
    snd_pcm_uframes_t frames, count;
    snd_pcm_uframes_t bufsize, period_size;
    frames = 0;
    count = 0;

    snd_pcm_prepare(PlaybackHandle);

    snd_pcm_get_params(PlaybackHandle, &bufsize, &period_size);
    printf("bufsize=%d\n", (int) bufsize);

    do {
        int remaining = len - count;
        int buflen = remaining < bufsize ? remaining : bufsize;
        frames = snd_pcm_writei(PlaybackHandle, bytes + count, buflen);
        // If an error, try to recover from it
        if (frames == -EPIPE) {
            printf("EPIPE\n");
            snd_pcm_prepare(PlaybackHandle);
        }
        if (frames < 0) {
            printf("Recovering\n");
            frames = snd_pcm_recover(PlaybackHandle, frames, 0);
        }
        if (frames < 0)
        {
            printf("Error playing wave: %s\n", snd_strerror(frames));
            break;
        }

        // Update our pointer
        count += frames;
        //printf("count=%d len=%d\n", (int)count, len);

    } while (count < len);

    // Wait for playback to completely finish
    if (count == len)
        snd_pcm_drain(PlaybackHandle);
    return 0;
}

int close_playback() {
    snd_pcm_close(PlaybackHandle);
    return 0;
}

int main(int argc, char **argv) {
    if(argc < 1) {
        printf("Usage: %s <WAV-file>\n", argv[0]);
        return -1;
    }

    int fd;
    unsigned long long len;

    fd = open(argv[1], O_RDONLY);
    // Find the length
    len = lseek(fd, 0, SEEK_END);

    // Skip the first 44 bytes (header)
    lseek(fd, 44, SEEK_SET);
    len -= 44;

    char *data = malloc(len);
    read(fd, data, len);

    init_playback("default", 44100, 2);
    play_bytes(data, len);
    close_playback();
    return 0;
}

This code can be compiled with gcc playback.c -o playback -lasound. The WAV file I'm using can be found here.

When I run this code snippet, where I chunk the incoming data based on bufsize there are fragments of audio that is repeated in the playback depending on the chunk size. A large chunk size yields fewer repetitions than a small chunk size. Combining this with how the audio sounds, I believe that a small fragment at the end of each chunk is being repeated.

The parameters I used were:

Why does sending the entire WAV file in one shot work whereas sending chunks of it not work? How do I send chunks of audio data to the driver and have it play properly?


Solution

  •     frames = snd_pcm_writei(PlaybackHandle, bytes + count, buflen);
        count += frames;
    

    snd_pcm_writei() measures the size in frames, but you treat it as bytes. So you skip over only one fourth of the data that was just played.