一步一步教你實現(xiàn)iOS音頻頻譜動畫(一)

原文連結(jié):
作者:potato04
鏈接:https://juejin.im/post/5c1bbec66fb9a049cb18b64c
來源:掘金
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。

一步一步教你實現(xiàn)iOS音頻頻譜動畫(一)

image

如果你想先看看最終效果再決定看不看文章 -> bilibili
示例代碼下載

第二篇:一步一步教你實現(xiàn)iOS音頻頻譜動畫(二)

基于篇幅考慮捉超,本次教程分為兩篇文章,本篇文章主要講述音頻播放和頻譜數(shù)據(jù)的獲取,下篇將講述數(shù)據(jù)處理和動畫繪制勿侯。

前言

很久以前在電腦上聽音樂的時候,經(jīng)常會調(diào)出播放器的一個小工具缴罗,里面的柱狀圖會隨著音樂節(jié)奏而跳動助琐,就感覺自己好專業(yè),盡管后來才知道這個是音頻信號在頻域下的表現(xiàn)面氓。

熱身知識

動手寫代碼之前兵钮,讓我們先了解幾個基礎(chǔ)概念吧

音頻數(shù)字化

  • 采樣: 總所周知,聲音是一種壓力波舌界,是連續(xù)的掘譬,然而在計算機(jī)中無法表示連續(xù)的數(shù)據(jù),所以只能通過間隔采樣的方式進(jìn)行離散化呻拌,其中采集的頻率稱為采樣率屁药。根據(jù)奈奎斯特采樣定理 ,當(dāng)采樣率大于信號最高頻率的2倍時信號頻率不會失真。人類能聽到的聲音頻率范圍是20hz到20khz酿箭,所以CD等采用了44.1khz采樣率能滿足大部分需要复亏。

  • 量化: 每次采樣的信號強(qiáng)度也會有精度的損失,如果用16位的Int(位深度)來表示缭嫡,它的范圍是[-32768,32767]缔御,因此位深度越高可表示的范圍就越大,音頻質(zhì)量越好妇蛀。

  • 聲道數(shù): 為了更好的效果耕突,聲音一般采集左右雙聲道的信號,如何編排呢评架?一種是采用交錯排列(Interleaved): LRLRLRLR 眷茁,另一種采用各自排列(non-Interleaved): LLL RRR

以上將模擬信號數(shù)字化的方法稱為脈沖編碼調(diào)制(PCM)纵诞,而本文中我們就需要對這類數(shù)據(jù)進(jìn)行加工處理上祈。

傅里葉變換

現(xiàn)在我們的音頻數(shù)據(jù)是時域的,也就是說橫軸是時間浙芙,縱軸是信號的強(qiáng)度登刺,而動畫展現(xiàn)要求的橫軸是頻率。將信號從時域轉(zhuǎn)換成頻域可以使用傅里葉變換實現(xiàn)嗡呼,信號經(jīng)過傅里葉變換分解成了不同頻率的正弦波纸俭,這些信號的頻率和振幅就是我們需要實現(xiàn)動畫的數(shù)據(jù)。

image

圖1 (from nti-audio) 傅里葉變換將信號從時域轉(zhuǎn)換成頻域

實際上計算機(jī)中處理的都是離散傅里葉變換(DFT)南窗,而快速傅里葉變換(FFT)是快速計算離散傅里葉變換(DFT)或其逆變換的方法揍很,它將DFT的復(fù)雜度從O(n2)降低到O(nlogn)。 如果你剛才點開前面鏈接看過其中介紹的FFT算法万伤,那么可能會覺得這FFT代碼該怎么寫女轿?不用擔(dān)心,蘋果為此提供了Accelerate框架壕翩,其中vDSP部分提供了數(shù)字信號處理的函數(shù)實現(xiàn)蛉迹,包含F(xiàn)FT。有了vDSP放妈,我們只需幾個步驟即可實現(xiàn)FFT北救,簡單便捷,而且性能高效芜抒。

iOS中的音頻框架

現(xiàn)在開始讓我們看一下iOS系統(tǒng)中的音頻框架珍策, AudioToolbox功能強(qiáng)大,不過提供的API都是基于C語言的宅倒,其大多數(shù)功能已經(jīng)可以通過AVFoundation實現(xiàn)攘宙,它利用Objective-C/Swift對于底層接口進(jìn)行了封裝。我們本次需求比較簡單,只需要播放音頻文件并進(jìn)行實時處理蹭劈,所以AVFoundation中的AVAudioEngine就能滿足本次音頻播放和處理的需要疗绣。

image

圖2 (from WWDC16) iOS/MAC OS X 音頻技術(shù)棧

AVAudioEngine

