iOS:在回调函数中调用 setNeedDisplay 时出现线程问题

标签 ios objective-c multithreading audio

上下文如下: 一段时间以来,我一直在开发与音频相关的应用程序,但我有点碰壁,不确定下一步该怎么做。

我最近在应用程序中实现了一个自定义类,用于绘制音频输出的 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];
                });

我的问题是什么:

  1. 我遇到了各种线程问题,尤其是在主线程上,例如 Thread 1: EXC_BAD_ACCESS (code=1, address=0xf00000c),有时在应用程序启动时调用 viewDidLoad,但是每当我尝试与任何 UI 对象交互时。
  2. 即使在 FFT 显示器上,用户界面响应速度也变得异常缓慢。

我认为问题是: 这肯定与线程问题有关,您可能知道,但我对这个主题真的没有经验。我考虑过可能会在主线程上强制更新任何 UI 显示,以再次解决我遇到的问题;我什至不确定如何正确地做到这一点。

任何输入/见解都将是一个巨大的帮助。 提前致谢!

最佳答案

正如所写,您的 SpectralView* 需要完全线程安全。

您的 for() 循环首先将帧/样本处理推到高优先级并发队列。由于这是异步的,它将立即返回,此时您的代码将对主要威胁加入更新频谱 View 显示的请求。

这几乎可以保证光谱 View 必须与后台处理代码同时更新显示,同时更新光谱 View 的状态。

还有第二个问题;您的代码最终将并行处理所有 channel 。通常,未缓解的并发性是导致性能下降的原因。此外,您将在每个 channel 的主线程上进行更新,无论该 channel 的处理是否已完成。


代码需要重构。您确实应该将模型层与 View 层分开。模型层可以编写为线程安全的,在处理过程中,您可以抓取要显示的数据的快照并将其扔到 SpectralView 中。或者,您的模型层可以有一个 isProcessing 标志,SpectralView 可以关闭该标志以了解它不应该读取数据。

这是相关的:

https://developer.apple.com/library/ios/documentation/General/Conceptual/ConcurrencyProgrammingGuide/Introduction/Introduction.html

关于iOS:在回调函数中调用 setNeedDisplay 时出现线程问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35280650/

相关文章:

ios - ARC 实例变量保留释放

ios - ios:“共享扩展名”不允许我将.txt或.csv文件共享到我的应用

iOS swift : How to overlay two images that zoom in at same time without loosing coordinates of overlaying image

java - 通过 executorservice 进行顺序事件处理

ios - 在 Groupedtableview 的末尾添加一个没有边框颜色的透明 UITableViewCell

iphone - 从 NSCoder 解码对象时,最好的中止方法是什么?

ios - 在 IOS 中使用自动布局在邻居之间居中 View

objective-c - NSXMLParser 只读取一半的属性

ios - NSThread参数问题

java - MySQL - 多线程更新(没有线程更新相同的id)但有死锁