在iOS中保存音频后效果

8

我正在开发一个应用程序,让用户可以通过应用程序录制和修改他们的声音并分享。基本上,我已经完成了很多工作,现在是向您求助的时候了。这是我的播放功能,在播放录制的音频文件时添加音效。

private func playAudio(pitch : Float, rate: Float, reverb: Float, echo: Float) {
        // Initialize variables
        audioEngine = AVAudioEngine()
        audioPlayerNode = AVAudioPlayerNode()
        audioEngine.attachNode(audioPlayerNode)

        // Setting the pitch
        let pitchEffect = AVAudioUnitTimePitch()
        pitchEffect.pitch = pitch
        audioEngine.attachNode(pitchEffect)

        // Setting the platback-rate
        let playbackRateEffect = AVAudioUnitVarispeed()
        playbackRateEffect.rate = rate
        audioEngine.attachNode(playbackRateEffect)

        // Setting the reverb effect
        let reverbEffect = AVAudioUnitReverb()
        reverbEffect.loadFactoryPreset(AVAudioUnitReverbPreset.Cathedral)
        reverbEffect.wetDryMix = reverb
        audioEngine.attachNode(reverbEffect)

        // Setting the echo effect on a specific interval
        let echoEffect = AVAudioUnitDelay()
        echoEffect.delayTime = NSTimeInterval(echo)
        audioEngine.attachNode(echoEffect)

        // Chain all these up, ending with the output
        audioEngine.connect(audioPlayerNode, to: playbackRateEffect, format: nil)
        audioEngine.connect(playbackRateEffect, to: pitchEffect, format: nil)
        audioEngine.connect(pitchEffect, to: reverbEffect, format: nil)
        audioEngine.connect(reverbEffect, to: echoEffect, format: nil)
        audioEngine.connect(echoEffect, to: audioEngine.outputNode, format: nil)

        audioPlayerNode.stop()

        let length = 4000
        let buffer = AVAudioPCMBuffer(PCMFormat: audioPlayerNode.outputFormatForBus(0),frameCapacity:AVAudioFrameCount(length))
        buffer.frameLength = AVAudioFrameCount(length)

        try! audioEngine.start()


        let dirPaths: AnyObject = NSSearchPathForDirectoriesInDomains( NSSearchPathDirectory.DocumentDirectory,  NSSearchPathDomainMask.UserDomainMask, true)[0]
        let tmpFileUrl: NSURL = NSURL.fileURLWithPath(dirPaths.stringByAppendingPathComponent("effectedSound.m4a"))


        do{
            print(dirPaths)
            let settings = [AVFormatIDKey: NSNumber(unsignedInt: kAudioFormatMPEG4AAC), AVSampleRateKey: NSNumber(integer: 44100), AVNumberOfChannelsKey: NSNumber(integer: 2)]
            self.newAudio = try AVAudioFile(forWriting: tmpFileUrl, settings: settings)

            audioEngine.outputNode.installTapOnBus(0, bufferSize: (AVAudioFrameCount(self.player!.duration)), format: self.audioPlayerNode.outputFormatForBus(0)){
                (buffer: AVAudioPCMBuffer!, time: AVAudioTime!)  in

                print(self.newAudio.length)
                print("=====================")
                print(self.audioFile.length)
                print("**************************")
                if (self.newAudio.length) < (self.audioFile.length){

                    do{
                        //print(buffer)
                        try self.newAudio.writeFromBuffer(buffer)
                    }catch _{
                        print("Problem Writing Buffer")
                    }
                }else{
                    self.audioPlayerNode.removeTapOnBus(0)
                }

            }
        }catch _{
            print("Problem")
        }

        audioPlayerNode.play()

    }

我猜问题出在我给audioPlayerNode安装了installTapOnBus,但是影响音频的是audioEngine.outputNode。然而,我尝试给audioEngine.outputNode安装installTapOnBus,但是出现了错误。我也尝试将效果连接到audioEngine.mixerNode,但这也不是一个解决方案。那么你有没有保存受影响的音频文件的经验?我该如何获取这个受影响的音频呢?
非常感谢您的帮助。
6个回答

