我正在尝试使用PortAudio制作音频应用程序。我的回调函数非常慢,并且一直在产生持续的欠载。我一步一步地删除了回调中的所有内容,直到发现问题:for循环。我删除了所有内容,以便在回调函数中唯一发生的事情是for循环,并且仍然导致欠载。我知道这是for循环,因为当我减少迭代次数时,欠载就消失了。
library(tidyverse)
dataset_subset <- tribble(
~"Unit", ~"Group", ~"Feature1", ~"Feature2", ~"Feature3", ~"Feature4",
1, 1, "blue" , "x", "a", "12",
2, 1, "yellow", "y", "b", "15",
3, 2, "green" , "x", "a", "13",
4, 3, "indigo", "z", "c", "12",
5, 1, "green" , "y", "b", "16")
dataset_subset %>%
pivot_longer(contains("Feature")) %>%
ggplot(aes(x = value)) +
geom_bar(aes(y = ..prop.., group = name), stat = "count", fill = "#f68060", alpha =.6, width = .4) +
scale_y_continuous(labels = scales::percent) +
facet_wrap(~name, scales = "free_x")
这是我测试的完整代码:https://gist.github.com/johnroper100/b87641f5609dbb49bc3c1121b1f4daf1
这个问题并不是很必要,但是我在python等效设备(声音设备)中执行了相同的回调,并且没有问题。
答案 0 :(得分:6)
在流运行时,PortAudio会定期调用流回调。回调函数负责处理通过输入和输出参数传递的音频样本的缓冲区。
PortAudio流回调以很高的优先级或实时优先级运行。它必须始终满足其时间期限。不要从流回调中分配内存,访问文件系统,调用库函数或调用其他函数,这些回调可能会阻塞或花费不可预测的时间来完成。
为了使流保持无故障操作,回调必须消耗和返回音频数据,其速度要比记录和/或播放的速度快。 PortAudio预计每次回调调用可能会执行一段时间以流采样率接近frameCount音频帧的持续时间。合理地期望能够利用PortAudio回调中70%或更多的可用CPU时间。但是,由于缓冲区大小的调整和其他因素,并非所有的主机API都能在任意固定回调缓冲区大小的情况下,在繁重的CPU负载下保证音频稳定性。 当需要较高的回调CPU使用率时,可以通过将paFramesPerBufferUnspecified指定为Pa_OpenStream()framesPerBuffer参数来实现最可靠的行为。
我突出显示了相关部分。
答案 1 :(得分:3)
一个没有任何内容的for循环对于您的CPU来说仍然是艰苦的工作。
如果您不希望CPU将所有功能都花在该for循环上,则至少应使该for循环包含放弃CPU周期的内容。
最简单的方法是在其中调用sleep()或nanosleep()。
更高级的方法是使用具有适当机制的线程,使它们等待所需的操作。
答案 2 :(得分:2)
我认为文档(在提供的答案@ÁdámHunyadi中)解释了回调函数无法正常运行的原因。但是我不确定它是否解释为什么 for循环太慢。
为了使流保持无故障操作,回调 必须消耗和返回音频数据的速度快于记录和/或 。PortAudio预计每次回调调用都可能 执行的持续时间接近frameCount音频的持续时间 帧。合理的期望是 能够利用PortAudio中70%或更多的可用CPU时间 打回来。但是,由于缓冲区大小调整和其他因素, 所有主机API均能在CPU繁重的情况下保证音频稳定性 加载任意固定的回调缓冲区大小。 高回调时 需要CPU利用率,才能实现最可靠的行为 通过使用paFramesPerBufferUnspecified作为Pa_OpenStream() framesPerBuffer参数。
看一下PortAudio Doc Example中的示例,它们遍历for-loop
迭代的framesPerBuffer
(在文档中定义为framesCount
)。 处理frameCount的持续时间。但是在您的代码示例中,您正在做但一样的操作,但操作的大小却是vector
次的大小(我相信这不是 frameCount的持续时间)。仅使用一个for-loop
而不是两个,并且使用framesPerBuffer
而不是10000
作为最大迭代次数可能会解决“循环太慢” 问题
答案 3 :(得分:0)
除了可能会导致您的循环或整个程序变慢的特定于API的问题外,我还将为您的一般问题提供一般性建议。如果您忽略返回值,则始终偏爱前缀(++i;
)而不是后缀(i++;
),因为前缀不会返回您要在其上应用运算符的值/对象的副本。因此,可以避免不必要的复制操作。虽然,大多数现代编译器确实会为您动态替换它,但是它们可能会以某种方式被您的代码所欺骗,因此始终最好养成使用前缀递增的习惯。当然,您需要使用-O3
参数来编译代码以获得最高性能,并确保编译器将尽其所能。要进一步优化循环的另一种选择是多线程。
答案 4 :(得分:0)
在patestCallback结束之后,可能会调用捕获新帧的代码,如果您无法按时完成任务,则意味着您的CPU功能不够强大(=处理过于复杂)。这不是(为什么)解释,而是一种解决方案,如果您的CPU中有多个Core(即使您拥有一个Core,有时它也可以工作),该解决方案应该可以工作,您可以创建一个处理音频的线程,回调应该仅将(或更改指针)复制到要处理的音频,这使您的主线程(音频捕获线程)不必等待太久。