2015-06-20 85 views
12

我在网上发现了很多在iOS上使用音频的例子,但是其中大多数都非常过时,并且不适用于我想要实现的功能。这是我的项目:如何使用Swift在iOS中捕获音频样本?

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

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

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

+0

你可以先看看苹果自己的例子。它们可能在Objective-C中,但API没有改变。 在任何情况下,所有的vDSP_xx函数都有一个C API,并且实际上,您的项目的分析部分可能需要用C或C++编写(顺便说一句,苹果工程师在今年WWDC上的建议是编写音频处理/渲染处理程序)。 至于音频指纹识别,这是一个不平凡的问题,也是SO的板子。 – marko

+0

你有什么发现? – hoangpx

回答

7

迅速

记录在iOS设备上:

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

记录会话序列:

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

完整斯威夫特/ 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

此信息很有帮助,但是如何从录音中提取单个音频样本?我需要原始数据 - 最好是可以执行分析的Float数组。同样的问题适用于已经在磁盘上的文件。 – hundley

+0

假设您使用上面的'kAudioFormatAppleLossless'格式,示例存储在https://developer.apple.com/library/ios/documentation/MusicAudio/Reference/CAFSpec/CAF_overview/CAF_overview.html#/中记录的CAF文件中/ apple_ref/DOC/UID/TP40001862-CH209-TPXREF101。从这样的文件读取样本在http://stackoverflow.com/questions/13996236/how-to-convert-wav-caf-files-sample-data-to-byte-array回答。 – SwiftArchitect

+1

我发现你的http://swiftarchitect.com/recipes/#SO-32342486非常有用。谢谢。 – vivin

0

AVAudioEngine就是为此而走的路。从苹果公司的文档:

  • 播放和单轨的记录,使用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指针来访问单个样本。