5

这是我针对问题的解决方案:

func playAndRecord(pitch : Float, rate: Float, reverb: Float, echo: Float) {
    // Initialize variables

// These are global variables . if you want you can just  (let audioEngine = etc ..) init here these variables
    audioEngine = AVAudioEngine()
    audioPlayerNode = AVAudioPlayerNode()
    audioEngine.attachNode(audioPlayerNode)
    playerB = AVAudioPlayerNode()

    audioEngine.attachNode(playerB)

    // Setting the pitch
    let pitchEffect = AVAudioUnitTimePitch()
    pitchEffect.pitch = pitch
    audioEngine.attachNode(pitchEffect)

    // Setting the platback-rate
    let playbackRateEffect = AVAudioUnitVarispeed()
    playbackRateEffect.rate = rate
    audioEngine.attachNode(playbackRateEffect)

    // Setting the reverb effect
    let reverbEffect = AVAudioUnitReverb()
    reverbEffect.loadFactoryPreset(AVAudioUnitReverbPreset.Cathedral)
    reverbEffect.wetDryMix = reverb
    audioEngine.attachNode(reverbEffect)

    // Setting the echo effect on a specific interval
    let echoEffect = AVAudioUnitDelay()
    echoEffect.delayTime = NSTimeInterval(echo)
    audioEngine.attachNode(echoEffect)

    // Chain all these up, ending with the output
    audioEngine.connect(audioPlayerNode, to: playbackRateEffect, format: nil)
    audioEngine.connect(playbackRateEffect, to: pitchEffect, format: nil)
    audioEngine.connect(pitchEffect, to: reverbEffect, format: nil)
    audioEngine.connect(reverbEffect, to: echoEffect, format: nil)
    audioEngine.connect(echoEffect, to: audioEngine.mainMixerNode, format: nil)


    // Good practice to stop before starting
    audioPlayerNode.stop()

    // Play the audio file 
// this player is also a global variable  AvAudioPlayer
    if(player != nil){
    player?.stop()
    }

    // audioFile here is our original audio
    audioPlayerNode.scheduleFile(audioFile, atTime: nil, completionHandler: {
        print("Complete")
    })


    try! audioEngine.start()


    let dirPaths: AnyObject = NSSearchPathForDirectoriesInDomains( NSSearchPathDirectory.DocumentDirectory,  NSSearchPathDomainMask.UserDomainMask, true)[0]
    let tmpFileUrl: NSURL = NSURL.fileURLWithPath(dirPaths.stringByAppendingPathComponent("effectedSound2.m4a"))

//Save the tmpFileUrl into global varibale to not lose it (not important if you want to do something else)
filteredOutputURL = tmpFileUrl

    do{
        print(dirPaths)

        self.newAudio = try! AVAudioFile(forWriting: tmpFileUrl, settings:  [
            AVFormatIDKey: NSNumber(unsignedInt:kAudioFormatAppleLossless),
            AVEncoderAudioQualityKey : AVAudioQuality.Low.rawValue,
            AVEncoderBitRateKey : 320000,
            AVNumberOfChannelsKey: 2,
            AVSampleRateKey : 44100.0
            ])

        let length = self.audioFile.length


        audioEngine.mainMixerNode.installTapOnBus(0, bufferSize: 1024, format: self.audioEngine.mainMixerNode.inputFormatForBus(0)) {
            (buffer: AVAudioPCMBuffer!, time: AVAudioTime!) -> Void in


            print(self.newAudio.length)
            print("=====================")
            print(length)
            print("**************************")

            if (self.newAudio.length) < length {//Let us know when to stop saving the file, otherwise saving infinitely

                do{
                    //print(buffer)
                    try self.newAudio.writeFromBuffer(buffer)
                }catch _{
                    print("Problem Writing Buffer")
                }
            }else{
                self.audioEngine.mainMixerNode.removeTapOnBus(0)//if we dont remove it, will keep on tapping infinitely

                //DO WHAT YOU WANT TO DO HERE WITH EFFECTED AUDIO

             }

        }
    }catch _{
        print("Problem")
    }

    audioPlayerNode.play()

}

