2015-06-20 54 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指針來訪問單個樣本。