iOS 音频系统。开始&停止还是只是开始?

iOS audio system. Start & stop or just start?

我有一个应用程序,其中录音是主要和最重要的部分。但是,用户可以切换到 table 显示所有记录但不执行记录的视图控制器。

问题是哪种方法更好:"start & stop audio system or just start it"。显然第一个更正确,例如 "allocate when you need it, deallocate when used it"。我会表达我对这个问题的看法,我希望在技术人员中通过争论找到赞同或不赞成的意见。

当我第一次构造 AudioController.m 时,我实现了 open/close 音频会话和 start/stop 音频单元的方法。我想在录音未激活时停止音频系统。我使用了以下代码:

- (BOOL)startAudioSystem {

    // open audio session
    AVAudioSession *audioSession = [AVAudioSession sharedInstance];
    NSError *err = nil;
    if (![audioSession setActive:YES error:&err] ) {
        NSLog(@"Couldn't activate audio session: %@", err);
    }
    // start audio unit
    OSStatus status;
    status = AudioOutputUnitStart([self audioUnit]);
    BOOL noErrors = err == nil && status == noErr;    
    return noErrors;
}

- (BOOL)stopAudioSystem {
    // stop audio unit
    BOOL result;
    result = AudioOutputUnitStop([self audioUnit]) == noErr;
    HANDLE_RESULT(result);
    // close audio session
    NSError *err;
    HANDLE_RESULT([[AVAudioSession sharedInstance] setActive:NO withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:&err]);
    HANDLE_ERROR(err);
    BOOL noErrors = err == nil && result;
    return noErrors;
}

我发现这种方法有问题,原因如下:

  1. 音频系统延迟启动。这意味着 recording_callback() 有一段时间没有调用了。我怀疑是 AudioOutputUnitStart 造成的。我试图用这个函数调用注释掉这一行并将其移至初始化。延迟消失了。
  2. 如果用户非常非常快地在录音视图和table视图之间执行切换(音频系统的启动和停止也非常快),它会导致媒体服务死亡(我知道观察 AVAudioSessionMediaServicesWereResetNotification 可以帮助这里,但这不是重点)。

为了解决这些问题,我用我设法发现的其他方法修改了 AudioController.m:当应用程序激活时启动音频系统,并且在应用程序终止之前不要停止它 这种情况下还有几个问题:

  1. CPU 用法
  2. 如果音频类别设置为仅录音,则当用户浏览 table 视图控制器时无法播放其他音频。

第一个令人惊讶的没什么大不了的,如果取消 recording_callback() 中的任何一种处理,就像这样:

    static OSStatus recordingCallback(void *inRefCon,
                                  AudioUnitRenderActionFlags *ioActionFlags,
                                  const AudioTimeStamp *inTimeStamp,
                                  UInt32 inBusNumber,
                                  UInt32 inNumberFrames,
                                  AudioBufferList *ioData) {

    AudioController *input = (__bridge AudioController*)inRefCon;

    if(!input->shouldPerformProcessing)
        return noErr;

    // processing
    // ...
    //

    return noErr;
}

通过这样做 CPU 在真实设备上使用率等于 0%,当不需要录制并且不执行其他操作时。

第二个问题可以通过将音频类别切换到 RecordAndPlay 并启用混音来解决,或者忽略该问题。比如我的应用需要外接设备使用mini Jack,所以不能并联耳机。

尽管如此,第一种方法更接近我,因为我喜欢在不再需要时每隔 stream/resource close/clean。而且我想确定除了启动音频系统之外确实没有其他选择。请确保我不是唯一找到此解决方案的人,而且它是正确的。

解决这个问题的关键是要注意音频系统实际上是在另一个(实时)线程中运行的。而且你不能在你(或应用程序的主UI线程)"don't need it"时真正停止并在另一个线程中释放某些东西运行,但必须延迟以允许另一个线程意识到它需要做一些事情然后完成并清理自己。对于音频,这可能需要多达 100 毫秒。

鉴于此,策略 2(刚刚开始)更安全、更现实。

或者在尝试停止音频之前设置许多秒的不使用延迟,然后可能在尝试重新启动之前再设置一个短暂的延迟。