【问题标题】:How to capture audio samples in iOS with Swift?如何使用 Swift 在 iOS 中捕获音频样本?
【发布时间】:2015-09-06 13:48:41
【问题描述】:

我在网上找到了很多在 iOS 中处理音频的示例,但其中大多数都已经过时,不适用于我想要完成的任务。这是我的项目:

我需要从两个来源捕获音频样本 - 麦克风输入和存储的音频文件。我需要对这些样本执行 FFT 以生成整个剪辑的“指纹”,并应用一些额外的过滤器。最终目标是构建一种类似于Shazam等的歌曲识别软件。

在 iOS 8 中捕获单个音频样本以执行快速傅里叶变换的最佳方法是什么?我想最终会有一大堆它们,但我怀疑它可能不会像那样工作。其次,如何使用 Accelerate 框架来处理音频?这似乎是在 iOS 中对音频执行复杂分析的最有效方式。

我在网上看到的所有示例都是使用旧版本的 iOS 和 Objective-C,我无法成功地将它们翻译成 Swift。 iOS 8 是否为这类事情提供了一些新框架?

【问题讨论】:

  • 您可以从查看 Apple 自己的示例开始。它们可能在 Objective-C 中,但 API 没有改变。在任何情况下,所有的 vDSP_xx 函数都有一个 C API,实际上,您项目的分析部分可能希望用 C 或 C++ 编写(顺便提一下,这是苹果工程师在今年的 WWDC 上对编写音频的建议处理/渲染处理程序)。至于音频指纹,这是一个非常重要的问题,对 SO 来说也很重要。
  • 你找到什么了吗?

标签: ios swift audio signal-processing fft


【解决方案1】:

迅速

在 iOS 中录制:

  • 创建和维护AVAudioRecorder 的实例,如var audioRecorder: AVAudioRecorder? = nil
  • 使用 URL 初始化您的 AVAudioRecorder 以存储样本和一些记录设置

录制会话顺序:

  1. 调用prepareToRecord()
  2. 调用record()
  3. 调用stop()

完整的 Swift/AVAudioRecorder 示例

记录方法的核心是:

func record() {
    self.prepareToRecord()
    if let recorder = self.audioRecorder {
        recorder.record()
    }
}

要准备录制(流式传输到 file),您可以:

func prepareToRecord() {
    var error: NSError?
    let documentsPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as! NSString
    let soundFileURL: NSURL? = NSURL.fileURLWithPath("\(documentsPath)/recording.caf")
    
    self.audioRecorder = AVAudioRecorder(URL: soundFileURL, settings: recordSettings as [NSObject : AnyObject], error: &error)
    if let recorder = self.audioRecorder {
        recorder.prepareToRecord()
    }
}

最后,要停止录制,请使用:

func stopRecording() {
    if let recorder = self.audioRecorder {
        recorder.stop()
    }
}

上面的示例还需要import AVFoundation 和一些recordSettings,由您选择。 recordSettings 的示例可能如下所示:

let recordSettings = [
    AVFormatIDKey: kAudioFormatAppleLossless,
    AVEncoderAudioQualityKey : AVAudioQuality.Max.rawValue,
    AVEncoderBitRateKey : 320000,
    AVNumberOfChannelsKey: 2,
    AVSampleRateKey : 44100.0
]

这样做,你就完成了。


您可能还想查看this Stack Overflow answer,其中包括demo project

【讨论】:

【解决方案2】:

AVAudioEngine 是解决这个问题的方法。来自 Apple 的文档:

  • 要播放和录制单个曲目,请使用 AVAudioPlayer 和 AVAudioRecorder。
  • 对于更复杂的音频处理,请使用 AVAudioEngine。 AVAudioEngine 包括用于音频输入和输出的 AVAudioInputNode 和 AVAudioOutputNode。您还可以使用 AVAudioNode 对象来处理和混合效果到您的音频中

