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
设备替换为plughw
或hw
,但随后您将失去设备共享或采样率/格式转换等功能。
【讨论】:
每次调用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 - 非阻塞(交错)读取的主要内容,如果未能解决你的问题,请参考以下文章
通过阅读python subprocess源码尝试实现非阻塞读取stdout以及非阻塞wait
在 Python 中对 subprocess.PIPE 进行非阻塞读取