停止 AudioUnit 录制后出错

Posted

技术标签:

【中文标题】停止 AudioUnit 录制后出错【英文标题】:error after stopping AudioUnit Recording 【发布时间】:2014-11-30 22:45:08 【问题描述】:

我正在尝试使用以下代码从 iPhone 的麦克风中获取音频输入:

@property (nonatomic) AudioUnit rioUnit;
@property (nonatomic) CAStreamBasicDescription outputCASBD;

(...)

// set our required format - LPCM non-interleaved 32 bit floating point
CAStreamBasicDescription outFormat = CAStreamBasicDescription(44100, // sample rate
                                                              kAudioFormatLinearPCM, // format id
                                                              4, // bytes per packet
                                                              1, // frames per packet
                                                              4, // bytes per frame
                                                              1, // channels per frame
                                                              32, // bits per channel
                                                            kAudioFormatFlagIsFloat | kAudioFormatFlagIsNonInterleaved);

try 
    // Open the output unit
    AudioComponentDescription desc;
    desc.componentType = kAudioUnitType_Output;
    desc.componentSubType = kAudioUnitSubType_RemoteIO;
    desc.componentManufacturer = kAudioUnitManufacturer_Apple;
    desc.componentFlags = 0;
    desc.componentFlagsMask = 0;

    AudioComponent comp = AudioComponentFindNext(NULL, &desc);

    AudioComponentInstanceNew(comp, &_rioUnit);

    UInt32 one = 1;
    XThrowIfError(AudioUnitSetProperty(_rioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 1, &one, sizeof(one)), "couldn't enable input on the remote I/O unit");

    AURenderCallbackStruct callbackStruct;
    callbackStruct.inputProc = renderInput;
    callbackStruct.inputProcRefCon = (__bridge void*)self;
    XThrowIfError(AudioUnitSetProperty(_rioUnit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, 0, &callbackStruct, sizeof(callbackStruct)), "couldn't set remote i/o render callback");

    XThrowIfError(AudioUnitSetProperty(_rioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &outFormat, sizeof(outFormat)), "couldn't set the remote I/O unit's output client format");
    XThrowIfError(AudioUnitSetProperty(_rioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &outFormat, sizeof(outFormat)), "couldn't set the remote I/O unit's input client format");

    XThrowIfError(AudioUnitInitialize(_rioUnit), "couldn't initialize the remote I/O unit");
    XThrowIfError(AudioOutputUnitStart(_rioUnit), "couldn't start the remote I/O unit");

catch (CAXException &e) 
    char buf[256];
    fprintf(stderr, "Error: %s (%s)\n", e.mOperation, e.FormatError(buf));

catch (...) 
    fprintf(stderr, "An unknown error occurred\n");


// Allocate our own buffers (1 channel, 16 bits per sample, thus 16 bits per frame, thus 2 bytes per frame).
// Practice learns the buffers used contain 512 frames, if this changes it will be fixed in processAudio.
// will need float for accellerate fft (input is 16 bit signed integer) so make space...
_tempBuffer.mNumberChannels = outFormat.mChannelsPerFrame;
_tempBuffer.mDataByteSize = 512 * outFormat.mBytesPerFrame * 2;
_tempBuffer.mData = malloc( self.tempBuffer.mDataByteSize );

一切都很好,直到我想停止收听麦克风输入。

我正在这样做:

    AudioOutputUnitStop(_rioUnit);
    AudioUnitUninitialize(_rioUnit);
    AudioComponentInstanceDispose(_rioUnit);
    _rioUnit = nil;

但是我从为kAudioOutputUnitProperty_SetInputCallback 定义的回调函数内部得到一个错误。

错误是:AURemoteIO::IOThread (14): EXC_BAD_ACCESS (code=1, address=0x8000000c)

在我看来,AudioUnit 的 InputCallback 仍在被调用,即使我正在停止它、对其实例进行统一化和处置。

为什么会发生这种情况?如何防止 InputCallback 在我停止、统一化和处置其实例后被调用?

【问题讨论】:

您在使用 AUGraph 吗?如果是这样,你是在调用 AUGraphStop() 吗? 我不是。原来_rioUnit = nil; 导致了这个问题。将其更改为 _rioUnit = NULL; 即可解决。 【参考方案1】:

首先,确保 _rioUnit 在 stop 和 start 中确实具有相同的值。

正在另一个异步线程中调用音频输入回调。因此,在取消初始化或处置音频单元或任何其他所需的数据结构之前,您可能需要等到该线程也停止(这通常发生在少于 2 个音频缓冲区持续时间的时间内,因此这可能是一个合理的等待时间) .

【讨论】:

谢谢你的想法,我还以为是取消初始化错误的_rioUnit。然而,事实证明它实际上并没有正确地取消初始化它。必须将其设置为NULL,而不是nil。请参阅我的回答了解更多详情。【参考方案2】:

问题出在这一行:

_rioUnit = nil; // causes EXC_BAD_ACCESS error

nil 更改为NULL 解决了这个问题:

_rioUnit = NULL; // works smoothly

我从这篇文章中得到了这个想法: http://teragonaudio.com/article/How-to-do-realtime-recording-with-effect-processing-on-ios.html

另外,this answer 概述了Objective-CnilNULL 之间的区别。

【讨论】:

以上是关于停止 AudioUnit 录制后出错的主要内容,如果未能解决你的问题,请参考以下文章

AudioUnit 每 30 秒录制一次故障

AudioUnits 和 MPMusicPlayerController

将 AudioUnit 回调与 NSOutputStream 同步

AudioUnit - 在 Swift 中控制左声道和右声道输出

CoreAudio:AudioUnit 既不能停止也不能未初始化

启动 AVAssetReader 停止对远程 I/O AudioUnit 的回调