【问题标题】:Streaming bytes to ALSA playback device将字节流式传输到 ALSA 播放设备
【发布时间】:2017-03-14 04:26:37
【问题描述】:

我在使用libasound 将随机字节写入 ALSA 播放设备时遇到了很多麻烦。最终,我的目标是能够通过网络路由播放流并在远程设备上播放。

this question 中提供的代码将 WAV 文件读入内存并通过snd_pcm_writei 将其写入驱动程序并且它可以工作。然而,这段代码所做的事情和我正在尝试做的事情之间的一个关键区别是,我没有立即获得所有可用的数据。我希望在数据可用时对其进行流式传输。

根据我的需要调整上面的示例代码,我最终得到了这个:

#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;
}

这段代码可以用gcc playback.c -o playback -lasound编译。我使用的WAV文件可以在here找到。

当我运行此代码 sn-p 时,我根据 bufsize 对传入数据进行分块,根据块大小,播放中会重复播放音频片段。大块大小产生的重复次数少于小块大小。结合音频听起来,我相信每个块末尾的一个小片段正在重复。

我使用的参数是:

  • 采样率:44100
  • 频道:2

为什么一次性发送整个 WAV 文件有效,而发送部分文件无效?如何向驱动程序发送音频数据块并使其正常播放?

【问题讨论】:

  • 您只在流的末尾使用snd_pcm_drain()
  • 您能详细说明一下吗?我所做的几乎所有事情都与有效的示例代码相同。不同之处在于我在内部对文件内容进行分块并调用snd_pcm_write,而示例代码将整个文件内容呈现给snd_pcm_writei。在我的 sn-p 中我应该在哪里调用 snd_pcm_drain
  • 我已经更新了问题,还添加了一个完整的代码示例和我用来测试的 WAV 文件之一。我发现很难描述“重复”,所以希望这会有所帮助

标签: linux audio alsa libalsa libasound


【解决方案1】:
    frames = snd_pcm_writei(PlaybackHandle, bytes + count, buflen);
    count += frames;

snd_pcm_writei()frames 为单位测量大小,但您将其视为 bytes。所以你只跳过了刚刚播放的数据的四分之一。

【讨论】:

  • 我明白了。所以链接的 SO 问题中的代码 sn-p 起作用的原因是因为它在 do{...}while 循环的一次迭代中输入所有数据,从而使 frames 返回值无关紧要。
  • WaveSize 以帧为单位计算;但是如果count &gt; 0WavePtr + count 是错误的。
猜你喜欢
  • 2016-06-13
  • 2011-03-14
  • 2021-10-04
  • 2014-09-02
  • 1970-01-01
  • 2017-05-26
  • 2015-04-19
  • 2013-06-29
  • 1970-01-01
相关资源
最近更新 更多