ALSA - 非阻塞(交错)读取

Posted

技术标签:

【中文标题】ALSA - 非阻塞(交错)读取【英文标题】:ALSA - Non blocking (interleaved) read 【发布时间】:2019-07-04 11:05:37 【问题描述】:

我继承了一些在 Linux 嵌入式平台上运行的 ALSA 代码。 现有实现使用snd_pcm_readi()snd_pcm_writei() 进行阻塞 读写。

我的任务是让它在 ARM 处理器上运行,但我发现阻塞的交错读取将 CPU 推到了 99%,所以我正在探索非阻塞读取和写入。

我按预期打开设备:

snd_pcm_handle *handle;
const char* hwname = "plughw:0"; // example name

snd_pcm_open(&handle, hwname, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK);

然后会发生其他 ALSA 内容,我可以根据要求提供。 在这一点上值得注意的是:

我们将采样率设置为 48,000 [Hz] 样本类型是带符号的 32 位整数 设备总是将我们请求的周期大小覆盖为 1024 帧

像这样读取流:

int32* buffer; // buffer set up to hold #period_size samples
int actual = snd_pcm_readi(handle, buffer, period_size);

在阻塞模式下完成此调用大约需要 15 [ms]。显然,变量actual 返回时会读取 1024。

问题是;在非阻塞模式下,这个函数也需要 15 毫秒才能完成,actual 在返回时也总是读取 1024。

我希望函数会立即返回,actual

在两次读取尝试之间,我计划让线程休眠一段特定的时间,从而将 CPU 时间留给其他进程。

我是否误解了 ALSA API?还是我的代码缺少关键步骤?

【问题讨论】:

在阻塞模式下,snd_pcm_read*() 休眠并等待来自设备的中断。您的 CPU 使用问题不是阻塞模式引起的。 来自snd_pcm_readi() 上的 ALSA 文档:“如果选择了非阻塞行为,则例程根本不会等待。” 【参考方案1】:

如果函数返回值 1024,则在调用时至少有 1024 帧可用。 (有可能这 15 毫秒是驱动程序实际启动设备所需的时间。)

无论如何,阻塞或非阻塞模式对 CPU 使用率没有任何影响。要减少 CPU 使用率,请将default 设备替换为plughwhw,但随后您将失去设备共享或采样率/格式转换等功能。

【讨论】:

每次调用snd_pcm_readi() 大约需要 15 [ms]。不仅仅是第一个。自从发布问题后,我在阅读之前向snd_pcm_avail() 添加了一个查询。每次我的线程点击该调用时,大约有 310 帧可用。我确信 snd_pcm_readi() 会阻塞,直到它获得剩余的 714 帧,尽管设备被打开为非阻塞。 嗯……你确定程序没有调用snd_pcm_nonblock()吗? 我昨天确实想知道这一点,我为这个调用搜索了代码库,但什么也没找到。是否有我可以调用的 ALSA API 函数来告诉我设备是阻塞还是非阻塞? 回到我的第一条评论(为了后代目的).... 根据snd_pcm_avail(),在启动时有超过 1024 帧可用。毫不奇怪,随后对snd_pcm_readi() 的调用需要0 [毫秒]。这仅持续 3 或 4 个循环,然后缓冲区耗尽并稳定在 310 帧可用。【参考方案2】:

我通过包装snd_pcm_readi() 解决了我的问题,如下所示:

/*
** Read interleaved stream in non-blocking mode
*/
template <typename SampleType>
snd_pcm_sframes_t snd_pcm_readi_nb(snd_pcm_t* pcm, SampleType* buffer, snd_pcm_uframes_t size, unsigned samplerate)

    const snd_pcm_sframes_t avail = ::snd_pcm_avail(pcm);
    if (avail < 0) 
        return avail;
    

    if (avail < size) 

        snd_pcm_uframes_t remain = size - avail;
        unsigned long msec = (remain * 1000) / samplerate;

        static const unsigned long SLEEP_THRESHOLD_MS = 1;

        if (msec > SLEEP_THRESHOLD_MS) 
            msec -= SLEEP_THRESHOLD_MS;
            // exercise for the reader: sleep for msec
        
    

    return ::snd_pcm_readi(pcm, buffer, size);

这对我来说效果很好。我的音频处理现在“只”占用 19% 的 CPU 时间。 PCM接口是用SND_PCM_NONBLOCK还是0打开的也无所谓。

将执行 callgrind 分析,看看是否可以在代码的其他地方节省更多的 CPU 周期。

【讨论】:

以上是关于ALSA - 非阻塞(交错)读取的主要内容,如果未能解决你的问题,请参考以下文章

Linux 阻塞与非阻塞串行读取

通过阅读python subprocess源码尝试实现非阻塞读取stdout以及非阻塞wait

linux网络编程中阻塞和非阻塞socket的区别

在 Python 中对 subprocess.PIPE 进行非阻塞读取

在 Python 中对 subprocess.PIPE 进行非阻塞读取

在 Python 中对 subprocess.PIPE 进行非阻塞读取