【问题标题】:Tap audio output using AVAudioEngine使用 AVAudioEngine 点击音频输出
【发布时间】:2020-08-13 19:56:48
【问题描述】:

我正在尝试在我的应用上播放的输出音频上安装一个水龙头。我从麦克风输入捕获缓冲区没有问题,但是当它捕获通过扬声器或听筒或任何输出设备的声音时,它没有成功。我错过了什么吗?

在我的示例中,我试图从 AVPLayer 正在播放的音频文件中捕获音频缓冲区。但是让我们假设我没有直接访问 AVPlayer 实例的权限。

目标是对音频流执行语音识别。

func catchAudioBuffers() throws {
    let audioSession = AVAudioSession.sharedInstance()
    try audioSession.setCategory(.playAndRecord, mode: .voiceChat, options: .allowBluetooth)
    try audioSession.setActive(true)

    let outputNode = audioEngine.outputNode

    let recordingFormat = outputNode.outputFormat(forBus: 0)
    outputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) { (buffer: AVAudioPCMBuffer, when: AVAudioTime) in
      // PROCESS AUDIO BUFFER
    }

    audioEngine.prepare()
    try audioEngine.start()

    // For example I am playing an audio conversation with an AVPlayer and a local file.
    player.playSound()
}

此代码导致:

AVAEInternal.h:76    required condition is false: [AVAudioIONodeImpl.mm:1057:SetOutputFormat: (_isInput)]
*** Terminating app due to uncaught exception 'com.apple.coreaudio.avfaudio', reason: 'required condition is false: _isInput'

【问题讨论】:

    标签: ios avfoundation core-audio audiounit avaudioengine


    【解决方案1】:

    我遇到了同样的问题,在 2 天的头脑风暴中发现了以下问题。

    Apple 说对于 AVAudioOutputNode,tap 格式必须指定为 nil。我不确定这是否重要,但就我而言,最终奏效了,格式为零。 您需要开始录制并且不要忘记停止它。

    移除水龙头非常重要,否则您将有无法打开的文件。

    尝试使用您在源文件中使用的相同音频设置保存文件。

    这是我终于奏效的代码。部分取自这个问题Saving Audio After Effect in iOS

        func playSound() {
        let rate: Float? = effect.speed
        let pitch: Float? = effect.pitch
        let echo: Bool? = effect.echo
        let reverb: Bool? = effect.reverb
    
        // initialize audio engine components
        audioEngine = AVAudioEngine()
    
        // node for playing audio
        audioPlayerNode = AVAudioPlayerNode()
        audioEngine.attach(audioPlayerNode)
    
        // node for adjusting rate/pitch
        let changeRatePitchNode = AVAudioUnitTimePitch()
        if let pitch = pitch {
            changeRatePitchNode.pitch = pitch
        }
        if let rate = rate {
            changeRatePitchNode.rate = rate
        }
        audioEngine.attach(changeRatePitchNode)
    
        // node for echo
        let echoNode = AVAudioUnitDistortion()
        echoNode.loadFactoryPreset(.multiEcho1)
        audioEngine.attach(echoNode)
    
        // node for reverb
        let reverbNode = AVAudioUnitReverb()
        reverbNode.loadFactoryPreset(.cathedral)
        reverbNode.wetDryMix = 50
        audioEngine.attach(reverbNode)
    
        // connect nodes
        if echo == true && reverb == true {
            connectAudioNodes(audioPlayerNode, changeRatePitchNode, echoNode, reverbNode, audioEngine.mainMixerNode, audioEngine.outputNode)
        } else if echo == true {
            connectAudioNodes(audioPlayerNode, changeRatePitchNode, echoNode, audioEngine.mainMixerNode, audioEngine.outputNode)
        } else if reverb == true {
            connectAudioNodes(audioPlayerNode, changeRatePitchNode, reverbNode, audioEngine.mainMixerNode, audioEngine.outputNode)
        } else {
            connectAudioNodes(audioPlayerNode, changeRatePitchNode, audioEngine.mainMixerNode, audioEngine.outputNode)
        }
    
        // schedule to play and start the engine!
        audioPlayerNode.stop()
        audioPlayerNode.scheduleFile(audioFile, at: nil) {
            var delayInSeconds: Double = 0
            if let lastRenderTime = self.audioPlayerNode.lastRenderTime, let playerTime = self.audioPlayerNode.playerTime(forNodeTime: lastRenderTime) {
                if let rate = rate {
                    delayInSeconds = Double(self.audioFile.length - playerTime.sampleTime) / Double(self.audioFile.processingFormat.sampleRate) / Double(rate)
                } else {
                    delayInSeconds = Double(self.audioFile.length - playerTime.sampleTime) / Double(self.audioFile.processingFormat.sampleRate)
                }
            }
    
            // schedule a stop timer for when audio finishes playing
            self.stopTimer = Timer(timeInterval: delayInSeconds, target: self, selector: #selector(EditViewController.stopAudio), userInfo: nil, repeats: false)
            RunLoop.main.add(self.stopTimer!, forMode: RunLoop.Mode.default)
        }
    
        do {
            try audioEngine.start()
        } catch {
            showAlert(Alerts.AudioEngineError, message: String(describing: error))
            return
        }
    
        //Try to save
        let dirPaths: String = (NSSearchPathForDirectoriesInDomains(.libraryDirectory, .userDomainMask, true)[0]) + "/sounds/"
        let tmpFileUrl = URL(fileURLWithPath: dirPaths + "effected.caf")
    
        //Save the tmpFileUrl into global varibale to not lose it (not important if you want to do something else)
        filteredOutputURL = URL(fileURLWithPath: filePath)
            do{
                print(dirPaths)
                let settings = [AVSampleRateKey : NSNumber(value: Float(44100.0)),
                AVFormatIDKey : NSNumber(value: Int32(kAudioFormatMPEG4AAC)),
                AVNumberOfChannelsKey : NSNumber(value: 1),
                AVEncoderAudioQualityKey : NSNumber(value: Int32(AVAudioQuality.medium.rawValue))]
                self.newAudio = try! AVAudioFile(forWriting: tmpFileUrl as URL, settings: settings)
                let length = self.audioFile.length
                audioEngine.mainMixerNode.installTap(onBus: 0, bufferSize: 4096, format: nil) {
                    (buffer: AVAudioPCMBuffer?, time: AVAudioTime!) -> Void in
                    //Let us know when to stop saving the file, otherwise saving infinitely
                    if (self.newAudio.length) <= length {
                        do{
                            try self.newAudio.write(from: buffer!)
    
                        } catch _{
                            print("Problem Writing Buffer")
                        }
                    } else {
                        //if we dont remove it, will keep on tapping infinitely
                        self.audioEngine.mainMixerNode.removeTap(onBus: 0)
                    }
                }
        }
    
        // play the recording!
        audioPlayerNode.play()
    
    }
    
    @objc func stopAudio() {
        if let audioPlayerNode = audioPlayerNode {
            let engine = audioEngine
            audioPlayerNode.stop()
            engine?.mainMixerNode.removeTap(onBus: 0)
    
        }
        if let stopTimer = stopTimer {
            stopTimer.invalidate()
        }
        configureUI(.notPlaying)
        if let audioEngine = audioEngine {
            audioEngine.stop()
            audioEngine.reset()
        }
        isPlaying = false
    }
    

    【讨论】:

      猜你喜欢
      • 2021-06-29
      • 2020-11-02
      • 2014-08-14
      • 2016-02-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-01-27
      相关资源
      最近更新 更多