上下文如下: 一段时间以来,我一直在开发与音频相关的应用程序,但我有点碰壁,不确定下一步该怎么做。
我最近在应用程序中实现了一个自定义类,用于绘制音频输出的 FFT 显示。此类是 UIView 的子类,这意味着每次我需要绘制新的 FFT 更新时,我都需要使用新的样本值在我的类实例上调用 setNeedDisplay
。
因为我需要为每一帧绘制一个新的 FFT(帧 ~= 1024 个样本),这意味着我的 FFT 的显示函数被调用了很多次(1024/SampleRate ~= 0.02321 秒)。至于样本计算,它是 44'100/秒。我在 iOS 中管理线程方面并没有真正的经验,所以我阅读了一些相关内容,下面是我是如何做到的。
它是如何完成的:我有一个 NSObject“AudioEngine.h”的子类,它负责我的应用程序中的所有 DSP 处理,这是我设置 FFT 显示的地方。所有样本值都经过计算并分配给我在 dispatch_get_global_queue
block 内的 FFT 子类,因为这些值需要在后台不断更新。一旦样本索引达到最大帧数,就会调用 setneedDisplay
方法,这是在 dispatch_async(dispatch_get_main_queue)
block 中完成的
在“AudioEngine.m”中
for (k = 0; k < nchnls; k++) {
buffer = (SInt32 *) ioData->mBuffers[k].mData;
if (cdata->shouldMute == false) {
buffer[frame] = (SInt32) lrintf(spout[nsmps++]*coef) ;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
@autoreleasepool {
// FFT display init here as a singleton
SpectralView *specView = [SpectralView sharedInstance];
//Here is created a pointer to the "samples" property of my subclass
Float32 *specSamps = [specView samples];
//set the number of frames the FFT should take
[specView setInNumberFrames:inNumberFrames];
//scaling sample values
specSamps[frame] = (buffer[frame] * (1./coef) * 0.5);
}
});
} else {
// If output is muted
buffer[frame] = 0;
}
}
//once the number of samples has reached ksmps (vector size) we update the FFT
if (nsmps == ksmps*nchnls){
dispatch_async(dispatch_get_main_queue(), ^{
SpectralView *specView = [SpectralView sharedInstance];
[specView prepareToDraw];
[specView setNeedsDisplay];
});
我的问题是什么:
- 我遇到了各种线程问题,尤其是在主线程上,例如
Thread 1: EXC_BAD_ACCESS (code=1, address=0xf00000c)
,有时在应用程序启动时调用 viewDidLoad,但是每当我尝试与任何 UI 对象交互时。 - 即使在 FFT 显示器上,用户界面响应速度也变得异常缓慢。
我认为问题是: 这肯定与线程问题有关,您可能知道,但我对这个主题真的没有经验。我考虑过可能会在主线程上强制更新任何 UI 显示,以再次解决我遇到的问题;我什至不确定如何正确地做到这一点。
任何输入/见解都将是一个巨大的帮助。 提前致谢!
最佳答案
正如所写,您的 SpectralView*
需要完全线程安全。
您的 for()
循环首先将帧/样本处理推到高优先级并发队列。由于这是异步的,它将立即返回,此时您的代码将对主要威胁加入更新频谱 View 显示的请求。
这几乎可以保证光谱 View 必须与后台处理代码同时更新显示,同时更新光谱 View 的状态。
还有第二个问题;您的代码最终将并行处理所有 channel 。通常,未缓解的并发性是导致性能下降的原因。此外,您将在每个 channel 的主线程上进行更新,无论该 channel 的处理是否已完成。
代码需要重构。您确实应该将模型层与 View 层分开。模型层可以编写为线程安全的或,在处理过程中,您可以抓取要显示的数据的快照并将其扔到 SpectralView 中。或者,您的模型层可以有一个 isProcessing
标志,SpectralView 可以关闭该标志以了解它不应该读取数据。
这是相关的:
关于iOS:在回调函数中调用 setNeedDisplay 时出现线程问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35280650/