AVAudioEngine inputNode installTap 重启录音时崩溃

Posted

技术标签:

【中文标题】AVAudioEngine inputNode installTap 重启录音时崩溃【英文标题】:AVAudioEngine inputNode installTap crash when restarting recording 【发布时间】:2017-06-07 21:44:11 【问题描述】:

我正在我的应用中实现语音识别。当我第一次向视图控制器展示语音识别逻辑时,一切正常。但是,当我再次尝试显示视图控制器时,会出现以下崩溃:

ERROR:    [0x190bf000] >avae> AVAudioNode.mm:568: CreateRecordingTap: required condition is false: IsFormatSampleRateAndChannelCountValid(format)
*** Terminating app due to uncaught exception 'com.apple.coreaudio.avfaudio', reason: 'required condition is false: IsFormatSampleRateAndChannelCountValid(format)'

这是用于开始和停止录制的代码:

@available(ios 10.0, *)
extension DictationViewController 

fileprivate func startRecording() throws 
    guard let recognizer = speechRecognizer else 
        debugLog(className, message: "Not supported for the device's locale")
        return
    

    guard recognizer.isAvailable else 
        debugLog(className, message: "Recognizer is not available right now")
        return
    

    mostRecentlyProcessedSegmentDuration = 0
    guard let node = audioEngine.inputNode else 
        debugLog(className, message: "Could not get an input node")
        return
    

    let recordingFormat = node.outputFormat(forBus: 0)
    node.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat)  [weak self] (buffer, _) in
        self?.request.append(buffer)
    

    audioEngine.prepare()
    try audioEngine.start()

    recognitionTask = recognizer.recognitionTask(with: request, resultHandler: /***/)


fileprivate func stopRecording() 
    audioEngine.stop()
    audioEngine.inputNode?.removeTap(onBus: 0)
    request.endAudio()
    recognitionTask?.cancel()



startRecording() 在我们请求授权后在 viewDidLoad 中调用。 stopRecording() 在视图控制器关闭时调用。

请帮忙。我正在努力寻找解决此崩溃的方法

【问题讨论】:

您找到解决方案了吗?我遇到了类似的崩溃,但仅限于在 iOS 8.1 上运行时?? 【参考方案1】:

您可以替换此代码:

let recordingFormat = node.outputFormat(forBus: 0)

以下内容:

let recordingFormat = AVAudioFormat(standardFormatWithSampleRate: 44100, channels: 1)

这段代码解决了这个问题。

【讨论】:

感谢您的修复。但是 outputFormat(forBus: 0) 不起作用的原因是什么? 因为你试图用 outputNode 的格式来监听 inputNode。 但是 AVAudioNode 类中的 installTapOnBus 方法示例显示开发人员可以这样使用此方法; AVAudioEngine *engine = [[AVAudioEngine alloc] init]; AVAudioInputNode *input = [引擎输入节点]; AVAudioFormat *format = [输入输出格式ForBus:0]; [input installTapOnBus: 0 bufferSize: 8192 format: format block: ^(AVAudioPCMBuffer *buf, AVAudioTime *when) // ‘buf’ 包含在 ‘when’ 时间从输入节点捕获的音频 ];他们将 outputformatonbus 提供给 installtaponbus 方法 @Daedelus OP 没有使用 outputNode 的格式。 OP 正在使用 inputNode 的 outputFormat。 您应该永远假设采样率:iPhone X 及以上设备的采样率我认为是 48000,请使用AVAudioEngine.inputNode.outputFormat.sampleRate 确定您的设备采样率,同时确保你的AVAudioSession.sharedInstance().sampleRate在录制之前是一样的,否则你会崩溃。【参考方案2】:

首先,一个小问题。当点击设备的麦克风时,您需要使用 input 总线的格式:

let recordingFormat = node.inputFormat(forBus: 0)

其次,经过一番挖掘,似乎这种崩溃最常见的原因是您的应用程序的共享 AVAudioSession 类别设置。如果您要执行实时麦克风音频处理,请确保您的音频会话配置如下:

private func configureAudioSession() 
    do 
        try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayAndRecord, with: .mixWithOthers)
        try AVAudioSession.sharedInstance().setActive(true)
     catch  

【讨论】:

第一部分是错误的。我们应该使用 inputNode 的“outputFormat”。 @joshmori 需要详细说明吗? 在总线上安装一个音频分接头来记录、监控和观察“节点的输出”。来自developer.apple.com/documentation/avfoundation/avaudionode/… 节点的输出 => 使用该节点的 outputFormat 有趣的是,您链接的示例在点击麦克风时使用了 outputFormat。我认为由于麦克风只处理语音 input,因此您需要一个 inputFormat。我在网上找到了几个例子,一些使用 inputFormat 一些使用 outputFormat。也许有人可以在这里解释差异?【参考方案3】:

有两种可能的方法来解决这个问题。

    检查inputFormat.channelCount。它可能会抛出错误,因为麦克风正在另一个应用程序或您的其他地方使用。
if(inputNode.inputFormat(forBus: 0).channelCount == 0)
    NSLog("Not enough available inputs!")
    return

    尝试重置audioEngine
audioEngine.reset()

【讨论】:

【参考方案4】:

我在接听电话时尝试使用语音识别时遇到required condition is false: IsFormatSampleRateAndChannelCountValid(format) 崩溃,这导致采样率为零。 我的解决方案是创建下面的audioInputIsBusy() 函数并在try audioSession.setCategory(.record, mode: .measurement, options: .duckOthers) 之前调用它 这防止了崩溃,我显示了“语音识别不可用”的消息,然后使用audioEngine = AVAudioEngine() 重置音频引擎。

func audioInputIsBusy(recordingFormat: AVAudioFormat) -> Bool 
    guard recordingFormat.sampleRate == 0 || recordingFormat.channelCount == 0 else 
        return false
    

    return true

ps:let recordingFormat = audioEngine.inputNode.outputFormat(forBus: 0)

【讨论】:

嗨,你能提供更多代码吗?你的意思是重置引擎然后再次调用 installTap? @famfamfam 是的,事件的顺序将是这样的......之间还有其他逻辑:1. audioEngine = AVAudioEngine() 2. guard !audioInputIsBusy(recordingFormat: recordingFormat) else 3 .audioEngine.inputNode.installTap 4.尝试audioEngine.start()【参考方案5】:

在每次开始运行之前试试这个:

audioEngine = AVAudioEngine()

【讨论】:

还是会导致崩溃【参考方案6】:

我必须在 installTap 之前调用removeTap() 才能使其正常工作。上述解决方案都不适合我。

//Remove tap first.
inputNode.removeTap(onBus: 0)

// Configure the microphone input.
let recordingFormat = inputNode.inputFormat(forBus: 0)            
inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat)  (buffer: AVAudioPCMBuffer, when: AVAudioTime) in
            //process buffer...
        

【讨论】:

以上是关于AVAudioEngine inputNode installTap 重启录音时崩溃的主要内容,如果未能解决你的问题,请参考以下文章

Swift AVAudioEngine - 如何使本地麦克风静音

JavaScript------创建节点

AvaudioEngine - 以特定采样率录制语音 AvaudioEngine for Analysis

在 iOS 上使用 avaudioengine 会破坏 avplayer

使用 AVAudioEngine 进行电平测量

设置 AVAudioEngine 输入和输出设备