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

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

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

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

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

迅速

在iOS录制:

  • 创建和维护AVAudioRecorder的实例,如var audioRecorder: AVAudioRecorder? = nil 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 ] 

做到这一点,你已经完成了。


您可能还想查看此Stack Overflow答案 ,其中包括演示项目 。

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指针来访问单个样本。