一步一步教你實(shí)現(xiàn)iOS音頻頻譜動(dòng)畫(huà)(一)

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

一步一步教你實(shí)現(xiàn)iOS音頻頻譜動(dòng)畫(huà)(一)

image

如果你想先看看最終效果再?zèng)Q定看不看文章 -> bilibili
示例代碼下載

第二篇:一步一步教你實(shí)現(xiàn)iOS音頻頻譜動(dòng)畫(huà)(二)

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

前言

很久以前在電腦上聽(tīng)音樂(lè)的時(shí)候,經(jīng)常會(huì)調(diào)出播放器的一個(gè)小工具,里面的柱狀圖會(huì)隨著音樂(lè)節(jié)奏而跳動(dòng),就感覺(jué)自己好專(zhuān)業(yè),盡管后來(lái)才知道這個(gè)是音頻信號(hào)在頻域下的表現(xiàn)。

熱身知識(shí)

動(dòng)手寫(xiě)代碼之前,讓我們先了解幾個(gè)基礎(chǔ)概念吧

音頻數(shù)字化

  • 采樣: 總所周知,聲音是一種壓力波,是連續(xù)的,然而在計(jì)算機(jī)中無(wú)法表示連續(xù)的數(shù)據(jù),所以只能通過(guò)間隔采樣的方式進(jìn)行離散化,其中采集的頻率稱(chēng)為采樣率。根據(jù)奈奎斯特采樣定理 ,當(dāng)采樣率大于信號(hào)最高頻率的2倍時(shí)信號(hào)頻率不會(huì)失真。人類(lèi)能聽(tīng)到的聲音頻率范圍是20hz到20khz,所以CD等采用了44.1khz采樣率能滿(mǎn)足大部分需要。

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

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

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

傅里葉變換

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

image

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

實(shí)際上計(jì)算機(jī)中處理的都是離散傅里葉變換(DFT),而快速傅里葉變換(FFT)是快速計(jì)算離散傅里葉變換(DFT)或其逆變換的方法,它將DFT的復(fù)雜度從O(n2)降低到O(nlogn)。 如果你剛才點(diǎn)開(kāi)前面鏈接看過(guò)其中介紹的FFT算法,那么可能會(huì)覺(jué)得這FFT代碼該怎么寫(xiě)?不用擔(dān)心,蘋(píng)果為此提供了Accelerate框架,其中vDSP部分提供了數(shù)字信號(hào)處理的函數(shù)實(shí)現(xiàn),包含F(xiàn)FT。有了vDSP,我們只需幾個(gè)步驟即可實(shí)現(xiàn)FFT,簡(jiǎn)單便捷,而且性能高效。

iOS中的音頻框架

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

image

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

AVAudioEngine

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

image

圖3 AVAudioEngine和AVAudioNode連接圖

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

代碼實(shí)現(xiàn)

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

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

音頻播放

AudioSpectrumPlayer類(lèi)中創(chuàng)建AVAudioEngineAVAudioPlayerNode兩個(gè)實(shí)例變量:

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

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

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

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

音頻播放功能已經(jīng)完成,運(yùn)行項(xiàng)目,試試點(diǎn)擊音樂(lè)右側(cè)的Play按鈕進(jìn)行音頻播放吧。

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

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

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

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

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

FFT實(shí)現(xiàn)

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

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

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

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

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

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

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

    第二次轉(zhuǎn)換過(guò)程如下:n位樣本數(shù)據(jù)(n/2位復(fù)數(shù))進(jìn)行fft計(jì)算會(huì)得到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)換時(shí),vDSP_ctoz函數(shù)將實(shí)數(shù)數(shù)組轉(zhuǎn)換成DSPSplitComplex結(jié)構(gòu)

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

image

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

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

資料參考
[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編程入門(mén),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)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容