1
我们能保存带有效果的音频文件吗? - Pradeep Bishnoi
1
只想保存新生成的音频文件。上述代码会播放文件。有没有办法在混合音频时停止播放声音? - ZaEeM ZaFaR

4

这似乎没有正确连接起来。我自己正在学习这些,但我发现当你将它们连接到混音节点时,效果是正确添加的。另外,您需要点击混音器而不是引擎输出节点。我刚刚复制了您的代码,并进行了一些修改以考虑到这一点。

private func playAudio(pitch : Float, rate: Float, reverb: Float, echo: Float) {
    // Initialize variables
    audioEngine = AVAudioEngine()
    audioPlayerNode = AVAudioPlayerNode()
    audioEngine.attachNode(audioPlayerNode)

    // Setting the pitch
    let pitchEffect = AVAudioUnitTimePitch()
    pitchEffect.pitch = pitch
    audioEngine.attachNode(pitchEffect)

    // Setting the playback-rate
    let playbackRateEffect = AVAudioUnitVarispeed()
    playbackRateEffect.rate = rate
    audioEngine.attachNode(playbackRateEffect)

    // Setting the reverb effect
    let reverbEffect = AVAudioUnitReverb()
    reverbEffect.loadFactoryPreset(AVAudioUnitReverbPreset.Cathedral)
    reverbEffect.wetDryMix = reverb
    audioEngine.attachNode(reverbEffect)

    // Setting the echo effect on a specific interval
    let echoEffect = AVAudioUnitDelay()
    echoEffect.delayTime = NSTimeInterval(echo)
    audioEngine.attachNode(echoEffect)

    // Set up a mixer node
    let audioMixer = AVAudioMixerNode()
    audioEngine.attachNode(audioMixer)

    // Chain all these up, ending with the output
    audioEngine.connect(audioPlayerNode, to: playbackRateEffect, format: nil)
    audioEngine.connect(playbackRateEffect, to: pitchEffect, format: nil)
    audioEngine.connect(pitchEffect, to: reverbEffect, format: nil)
    audioEngine.connect(reverbEffect, to: echoEffect, format: nil)
    audioEngine.connect(echoEffect, to: audioMixer, format: nil)
    audioEngine.connect(audioMixer, to: audioEngine.outputNode, format: nil)

    audioPlayerNode.stop()

    let length = 4000
    let buffer = AVAudioPCMBuffer(PCMFormat: audioPlayerNode.outputFormatForBus(0),frameCapacity:AVAudioFrameCount(length))
    buffer.frameLength = AVAudioFrameCount(length)

    try! audioEngine.start()


    let dirPaths: AnyObject = NSSearchPathForDirectoriesInDomains( NSSearchPathDirectory.DocumentDirectory,  NSSearchPathDomainMask.UserDomainMask, true)[0]
    let tmpFileUrl: NSURL = NSURL.fileURLWithPath(dirPaths.stringByAppendingPathComponent("effectedSound.m4a"))


    do{
        print(dirPaths)
        let settings = [AVFormatIDKey: NSNumber(unsignedInt: kAudioFormatMPEG4AAC), AVSampleRateKey: NSNumber(integer: 44100), AVNumberOfChannelsKey: NSNumber(integer: 2)]
        self.newAudio = try AVAudioFile(forWriting: tmpFileUrl, settings: settings)

        audioMixer.installTapOnBus(0, bufferSize: (AVAudioFrameCount(self.player!.duration)), format: self.audioMixer.outputFormatForBus(0)){
            (buffer: AVAudioPCMBuffer!, time: AVAudioTime!)  in

            print(self.newAudio.length)
            print("=====================")
            print(self.audioFile.length)
            print("**************************")
            if (self.newAudio.length) < (self.audioFile.length){

                do{
                    //print(buffer)
                    try self.newAudio.writeFromBuffer(buffer)
                }catch _{
                    print("Problem Writing Buffer")
                }
            }else{
                self.audioMixer.removeTapOnBus(0)
            }

        }
    }catch _{
        print("Problem")
    }

    audioPlayerNode.play()

}

