Нажмите аудио выход с помощью AVAudioEngine - PullRequest
0 голосов
/ 29 апреля 2020

Я пытаюсь установить сигнал на выходной звук, который воспроизводится в моем приложении. У меня нет проблем с захватом буфера с входа микрофона, но когда дело доходит до перехвата звука, который идет через динамик, динамик или какое-либо другое устройство вывода, это не удается. Я что-то упустил?

В моем примере я пытаюсь перехватить аудиобуфер из аудиофайла, который воспроизводит 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'

1 Ответ

3 голосов
/ 06 мая 2020

Я столкнулся с той же проблемой и в течение 2 дней мозгового штурма обнаружил следующее:

Apple говорит, что для AVAudioOutputNode формат касания должен быть указан как nil. Я не уверен, что это важно, но в моем случае это сработало, формат был нулевым. Вы должны начать запись и не забудьте остановить ее.

Удаление касания действительно важно, иначе у вас будет файл, который вы не сможете открыть.

Попробуйте сохранить файл с теми же настройками звука, которые вы использовали в исходном файле.

Вот мой код, который наконец заработал. Он был частично взят из этого вопроса Сохранение аудио After Effects в 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
}
...