1

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:

  • samplerate: 44100
  • channels: 2

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?

Community
  • 1
  • 1
Guru Prasad
  • 4,053
  • 2
  • 25
  • 43
  • 1
    You use `snd_pcm_drain()` only at the end of the stream. – CL. Mar 12 '17 at 21:40
  • Could you kindly elaborate? Almost all of what I'm doing is the same as the sample code which works. The difference is that I'm internally chunking the file content and calling `snd_pcm_write` whereas the sample code presents the entire file content to `snd_pcm_writei`. Where should I be calling `snd_pcm_drain` in my snippet? – Guru Prasad Mar 12 '17 at 21:45
  • I've updated the question and also added a fully working code sample and one of the WAV files I'm using to test. I find it difficult to describe the "repetition" and so hopefully this will help – Guru Prasad Mar 13 '17 at 15:53

1 Answers1

0
    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.

CL.
  • 173,858
  • 17
  • 217
  • 259
  • I see. So the reason the code snippet in the linked SO question works is because it feeds in all the data in just one iteration of the `do{...}while` loop thereby rendering the `frames` return value irrelevant. – Guru Prasad Mar 13 '17 at 21:03
  • `WaveSize` is counted in frames; but `WavePtr + count` is wrong if `count > 0`. – CL. Mar 13 '17 at 21:20