ios - 如何使用 Swift 在 iOS 中捕获音频样本?

标签 ios swift audio signal-processing fft

我在网上找到了很多关于在 iOS 中处理音频的示例,但其中大部分都已经过时并且不适用于我要实现的目标。这是我的项目:

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

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

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

最佳答案

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

  • For playback and recording of a single track, use AVAudioPlayer and AVAudioRecorder.
  • For more complex audio processing, use AVAudioEngine. AVAudioEngine includes AVAudioInputNode and AVAudioOutputNode for audio input and output. You can also use AVAudioNode objects for processing and mixing effects into your audio

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

我构建了一个简单的“ Playground ” View Controller ,演示了麦克风和音频文件采样的协同工作:

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 的完成处理程序提供给您,当音频实时通过被点击的节点(麦克风或音频文件播放器)时,该处理程序会不断调用。您可以通过索引我在每个 block 中创建的 sampleData 指针来访问单个样本。

关于ios - 如何使用 Swift 在 iOS 中捕获音频样本?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30957434/

相关文章:

audio - Tumblr音频和视频+无限滚动

java - Java和USB声卡中的声音问题

windows - 他们是否存在可以存储或保存系统音频的任何软件?

ios - 如何正确调整 UILabel 内容的大小? (查看帖子内的图片以便更好地理解)

ios - 在应用内购买后重新加载 SKScene 或 View 以删除 iAd

ios - 如何将图片插入mysql数据库

iphone - 应用程序在后台时计划的 NSTimer?

ios - 如何仅在 swiftUI 中将拖动手势限制到特定帧

swift - UICollectionView header 中的标题无法正确显示

ios - URLSession 完成处理程序应用程序崩溃