使用 AVAudioEngine 离线渲染音频文件

Posted

技术标签:

【中文标题】使用 AVAudioEngine 离线渲染音频文件【英文标题】:Render audio file offline using AVAudioEngine 【发布时间】:2018-09-05 11:33:40 【问题描述】:

我想录制音频文件并通过应用一些效果来保存它。 录音没问题,播放这个有效果的音频也没问题。 问题是当我尝试离线保存此类音频时,它会产生空音频文件。 这是我的代码:

let effect = AVAudioUnitTimePitch()
effect.pitch = -300
self.addSomeEffect(effect)

func addSomeEffect(_ effect: AVAudioUnit) 
    try? AVAudiosession.sharedInstance().setCategory(AVAudioSessionCategoryPlayAndRecord, with: .defaultToSpeaker)

    let format = self.audioFile.processingFormat
    self.audioEngine.stop()
    self.audioEngine.reset()

    self.audioEngine = AVAudioEngine()
    let audioPlayerNode = AVAudioPlayerNode()


    self.audioEngine.attach(audioPlayerNode)
    self.audioEngine.attach(effect)

    self.audioEngine.connect(audioPlayerNode, to: self.audioEngine.mainMixerNode, format: format)
    self.audioEngine.connect(effect, to: self.audioEngine.mainMixerNode, format: format)

    audioPlayerNode.scheduleFile(self.audioFile, at: nil)
    do 
        let maxNumberOfFrames: AVAudioFrameCount = 8096
        try self.audioEngine.enableManualRenderingMode(.offline,
                                                       format: format,
                                                       maximumFrameCount: maxNumberOfFrames)
     catch 
        fatalError()
    


    do 
        try audioEngine.start()
        audioPlayerNode.play()
     catch 

    

    let outputFile: AVAudioFile
    do 
        let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("temp.m4a")
        if FileManager.default.fileExists(atPath: url.path) 
            try? FileManager.default.removeItem(at: url)
        

        let recordSettings = self.audioFile.fileFormat.settings

        outputFile = try AVAudioFile(forWriting: url, settings: recordSettings)
     catch 
        fatalError()
    

    let buffer = AVAudioPCMBuffer(pcmFormat: self.audioEngine.manualRenderingFormat,
                                  frameCapacity: self.audioEngine.manualRenderingMaximumFrameCount)!

    while self.audioEngine.manualRenderingSampleTime < self.audioFile.length 
        do 
            let framesToRender = min(buffer.frameCapacity,
                                     AVAudioFrameCount(self.audioFile.length - self.audioEngine.manualRenderingSampleTime))
            let status = try self.audioEngine.renderOffline(framesToRender, to: buffer)
            switch status 
            case .success:
                print("Write to file")
                try outputFile.write(from: buffer)
            case .error:
                fatalError()
            default:
                break
            
         catch 
            fatalError()
        
    
    print("Finish write")


    audioPlayerNode.stop()
    audioEngine.stop()


    self.outputFile = outputFile
    self.audioPlayer = try? AVAudioPlayer(contentsOf: outputFile.url)


AVAudioPlayer 无法打开带有输出 url 的文件。我通过文件系统查看了文件,是空的,无法播放。

为 AVAudioSession 选择不同的类别也不起作用。

感谢您的帮助!

更新

我切换到在输出文件的记录中使用 .caf 文件扩展名,并且它有效。知道为什么 .m4a 不起作用吗?

【问题讨论】:

你需要 nil outputFile 来刷新头部并关闭 m4a 文件。 @RhythmicFistman 谢谢,它有效! 【参考方案1】:

你需要niloutputFile刷新头部并关闭m4a文件。

【讨论】:

抱歉,为了清楚起见,您的意思是在所有内容完成渲染后的某个时间点将 outputFile 设置为 nil 吗? 没错。在你写完最后一个缓冲区之后,你通常会调用 close 你将 AVAudioFile 设置为 nil。 顺便说一句,这在我的书中是一个非常愚蠢的 API 决定。使用对象清理的副作用来关闭文件既不容易发现,也不确定。我不明白为什么AVAudioFile 没有明确的close() 方法。

以上是关于使用 AVAudioEngine 离线渲染音频文件的主要内容,如果未能解决你的问题,请参考以下文章

AUGraph 弃用是不是意味着不再有音频渲染回调?

使用 AVAudioEngine 重复播放音频文件

我可以离线渲染具有动态速度的音频文件吗?

使用 AVAudioEngine 录制音频文件

我可以使用 AVAudioEngine 从文件中读取,使用音频单元处理并写入文件,比实时更快吗?

AVAudioEngine 播放多声道音频