如何将音频效果应用于文件并写入文件系统 - iOS

Posted

技术标签:

【中文标题】如何将音频效果应用于文件并写入文件系统 - iOS【英文标题】:How to apply audio effect to a file and write to filesystem - iOS 【发布时间】:2017-08-21 14:29:08 【问题描述】:

我正在构建一个应用程序,该应用程序应允许用户将音频过滤器应用于录制的音频,例如混响、增强。

我无法找到有关如何将过滤器应用于文件本身的任何可行信息来源,因为稍后需要将处理后的文件上传到服务器。

我目前正在使用 AudioKit 进行可视化,并且我知道它能够进行音频处理,但仅用于播放。请提出任何进一步研究的建议。

【问题讨论】:

How to apply filters to previously recorded sound and save modified version using AudioKit?的可能重复 【参考方案1】:

AudioKit 有一个不需要 ios 11 的离线渲染节点。下面是一个示例,需要 player.schedule(...) 和 player.start(at.) 位,因为 AKAudioPlayer 的底层 AVAudioPlayerNode 将阻止调用如果您使用player.play() 启动它,线程会等待下一次渲染。

import UIKit
import AudioKit

class ViewController: UIViewController 

    var player: AKAudioPlayer?
    var reverb = AKReverb()
    var boost = AKBooster()
    var offlineRender = AKOfflineRenderNode()

    override func viewDidLoad() 
        super.viewDidLoad()

        guard let url = Bundle.main.url(forResource: "theFunkiestFunkingFunk", withExtension: "mp3") else 
            return
        
        var audioFile: AKAudioFile?
        do 
            audioFile = try AKAudioFile.init(forReading: url)
            player = try AKAudioPlayer.init(file: audioFile!)
         catch 
            print(error)
            return
        
        guard let player = player else 
            return
        


        player >>> reverb >>> boost >>> offlineRender

        AudioKit.output = offlineRender
        AudioKit.start()


        let docs = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
        let dstURL = docs.appendingPathComponent("rendered.caf")

        offlineRender.internalRenderEnabled = false
        player.schedule(from: 0, to: player.duration, avTime: nil)
        let sampleTimeZero = AVAudioTime(sampleTime: 0, atRate: AudioKit.format.sampleRate)
        player.play(at: sampleTimeZero)
        do 
            try offlineRender.renderToURL(dstURL, seconds: player.duration)
         catch 
            print(error)
            return
        
        offlineRender.internalRenderEnabled = true

        print("Done! Rendered to " + dstURL.path)
    

【讨论】:

嘿,戴夫,我正在尝试似乎有离线渲染的 AudioKit 4.0 (audiokitpro.com/audiokit-4-released),但似乎 AudioKit.mainMixer 不可用,所以我尝试了player >>> reverb >>> boost >>> offlineRender >>> **AudioKit.engine.mainMixerNode**,但它不工作.它会生成相同长度的音频,但只是静音。谢谢! 抱歉,AudioKit.mainMixer 从未合并。我会编辑答案。 我刚刚又试了一次,它正在工作,但我应该怎么做才能避免玩它?如果我删除 player.play() 文件又是空的。另外,我在 play() 之后立即尝试了 player.pause() 并且它崩溃了。提前致谢! 渲染到 url 之后,并且在将 offlineRender.internalRenderEnabled 设置为 true 之前,停止播放器。当你设置offlineRender.internalRenderEnabled = true时,你基本上是在恢复正常渲染。 效果很好!谢谢戴夫!【参考方案2】:

您可以使用音频单元插件中新引入的“手动渲染”功能(参见下面的示例)。

如果您需要支持较旧的 macOS/iOS 版本,如果您无法使用 AudioKit 实现相同的功能,我会感到惊讶(即使我自己没有尝试过)。例如,使用AKSamplePlayer 作为您的第一个节点(它将读取您的音频文件),然后构建并连接您的效果器并使用AKNodeRecorder 作为您的最后一个节点。

使用新音频单元功能的手动渲染示例

import AVFoundation