AVAudioEngine 從iOS8加入到AVFoundation框架,它提供了以前需要深入到底層AudioToolbox才實現(xiàn)的功能铺韧,比如實時音頻處理多矮。它把音頻處理的各環(huán)節(jié)抽象成AVAudioNode并通過AVAudioEngine進(jìn)行管理,最后將它們連接構(gòu)成完整的節(jié)點圖哈打。以下就是本次教程的AVAudioEngine與其節(jié)點的連接方式塔逃。

image

圖3 AVAudioEngine和AVAudioNode連接圖

mainMixerNodeoutputNode都是在被訪問的時候默認(rèn)由AVAudioEngine對象創(chuàng)建并連接的單例對象,也就是說我們只需要手動創(chuàng)建engineplayer節(jié)點并將他們連接就可以了料仗!最后在mainMixerNode的輸出總線上安裝分接頭將定量輸出的AVAudioPCMBuffer數(shù)據(jù)進(jìn)行轉(zhuǎn)換和FFT湾盗。

代碼實現(xiàn)

了解了以上相關(guān)知識后,我們就可以開始編寫代碼了立轧。打開項目AudioSpectrum01-starter格粪,首先要實現(xiàn)的是音頻播放功能。

如果你只是想瀏覽實現(xiàn)代碼肺孵,打開項目AudioSpectrum01-final即可,已經(jīng)完成本篇文章的所有代碼

音頻播放

AudioSpectrumPlayer類中創(chuàng)建AVAudioEngineAVAudioPlayerNode兩個實例變量:

接下來在init()方法中添加如下代碼:

//1:這里將player掛載到engine上颜阐,再把playerenginemainMixerNode連接起來就完成了AVAudioEngine的整個節(jié)點圖創(chuàng)建(詳見圖3)平窘。
//2:在調(diào)用enginestrat()方法開始啟動engine之前,需要通過prepare()方法提前分配相關(guān)資源

繼續(xù)完善play(withFileName fileName: String)stop()方法:

//1:首先需要確保文件名為fileName的音頻文件能正常加載凳怨,然后通過stop()方法停止之前的播放瑰艘,再調(diào)用scheduleFile(_:at:completionHandler:)方法編排新的文件,最后通過play()方法開始播放肤舞。
//2:停止播放調(diào)用playerstop()方法即可紫新。

音頻播放功能已經(jīng)完成,運行項目李剖,試試點擊音樂右側(cè)的Play按鈕進(jìn)行音頻播放吧芒率。

音頻數(shù)據(jù)獲取

前面提到我們可以在mainMixerNode上安裝分接頭定量獲取AVAudioPCMBuffer數(shù)據(jù),現(xiàn)在打開AudioSpectrumPlayer文件篙顺,先定義一個屬性: fftSize偶芍,它是每次獲取到的buffer的frame數(shù)量。

將光標(biāo)定位至init()方法中的engine.connect()語句下方德玫,調(diào)用mainMixerNodeinstallTap方法開始安裝分接頭:

在分接頭的回調(diào) block 中將拿到的 2048 個 frame 的 buffer 交由fft函數(shù)進(jìn)行計算匪蟀,最后將計算的結(jié)果通過delegate進(jìn)行傳遞。

按照 44100hz 采樣率和 1 Frame = 1 Packet 來計算(可以參考這里關(guān)于channel宰僧、sample材彪、frame、packet的概念與關(guān)系),那么block將會在一秒中調(diào)用44100/2048≈21.5次左右段化,另外需要注意的是block有可能不在主線程調(diào)用嘁捷。

FFT實現(xiàn)

終于到實現(xiàn)FFT的時候了,根據(jù)vDSP文檔穗泵,首先需要定義一個FFT權(quán)重數(shù)組(fftSetup)普气,它可以在多次FFT中重復(fù)使用和提升FFT性能:

不需要時在析構(gòu)函數(shù)(反初始化函數(shù))中銷毀:

最后新建fft函數(shù),實現(xiàn)代碼如下:

通過代碼中的注釋佃延,應(yīng)該能了解如何從buffer獲取音頻樣本數(shù)據(jù)并進(jìn)行FFT計算了现诀,不過以下兩點是我在完成這一部分內(nèi)容過程中比較難理解的部分:

  1. 通過buffer對象的方法floatChannelData獲取樣本數(shù)據(jù),如果是多聲道并且是interleaved履肃,我們就需要對它進(jìn)行deinterleave, 通過下圖就能比較清楚的知道deinterleave前后的結(jié)構(gòu)仔沿,不過在我試驗了許多音頻文件之后,發(fā)現(xiàn)都是non-interleaved的尺棋,也就是無需進(jìn)行轉(zhuǎn)換封锉。┑( ̄Д  ̄)┍
image