我也曾经遇到了文件格式的问题。当我将输出文件路径从 m4a 改为 caf 时,问题最终得到了解决。另一个建议是不要将 format 参数设置为 nil。我使用了 audioFile.processingFormat。希望这能有所帮助。我的音频效果/混音已经正常运行,尽管我没有将所有效果链接在一起。所以请随时提问。


@KBB 我有一个问题,那就是原始音频文件在哪里?它是之前录制然后播放的吗?如果是这样,你需要安排该文件 - audioPlayerNode.scheduleFile(recordedAudioFile, atTime: nil, completionHandler: nil) 并在调用 audioPlayerNode.play() 之前启动 audioEngine。 - FromTheStix
这绝对是我要写作为对自己问题的答案的内容:D 我通过将最后一个效果连接到混音节点并在混音节点上放置一个触发器来解决了它,下面我将写出我的解决方案。 - Kaan Baris Bayrak
太好了!很高兴有所帮助。我一直从缓冲区获取数据,当缓冲区写入文件时,文件长度会增加,但是音频文件没有持续时间。当我将文件类型更改为.caf时,它就可以被读取/播放了。 - FromTheStix

1

只需将参数unsigned int从kAudioFormatMPEG4AAC更改为kAudioFormatLinearPCM,并将文件类型更改为.caf,这肯定会对我的朋友有所帮助。


你救了我的一整天 :) 谢谢 - Akif Demirezen
1
谢谢兄弟,你也可以用其他方法做到这一点,我从这个项目中学到了更多关于这个东西的知识,所以请查看 https://github.com/alvesmarcel/V-Voice-Changer/tree/master/V%20Voice%20Changer - Rock Patekar

0
对于那些不得不将音频文件播放两次才能保存的人,我只是在相应的位置添加了以下行并解决了我的问题。希望将来可以帮到某些人。
顺便提一下:我使用的是与上面选中答案完全相同的代码,只是添加了这一行就解决了我的问题。
//Do what you want to do here with effected Audio

   self.newAudio = try! AVAudioFile(forReading: tmpFileUrl)

0
我们可以使用一种特定的方法来调整声音,如外星人、男性、老年人、机器人、儿童等,而且具有回放计数器。
var delayInSecond: Double = 0
        if let lastRenderTime = self.audioPlayerNode.lastRenderTime, let playerTime = self.audioPlayerNode.playerTime(forNodeTime: lastRenderTime)
        {
            if let rate = rate {
                delayInSecond = Double(self.audioFile.length - playerTime.sampleTime) / Double(self.audioFile.processingFormat.sampleRate) / Double(rate)
            }else{
                delayInSecond = Double(self.audioFile.length - playerTime.sampleTime) / Double(self.audioFile.processingFormat.sampleRate)
            }
            //schedule a stop timer for when audio finishes playing
            self.stopTimer = Timer(timeInterval: delayInSecond, target: self, selector: #selector(stopPlay), userInfo: nil, repeats: true)
            RunLoop.main.add(self.stopTimer, forMode: .default)
            
        }

-1

在我添加后,我得到了这个结果

self.newAudio = try! AVAudioFile(forReading: tmpFileUrl)

返回像这样

Error
Domain=com.apple.coreaudio.avfaudio
Code=1685348671 "(null)" UserInfo={failed
call=ExtAudioFileOpenURL((CFURLRef)fileUR
L, &_extAudioFile)}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接