//: ## Source File
//: Open the audio file to process
let sourceFile: AVAudioFile
let format: AVAudioFormat
do 
    let sourceFileURL = Bundle.main.url(forResource: "mixLoop", withExtension: "caf")!
    sourceFile = try AVAudioFile(forReading: sourceFileURL)
    format = sourceFile.processingFormat
 catch 
    fatalError("could not open source audio file, \(error)")


//: ## Engine Setup
//:    player -> reverb -> mainMixer -> output
//: ### Create and configure the engine and its nodes
let engine = AVAudioEngine()
let player = AVAudioPlayerNode()
let reverb = AVAudioUnitReverb()

engine.attach(player)
engine.attach(reverb)

// set desired reverb parameters
reverb.loadFactoryPreset(.mediumHall)
reverb.wetDryMix = 50

// make connections
engine.connect(player, to: reverb, format: format)
engine.connect(reverb, to: engine.mainMixerNode, format: format)

// schedule source file
player.scheduleFile(sourceFile, at: nil)
//: ### Enable offline manual rendering mode
do 
    let maxNumberOfFrames: AVAudioFrameCount = 4096 // maximum number of frames the engine will be asked to render in any single render call
    try engine.enableManualRenderingMode(.offline, format: format, maximumFrameCount: maxNumberOfFrames)
 catch 
    fatalError("could not enable manual rendering mode, \(error)")

//: ### Start the engine and player
do 
    try engine.start()
    player.play()
 catch 
    fatalError("could not start engine, \(error)")

//: ## Offline Render
//: ### Create an output buffer and an output file
//: Output buffer format must be same as engine's manual rendering output format
let outputFile: AVAudioFile
do 
    let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
    let outputURL = URL(fileURLWithPath: documentsPath + "/mixLoopProcessed.caf")
    outputFile = try AVAudioFile(forWriting: outputURL, settings: sourceFile.fileFormat.settings)
 catch 
    fatalError("could not open output audio file, \(error)")


// buffer to which the engine will render the processed data
let buffer: AVAudioPCMBuffer = AVAudioPCMBuffer(pcmFormat: engine.manualRenderingFormat, frameCapacity: engine.manualRenderingMaximumFrameCount)!
//: ### Render loop
//: Pull the engine for desired number of frames, write the output to the destination file
while engine.manualRenderingSampleTime < sourceFile.length 
    do 
        let framesToRender = min(buffer.frameCapacity, AVAudioFrameCount(sourceFile.length - engine.manualRenderingSampleTime))
        let status = try engine.renderOffline(framesToRender, to: buffer)
        switch status 
        case .success:
            // data rendered successfully
            try outputFile.write(from: buffer)

        case .insufficientDataFromInputNode:
            // applicable only if using the input node as one of the sources
            break

        case .cannotDoInCurrentContext:
            // engine could not render in the current render call, retry in next iteration
            break

        case .error:
            // error occurred while rendering
            fatalError("render failed")
        
     catch 
        fatalError("render failed, \(error)")
    


player.stop()
engine.stop()

print("Output \(outputFile.url)")
print("AVAudioEngine offline rendering completed")

您可以找到更多有关 AudioUnit 格式 there 更新的文档和示例。

【讨论】:

但问题是,如果不播放文件,您将无法使用 AudioEngine 处理文件。 那一定是 iOS 11 的新功能。关于时间。谢谢! @matt 是的,这是 iOS 11/macOS High Sierra 的新功能。可以在developer.apple.com/videos/play/wwdc2017/501观看对应的WWDC(离线手动渲染部分8分钟左右开始,15分钟左右结束)

以上是关于如何将音频效果应用于文件并写入文件系统 - iOS的主要内容,如果未能解决你的问题,请参考以下文章

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

IOS实时录制音频并分割成文件

如何将图形的输出写入 WAV 或 AIF 格式的外部音频文件?

iPhone SDK:如何将从 iPod 库中挑选的音频文件写入我的应用程序的文档文件夹?

在 iPhone 中使用 FMOD 为音频添加效果并保存新音频

添加效果后导出音频文件