圖4 interleaved的樣本數(shù)據(jù)需要進(jìn)行deinterleave

  1. vDSP在進(jìn)行實數(shù)FFT計算時利用一種獨特的數(shù)據(jù)格式化方式以達(dá)到節(jié)省內(nèi)存的目的,它在FFT計算的前后通過兩次轉(zhuǎn)換將FFT的輸入和輸出的數(shù)據(jù)結(jié)構(gòu)進(jìn)行統(tǒng)一成DSPSplitComplex膘螟。第一次轉(zhuǎn)換是通過vDSP_ctoz函數(shù)將樣本數(shù)據(jù)的實數(shù)數(shù)組轉(zhuǎn)換成DSPSplitComplex成福。第二次則是將FFT結(jié)果轉(zhuǎn)換成DSPSplitComplex,這個轉(zhuǎn)換的過程是在FFT計算函數(shù)vDSP_fft_zrip中自動完成的荆残。

    第二次轉(zhuǎn)換過程如下:n位樣本數(shù)據(jù)(n/2位復(fù)數(shù))進(jìn)行fft計算會得到n/2+1位復(fù)數(shù)結(jié)果:{[DC,0],C[2],...,C[n/2],[NY,0]} (其中DC是直流分量奴艾,NY是奈奎斯特頻率的值,C是復(fù)數(shù)數(shù)組),其中[DC,0]和[NY,0]的虛部都是0内斯,所以可以將NY放到DC中的虛部中蕴潦,其結(jié)果變成{[DC,NY],C[2],C[3],...,C[n/2]},與輸入位數(shù)一致俘闯。

image

圖5 第一次轉(zhuǎn)換時潭苞,vDSP_ctoz函數(shù)將實數(shù)數(shù)組轉(zhuǎn)換成DSPSplitComplex結(jié)構(gòu)

再次運行項目,現(xiàn)在除了聽到音樂之外還可以在控制臺中看到打印輸出的頻譜數(shù)據(jù)真朗。

image

圖6 將結(jié)果通過Google Sheets繪制出來的頻譜圖

好了此疹,本篇文章內(nèi)容到這里就結(jié)束了,下一篇文章將對計算好的頻譜數(shù)據(jù)進(jìn)行處理和動畫繪制遮婶。

資料參考
[1] wikipedia秀菱,脈沖編碼調(diào)制, zh.wikipedia.org/wiki/%E8%84…
[2] Mike Ash蹭睡,音頻數(shù)據(jù)獲取與解析, www.mikeash.com/pyblog/frid…
[3] 韓 昊, 傅里葉分析之掐死教程, blog.jobbole.com/70549/
[4] raywenderlich, AVAudioEngine編程入門衍菱,www.raywenderlich.com/5154-avaudi…
[5] Apple, vDSP編程指南, developer.apple.com/library/arc…
[6] Apple, aurioTouch案例代碼,developer.apple.com/library/arc…

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末肩豁,一起剝皮案震驚了整個濱河市脊串,隨后出現(xiàn)的幾起案子辫呻,更是在濱河造成了極大的恐慌,老刑警劉巖琼锋,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件放闺,死亡現(xiàn)場離奇詭異,居然都是意外死亡缕坎,警方通過查閱死者的電腦和手機(jī)怖侦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來谜叹,“玉大人匾寝,你說我怎么就攤上這事『衫埃” “怎么了艳悔?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長女仰。 經(jīng)常有香客問我猜年,道長,這世上最難降的妖魔是什么疾忍? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任乔外,我火速辦了婚禮,結(jié)果婚禮上一罩,老公的妹妹穿的比我還像新娘杨幼。我一直安慰自己,他們只是感情好擒抛,可當(dāng)我...
    茶點故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布推汽。 她就那樣靜靜地躺著补疑,像睡著了一般歧沪。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上莲组,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天诊胞,我揣著相機(jī)與錄音,去河邊找鬼锹杈。 笑死撵孤,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的竭望。 我是一名探鬼主播邪码,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼咬清!你這毒婦竟也來了闭专?” 一聲冷哼從身側(cè)響起奴潘,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎影钉,沒想到半個月后画髓,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡平委,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年奈虾,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片廉赔。...
    茶點故事閱讀 40,505評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡肉微,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出昂勉,到底是詐尸還是另有隱情浪册,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布岗照,位于F島的核電站村象,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏攒至。R本人自食惡果不足惜厚者,卻給世界環(huán)境...
    茶點故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望迫吐。 院中可真熱鬧库菲,春花似錦、人聲如沸志膀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽溉浙。三九已至烫止,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間戳稽,已是汗流浹背馆蠕。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留惊奇,地道東北人互躬。 一個月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像颂郎,于是被迫代替她去往敵國和親吼渡。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,515評論 2 359