android pcm频谱_Android音频可视化
本文作者:熊鋆洋 (網(wǎng)易云音樂大前端團(tuán)隊(duì))
前言
音頻可視化,顧名思義就是將聲音以視覺的方式呈現(xiàn)出來。如何將音頻信號(hào)繪制出來?如何將聲音的變化在視覺上清晰的表現(xiàn)出來,讓視覺和聽覺上的感受一致?這些在 Android 上如何實(shí)現(xiàn)?本文將針對(duì)這些問題做出解答,盡量對(duì) Android 上的音頻可視化實(shí)現(xiàn)做一個(gè)全面的介紹。
傅里葉變換
Android 音頻播放的一般流程是: 1. 播放器從本地音頻文件或網(wǎng)絡(luò)加載編碼后的音頻數(shù)據(jù),解碼為 pcm 數(shù)據(jù)寫入 AudioTrack 2. AudioTrack 將 pcm 數(shù)據(jù)寫入 FIFO 3. AudioFlinger 中的 MixerThread 通過 AudioMixer 讀取 FIFO 中的數(shù)據(jù)進(jìn)行混音后寫入 HAL 輸出設(shè)備進(jìn)行播放
在這個(gè)流程中,直接體現(xiàn)音頻特征,可用于可視化繪制的是 pcm 數(shù)據(jù)。但 pcm 表示各采樣時(shí)間點(diǎn)上音頻信號(hào)強(qiáng)度,看起來雜亂無章,難以體現(xiàn)聽覺感知到的聲音變化。pcm 數(shù)據(jù)僅可用來繪制體現(xiàn)音頻信號(hào)平均強(qiáng)度變化的可視化動(dòng)效,其他大部分動(dòng)效需要使用對(duì) pcm 數(shù)據(jù)做傅里葉變換后得到的體現(xiàn)各頻率點(diǎn)上信號(hào)強(qiáng)度變化的頻域數(shù)據(jù)來繪制。
這里簡單回顧下傅里葉變換,它將信號(hào)從時(shí)域轉(zhuǎn)換為頻域,一般用于信號(hào)頻譜分析,確定其成分。轉(zhuǎn)換結(jié)果如下圖所示:
pcm 數(shù)據(jù)是時(shí)間離散的,需要使用離散傅里葉變換(DFT),它將包含 N 個(gè)復(fù)數(shù)的序列 $\{x_n\}:=x_0, x_1, ..., x_{N-1}$ 轉(zhuǎn)換為另一個(gè)復(fù)數(shù)序列 $\{X_k\}:=X_0, X_1, ..., X_{N-1}$,計(jì)算公式為:
X_k=\sum_{n=0}^{N-1}x_n \cdot e^{-i2 \pi {kn \over N}}=\sum_{n=0}^{N-1}x_n \cdot (cos(2\pi {kn \over N})-i \cdot sin(2\pi {kn \over N}))
直接用上面公式計(jì)算長度為 N 的序列的 DFT,時(shí)間復(fù)雜度為 $O(N^2)$,速度較慢,實(shí)際應(yīng)用中,一般會(huì)使用快速傅里葉變換(FFT),將時(shí)間復(fù)雜度降為 $O(Nlog(N))$。
計(jì)算公式看起來很復(fù)雜,但不懂也不會(huì)影響我們實(shí)現(xiàn)音頻可視化,FFT 的計(jì)算可以使用已有的庫,不需要自己來實(shí)現(xiàn)。但為了從 FFT 的計(jì)算結(jié)果得到最終用來繪制的數(shù)據(jù),有必要了解以下DFT特性: 輸入全部為實(shí)數(shù)時(shí),輸出結(jié)果滿足共軛對(duì)稱性:$X_{N-k}=X_k^*$,因此一般實(shí)現(xiàn)只返回一半結(jié)果 如原始信號(hào)采樣率為 $f_s$,序列長度為 N,輸出頻率分辨率為 $f_s/N$,第 k 個(gè)點(diǎn)的頻率為 $kf_s/N$,可用于查找指定頻率范圍在結(jié)果中對(duì)應(yīng)的位置 * 如一個(gè)頻率對(duì)應(yīng)輸出的實(shí)部和虛部為 re 和 im,其模為 $M=\sqrt{re^2+im^2}$,原始信號(hào)振幅為 $A=\begin{cases} M/N & DC \\ 2M/N & other \end{cases}$,可用于計(jì)算分貝和數(shù)據(jù)縮放
數(shù)據(jù)源
提供播放 pcm 數(shù)據(jù)的 FFT 計(jì)算結(jié)果的數(shù)據(jù)源有兩種,一種是 Android 系統(tǒng)提供的 Visualizer 類,這種存在兼容性問題,因此我們引入了另一種自己實(shí)現(xiàn)的數(shù)據(jù)源。同時(shí),我們實(shí)現(xiàn)了在不修改上層各動(dòng)效的數(shù)據(jù)處理和繪制邏輯的基礎(chǔ)上切換數(shù)據(jù)源,如下圖所示:
Android Visualizer
系統(tǒng) Visualizer 提供了方便的 api 來獲取播放音頻的波形或 FFT 數(shù)據(jù),一般使用方式是: 1. 用 audio session ID 創(chuàng)建 Visualizer對(duì)象,傳 0 可獲取混音后的可視化數(shù)據(jù),傳特定播放器或 AudioTrack 所使用的 audio session 的 ID,可獲取它們所播放音頻的可視化數(shù)據(jù) 2. 調(diào) setCaptureSize 方法設(shè)置每次獲取的數(shù)據(jù)大小,調(diào) setDataCaptureListener 方法設(shè)置數(shù)據(jù)回調(diào)并指定獲取數(shù)據(jù)頻率(即回調(diào)頻率)和數(shù)據(jù)類型(波形或 FFT) 3. 調(diào) setEnabled 方法開始獲取數(shù)據(jù),不再需要時(shí)調(diào) release 方法釋放資源
更詳細(xì)的 api 信息可查看官方文檔。
系統(tǒng) Visualizer 輸出的數(shù)據(jù)大小正比于音量,當(dāng)音量為 0 時(shí),輸出也為 0,可視化效果會(huì)隨音量變化。
使用系統(tǒng) Visualizer 存在兼容性問題,在有些機(jī)型上會(huì)導(dǎo)致系統(tǒng)音效失效,如要在所有機(jī)型上都能無副作用地展示動(dòng)效,需要實(shí)現(xiàn)自定義 Visualizer。
自定義 Visualizer
作為跟系統(tǒng) Visualizer 功能一致的數(shù)據(jù)源,自定義 Visualizer 需具備兩個(gè)功能: 獲取 pcm 數(shù)據(jù),計(jì)算 FFT 以指定頻率和大小發(fā)送 FFT 數(shù)據(jù)
實(shí)現(xiàn)第一個(gè)功能首先要獲取播放音頻的 pcm 數(shù)據(jù),這要求使用的播放器能夠提供 pcm 數(shù)據(jù),我們的播放器是自己實(shí)現(xiàn)的,能夠滿足這個(gè)要求。我們對(duì)播放器進(jìn)行了擴(kuò)展,增加了收集解碼后的 pcm 數(shù)據(jù)計(jì)算 FFT 的功能。
由于不同音頻采樣率不同,而計(jì)算 FFT 時(shí)采用固定的窗口大小,導(dǎo)致 FFT 計(jì)算結(jié)果回調(diào)頻率隨播放音頻改變,同時(shí)指定的數(shù)據(jù)大小可能跟計(jì)算結(jié)果的大小不同,因此要實(shí)現(xiàn)第二個(gè)功能,需要對(duì)計(jì)算結(jié)果做固定頻率和采樣等處理。
另外,我們的播放器在播放進(jìn)程中運(yùn)行,而實(shí)際使用 FFT 數(shù)據(jù)的動(dòng)效頁面運(yùn)行于主進(jìn)程中,所以還需要跨進(jìn)程傳輸數(shù)據(jù)。
綜上,自定義 Visualizer 的整體流程是:在播放進(jìn)程 native 層中計(jì)算 FFT,通過 JNI 調(diào)用,把計(jì)算結(jié)果回調(diào)給Java 層,然后通過 AIDL 把 FFT 數(shù)據(jù)傳遞給主進(jìn)程進(jìn)行后續(xù)的數(shù)據(jù)處理和發(fā)送操作。如下圖所示:
固定頻率需要將可變的 FFT 計(jì)算結(jié)果回調(diào)頻率轉(zhuǎn)換為外部設(shè)置的 Visualizer 回調(diào)頻率,如下圖所示:
根據(jù)所需數(shù)據(jù)發(fā)送時(shí)間間隔和 FFT 回調(diào)時(shí)間間隔差值的不同,我們采用兩種不同的方式。
當(dāng)時(shí)間間隔差值小于等于回調(diào)時(shí)間間隔時(shí),每 $t/ \Delta t$ 次回調(diào)丟棄一次數(shù)據(jù),其中 t 為 FFT 回調(diào)時(shí)間間隔,$\Delta t$ 為時(shí)間間隔差值,如下圖所示:
當(dāng)時(shí)間間隔差值大于回調(diào)時(shí)間間隔時(shí),每 $t1/t$ 次回調(diào)發(fā)送一次數(shù)據(jù),其中 t1 為所需數(shù)據(jù)發(fā)送時(shí)間間隔,t 為 FFT 回調(diào)時(shí)間間隔,如下圖所示:
采樣就是當(dāng)外部設(shè)置的數(shù)據(jù)大小小于 FFT 計(jì)算結(jié)果的數(shù)據(jù)大小時(shí),對(duì)原始 FFT 數(shù)據(jù)以合適的間隔抽取數(shù)據(jù),以滿足設(shè)置的要求。
為了讓自定義 Visualizer 返回?cái)?shù)據(jù)的取值范圍跟系統(tǒng) Visualizer 一致,從而實(shí)現(xiàn)數(shù)據(jù)源無縫切換,我們需要對(duì) FFT 數(shù)據(jù)進(jìn)行縮放。這里就需要用到前面提到的模與振幅的計(jì)算了,解碼所得 pcm 數(shù)據(jù)的取值范圍為 [-1, 1],所以原始信號(hào)振幅取值范圍為 [0, 1],即 $2M/N$ 的取值范圍為 [0, 1](繪制時(shí)不會(huì)用到直流分量,這里不考慮);而系統(tǒng) Visualizer 返回的 FFT 數(shù)據(jù)是一個(gè) byte 數(shù)組,實(shí)部和虛部的取值范圍為 [-128, 128],模的取值范圍為 $[0, 128 \times \sqrt2]$,那么 $2M/N \times 128 \times \sqrt2$ 的取值范圍跟系統(tǒng) Visualizer 輸出 FFT 的模的取值范圍一致。由于繪制不會(huì)用到相位信息,我們可以將用上述方式縮放后的值作為輸出 FFT 數(shù)據(jù)的實(shí)部,并把虛部設(shè)為 0。
由于數(shù)據(jù)發(fā)送的頻率較高,為了避免頻繁創(chuàng)建對(duì)象導(dǎo)致內(nèi)存抖動(dòng),我們采用對(duì)象池來保存數(shù)據(jù)數(shù)組對(duì)象,每次從對(duì)象池中獲取所需大小的數(shù)組對(duì)象,填充采樣數(shù)據(jù)后加入到隊(duì)列中等待發(fā)送,數(shù)據(jù)消費(fèi)完后將數(shù)組對(duì)象返回到對(duì)象池中。
數(shù)據(jù)處理
不同動(dòng)效的具體數(shù)據(jù)處理方式不同,忽略細(xì)節(jié)上的差異,云音樂現(xiàn)有的動(dòng)效中,除了宇宙塵埃和孤獨(dú)星球,其他的處理流程基本一致,如下圖所示:
首先根據(jù)動(dòng)效選擇的頻率范圍計(jì)算所需的頻率數(shù)據(jù)在 FFT 數(shù)組中的索引位置:
f_r=f_s/N, start=\lceil MIN/f_r \rceil, end=\lfloor MAX/f_r \rfloor
其中 $f_s$ 為采樣率,N 為 FFT 窗口大小,$f_r$ 為頻率分辨率,MIN 為頻率范圍起始值,MAX 為頻率范圍結(jié)束值。
然后根據(jù)動(dòng)效所需數(shù)據(jù)點(diǎn)數(shù),對(duì)頻率范圍內(nèi)的 FFT 數(shù)據(jù)進(jìn)行采樣或用一個(gè) FFT 數(shù)據(jù)表示多個(gè)數(shù)據(jù)點(diǎn)。
然后計(jì)算分貝:
db=20\log_{10}M
其中 M 為 FFT 數(shù)據(jù)的模。
然后將分貝轉(zhuǎn)化為高度:
h=db/MAX\_DB \cdot maxHeight
其中 MAX_DB 是預(yù)設(shè)的分貝最大值,maxHeight 是當(dāng)前動(dòng)效要求的最大高度。
最后對(duì)計(jì)算出的高度做數(shù)據(jù)上的平滑處理。
平滑
對(duì)最終用來繪制的數(shù)據(jù)做平滑處理,可以得到更柔和的曲線,達(dá)到更好的視覺效果,如下圖所示:
數(shù)據(jù)平滑算法有很多,我們綜合考慮效果和計(jì)算復(fù)雜度選擇了 Savitzky–Golay 濾波法,其計(jì)算方式如下,對(duì)應(yīng)的窗口大小分別為5、7 和 9,可以按需選擇不同的窗口大小。
Y_i={1 \over 35}(-3y_{i-2}+12y_{i-1}+17y_i+12y_{i+1}-3y_{i+2})
Y_i={1 \over 21}(-2y_{i-3}+3y_{i-2}+6y_{i-1}+7y_i+6y_{i+1}+3y_{i+2}-2y_{i+3})
Y_i={1 \over 231}(-21y_{i-4}+14y_{i-3}+39y_{i-2}+54y_{i-1}+59y_i+54y_{i+1}+39y_{i+2}+14y_{i+3}-21y_{i+4})
經(jīng)過平滑處理后數(shù)據(jù)的變化如下圖所示:
BufferQueue
有些動(dòng)效的數(shù)據(jù)處理計(jì)算比較復(fù)雜,為提升并行性,減少主線程耗時(shí),我們借鑒系統(tǒng)圖形框架中 BufferQueue 的思想,實(shí)現(xiàn)了一個(gè)簡單的承載動(dòng)效繪制數(shù)據(jù),連接數(shù)據(jù)處理和繪制的 BufferQueue,其工作過程如下圖所示:
在使用 BufferQueue 的動(dòng)效繪制類初始化時(shí),根據(jù)需要?jiǎng)?chuàng)建一個(gè)合適大小的 BufferQueue,并啟動(dòng)用于執(zhí)行數(shù)據(jù)處理的 Looper 線程。
數(shù)據(jù)處理部分對(duì)應(yīng) BufferQueue 的 Producer,當(dāng) FFT 數(shù)據(jù)到來時(shí),通過綁定 Looper 線程的 Handler 將數(shù)據(jù)發(fā)送到 Looper 線程中執(zhí)行數(shù)據(jù)處理。數(shù)據(jù)處理時(shí),首先調(diào)用 Producer 的 dequeue 方法從 BufferQueue 中獲取空閑的 Buffer,然后對(duì) FFT 數(shù)據(jù)進(jìn)行處理,生成需要的數(shù)據(jù)向 Buffer 中填充,最后調(diào)用 Producer 的 queue 方法將 Buffer 加入到 BufferQueue 中的 queued 隊(duì)列中。
繪制部分對(duì)應(yīng) BufferQueue 的 Consumer,調(diào)用 Producer 的 queue 方法時(shí)會(huì)觸發(fā) ConsumerListener 的 onBufferAvailable 回調(diào),在回調(diào)中通過綁定主線程的 Handler 切換到主線程消費(fèi) Buffer。首先調(diào)用 Consumer 的 acquire 方法從 BufferQueue 的 queued 隊(duì)列中獲取 Buffer,然后從 Buffer 中取出所需數(shù)據(jù)來繪制,最后調(diào)用 Consumer 的 release 方法將上次的 Buffer 返回給 BufferQueue。
繪制
繪制部分的主要工作是調(diào)用系統(tǒng) Canvas API 將處理后的數(shù)據(jù)繪制成所需的效果,具體如何使用 API 繪制,隨動(dòng)效的不同而不同,這里不展開介紹。本節(jié)將從對(duì)繪制來說比較重要的體驗(yàn)和性能方面介紹一些動(dòng)效繪制的優(yōu)化經(jīng)驗(yàn)。
由于 FFT 數(shù)據(jù)回調(diào)的時(shí)間間隔大于 16ms,如果只在數(shù)據(jù)到來時(shí)繪制,會(huì)產(chǎn)生視覺上的卡頓,為了得到更好的視覺效果,需要在兩次回調(diào)之間加入過渡幀,以達(dá)到漸變的動(dòng)畫效果。實(shí)現(xiàn)方式是在兩次數(shù)據(jù)到達(dá)的時(shí)間間隔內(nèi),以上次數(shù)據(jù)為起點(diǎn),本次數(shù)據(jù)為終點(diǎn),根據(jù)當(dāng)前時(shí)間相對(duì)于數(shù)據(jù)到達(dá)時(shí)間的消逝時(shí)間計(jì)算當(dāng)前的高度,不斷重復(fù)繪制,如下圖所示:
性能優(yōu)化有兩大手段:batch 和 cache,在動(dòng)效繪制時(shí)也可以使用這些手段。對(duì)于需要繪制多條線或多個(gè)點(diǎn)的動(dòng)效,應(yīng)該調(diào)用 drawLines 或 drawPoints 方法進(jìn)行批處理,而不是循環(huán)調(diào)用 drawLine 或 drawPoint 方法,以減少執(zhí)行時(shí)間。
結(jié)語
本文介紹了 Android 音頻可視化涉及的背景知識(shí)和實(shí)現(xiàn)過程,并提供了一些問題解決方案和優(yōu)化思路。本文專注于通用方案,不涉及特定動(dòng)效的具體實(shí)現(xiàn),希望讀者能從中受到些許啟發(fā),實(shí)現(xiàn)自己的酷炫動(dòng)效。
參考資料本文發(fā)布自 網(wǎng)易云音樂大前端團(tuán)隊(duì),文章未經(jīng)授權(quán)禁止任何形式的轉(zhuǎn)載。我們常年招收前端、iOS、Android,如果你準(zhǔn)備換工作,又恰好喜歡云音樂,那就加入我們 grp.music-fe@corp.netease.com!
總結(jié)
以上是生活随笔為你收集整理的android pcm频谱_Android音频可视化的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: auuc 评估指标_广告中增益模型理解
- 下一篇: android的动态注册,Android