我会直截了当地告诉你:AVAudioEngine 是一个极其挑剔的 API,它的文档模糊不清,错误消息很少有用,而且几乎没有在线代码示例演示超出最基本任务的内容。 但是如果你花时间克服小的学习曲线,你真的可以相对轻松地做一些神奇的事情。

我构建了一个简单的“游乐场”视图控制器,它演示了麦克风和音频文件采样协同工作:

import UIKit

class AudioEnginePlaygroundViewController: UIViewController {
    private var audioEngine: AVAudioEngine!
    private var mic: AVAudioInputNode!
    private var micTapped = false
    override func viewDidLoad() {
        super.viewDidLoad()
        configureAudioSession()
        audioEngine = AVAudioEngine()
        mic = audioEngine.inputNode!
    }

    static func getController() -> AudioEnginePlaygroundViewController {
        let me = AudioEnginePlaygroundViewController(nibName: "AudioEnginePlaygroundViewController", bundle: nil)
        return me
    }

    @IBAction func toggleMicTap(_ sender: Any) {
        if micTapped {
            mic.removeTap(onBus: 0)
            micTapped = false
            return
        }

        let micFormat = mic.inputFormat(forBus: 0)
        mic.installTap(onBus: 0, bufferSize: 2048, format: micFormat) { (buffer, when) in
            let sampleData = UnsafeBufferPointer(start: buffer.floatChannelData![0], count: Int(buffer.frameLength))
        }
        micTapped = true
        startEngine()
    }

    @IBAction func playAudioFile(_ sender: Any) {
        stopAudioPlayback()
        let playerNode = AVAudioPlayerNode()

        let audioUrl = Bundle.main.url(forResource: "test_audio", withExtension: "wav")!
        let audioFile = readableAudioFileFrom(url: audioUrl)
        audioEngine.attach(playerNode)
        audioEngine.connect(playerNode, to: audioEngine.outputNode, format: audioFile.processingFormat)
        startEngine()

        playerNode.scheduleFile(audioFile, at: nil) {
            playerNode .removeTap(onBus: 0)
        }
        playerNode.installTap(onBus: 0, bufferSize: 4096, format: playerNode.outputFormat(forBus: 0)) { (buffer, when) in
            let sampleData = UnsafeBufferPointer(start: buffer.floatChannelData![0], count: Int(buffer.frameLength))
        }
        playerNode.play()
    }

    // MARK: Internal Methods

    private func configureAudioSession() {
        do {
            try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayAndRecord, with: [.mixWithOthers, .defaultToSpeaker])
            try AVAudioSession.sharedInstance().setActive(true)
        } catch { }
    }

    private func readableAudioFileFrom(url: URL) -> AVAudioFile {
        var audioFile: AVAudioFile!
        do {
            try audioFile = AVAudioFile(forReading: url)
        } catch { }
        return audioFile
    }

    private func startEngine() {
        guard !audioEngine.isRunning else {
            return
        }

        do {
            try audioEngine.start()
        } catch { }
    }

    private func stopAudioPlayback() {
        audioEngine.stop()
        audioEngine.reset()
    }
}

音频样本是通过 installTap 的完成处理程序提供给您的,当音频实时通过被点击的节点(麦克风或音频文件播放器)时,该完成处理程序会被连续调用。您可以通过索引我在每个块中创建的 sampleData 指针来访问单个样本。

【讨论】:

  • 这就是 OP 正在寻找的。感谢您在提出问题多年后添加此答案。
  • @BigHeadCreations 很高兴与 AVAudioEngine 信息的空白作斗争。谢谢你的点头:)
  • @WongWray 很好的答案。这方面的例子真的很少。在调用 ML 预测器之前,我正在尝试在输入抽头上进行采样率转换。我需要 4 秒的采样数据,比如环形缓冲区,所以我称之为预测。如果我为它创建一个问题,您是否有兴趣回答它? tnx
  • @WongWray 如何让音频引擎立即播放麦克风录制的音频?
猜你喜欢
  • 2019-01-14
  • 1970-01-01
  • 2018-09-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多