FFmpeg?音頻可視化解碼流程詳解
一、解碼流程
解碼流程大致分為以下三個(gè)部分,以FFmpge源碼下的ffmpeg\doc\examples\decode_audio.c為參考。
1.1、解析音頻信息
avformat_open_input負(fù)責(zé)打開需要解碼的音頻文件,如果文件打開成功的話會(huì)初始化AVFormatContext,avformat_find_stream_info開啟音頻流遍歷,av_find_best_stream找到最合適解析數(shù)據(jù)的幀,解析完后我們可以通過返回的AVStream獲取到我們需要用的解碼器id、通道數(shù)、采樣率、位深、音頻時(shí)長(zhǎng)、數(shù)據(jù)排列結(jié)構(gòu)。拿到解碼器id我們通過解碼器id獲取解碼器avcodec_find_decoder,有些解碼器并不是FFmpeg內(nèi)置的,所以有些需要在編譯時(shí)就加進(jìn)去,我之前的文章也有講過AAC和MP3編解碼第三方庫。如果找到了解碼器,下一步就是avcodec_alloc_context3對(duì)解碼器上下文AVCodecContext進(jìn)行初始化,初始化完成后avcodec_parameters_to_context將解碼器參數(shù)設(shè)置給解碼器上下文,例如通道數(shù),采樣率,采樣位深等等信息。如果未設(shè)置可能會(huì)出現(xiàn)invalid argument的錯(cuò)誤,導(dǎo)致后續(xù)無法繼續(xù)。最后通過avcodec_open2打開解碼器,如果打開成功我們就可以開始對(duì)音頻數(shù)據(jù)進(jìn)行讀取。
1.2、從原始數(shù)據(jù)packet到frame
我們解碼的目的就是為了拿到底層播放器能播的PCM數(shù)據(jù)。既然我們已經(jīng)獲取到了解碼器,那么下面就是一幀一幀的讀取解碼器解析出來的數(shù)據(jù)。首先我們需要av_packet_alloc初始化包對(duì)象AVPacket,包對(duì)象是未解碼的數(shù)據(jù),原始的音頻數(shù)據(jù)被打包成一個(gè)一個(gè)的包,然后送給解碼器去把包打開,變成幀對(duì)象,所以我們又需要通過av_frame_alloc初始化幀對(duì)象AVFrame,把它送給解碼器,解碼器用數(shù)據(jù)把它裝滿后返回回來。av_read_frame就是從打開的文件讀取一個(gè)數(shù)據(jù)包,對(duì)于AAC/MP3來說他們是未解碼的壓縮數(shù)據(jù)。然后通過avcodec_send_packet把數(shù)據(jù)包送給解碼器,返回0表示解碼器解包成功,接下來就可以從解碼器讀數(shù)據(jù),這時(shí)的數(shù)據(jù)就是以幀的形式存在,avcodec_receive_frame讀取幀,因?yàn)橐粋€(gè)包可能有幾個(gè)幀,所以需要循環(huán)讀取,當(dāng)avcodec_receive_frame返回0時(shí)表示讀取成功,可以進(jìn)行下一步操作,當(dāng)返回值是AVERROR_EOF表示讀取完畢可以跳出循環(huán)了,返回AVERROR(EAGAIN)表示解碼器輸出已經(jīng)是不可用的狀態(tài),必須向解碼器送新包來激活輸出,同樣也可以跳出讀取和解析幀的循環(huán)。
1.3、從frame到PCM byte
我們的PCM數(shù)據(jù)就在frame的data里,但是我們并不能直接拿,首先我們得知道拿多少,怎么拿。拿多少取決于采樣位數(shù),通道數(shù)和幀里面的樣本數(shù)。例如44100HZ的話一秒就有44100通道數(shù)個(gè)樣本。那一個(gè)幀里面就一共有 采樣位數(shù)/8通道數(shù)*樣本數(shù)個(gè)字節(jié)數(shù)據(jù)。怎么拿取決于音頻數(shù)據(jù)的存儲(chǔ)方式,音頻存儲(chǔ)方式有兩種:
- planar:音頻左右聲道數(shù)據(jù)分開放置,數(shù)據(jù)存儲(chǔ)格式為
data[0]:LLLLLLLLLLLLLLLL
data[1]:RRRRRRRRRRRRR
- packet:音頻左右聲道數(shù)據(jù)交替放置,數(shù)據(jù)存儲(chǔ)格式為
data[0]:LRLRLRLRLRLRLRLR
最終拿到的數(shù)據(jù)都是以LRLRLRLRLR的方式排列,到這里我們可以把它送給播放器或者在送給播放器前加一些我們自己的音頻算法,全部解碼完成后,最后記得釋放掉相關(guān)資源。在這里我們簡(jiǎn)單點(diǎn),計(jì)算它的分貝,實(shí)現(xiàn)音頻可視化的功能。
二、分貝計(jì)算
我們音頻的分貝往往不需要計(jì)算每一個(gè)樣本的分貝數(shù),第一計(jì)算密度太大超出人眼感知對(duì)顯示沒有益處,二是計(jì)算量太大會(huì)導(dǎo)致我們的計(jì)算時(shí)間大大延長(zhǎng)。因?yàn)槁曇艟哂幸欢ǖ难永m(xù)性,所以我們可以計(jì)算一個(gè)時(shí)間段內(nèi)的平均值來獲得一段音頻范圍的分布值,這樣既減小了工作量又達(dá)到了合理可視化的效果。首先是獲取平均值,假設(shè)我們每秒想獲取10個(gè)分貝值,那么我們需要計(jì)算采樣率通道數(shù)采樣位數(shù)/8/10個(gè)字節(jié)數(shù)據(jù)的平均值,我們不妨自己把它叫dB采樣區(qū)間樣本數(shù),一個(gè)16bit位深的音頻每?jī)蓚€(gè)字節(jié)組成一個(gè)樣本,將區(qū)間內(nèi)樣本相加再除以樣本數(shù)取平均值即可。接下來就是dB的計(jì)算,dB其實(shí)并不特指分貝,它只是在音頻描述領(lǐng)域。它描述的是音頻的增益關(guān)系,如果想詳細(xì)了解db是什么可以自行百度相關(guān)的知識(shí)。分貝的計(jì)算公式是
20*log10(value)
所以聲音的分貝描述的并不是線性關(guān)系而是指數(shù)關(guān)系,例如70db比50db的聲音大了20倍,例如16bit可以描述的音頻范圍為0-65535那么它的最大dB值在96.3左右,32bit可以描述音頻范圍在0-4294967296,那么它的最大dB值在192.6。把我們剛才計(jì)算的平均值帶入value就能獲得我們的區(qū)間的分貝,把它存起來解析完一起返回或者逐個(gè)回調(diào)都可以,看你的業(yè)務(wù)需求。下面是計(jì)算16bit采樣位數(shù)的分貝的方法,32bit的處理方法類似,主要注意值的大小,和每次位移的byte步長(zhǎng)。拿到了了分貝我們就可以將它們變成條變成塊的繪制到屏幕了。
void getPcmDB16(const unsigned char *pcmdata, size_t size) { int db = 0; short int value = 0; double sum = 0; for(int i = 0; i < size; i += bit_format/8) { memcpy(&value, pcmdata+i, bit_format/8); //獲取2個(gè)字節(jié)的大?。ㄖ担? sum += abs(value); //絕對(duì)值求和 } sum = sum / (size / (bit_format/8)); //求平均值(2個(gè)字節(jié)表示一個(gè)振幅,所以振幅個(gè)數(shù)為:size/2個(gè)) if(sum > 0) { db = (int)(20.0*log10(sum)); } memcpy(wave_buffer+wave_index,(char*)&db,1); wave_index++; }
需要注意的是我們?cè)诮獯a時(shí)ffmpeg的音頻格式類型除了packet和planar兩個(gè)大類外,對(duì)于32位的音頻又區(qū)分了32位整形和32位浮點(diǎn)型。
enum AVSampleFormat { AV_SAMPLE_FMT_NONE = -1, AV_SAMPLE_FMT_U8, ///< unsigned 8 bits AV_SAMPLE_FMT_S16, ///< signed 16 bits AV_SAMPLE_FMT_S32, ///< signed 32 bits AV_SAMPLE_FMT_FLT, ///< float AV_SAMPLE_FMT_DBL, ///< double AV_SAMPLE_FMT_U8P, ///< unsigned 8 bits, planar AV_SAMPLE_FMT_S16P, ///< signed 16 bits, planar AV_SAMPLE_FMT_S32P, ///< signed 32 bits, planar AV_SAMPLE_FMT_FLTP, ///< float, planar AV_SAMPLE_FMT_DBLP, ///< double, planar AV_SAMPLE_FMT_S64, ///< signed 64 bits AV_SAMPLE_FMT_S64P, ///< signed 64 bits, planar AV_SAMPLE_FMT_NB ///< Number of sample formats. DO NOT USE if linking dynamically };
浮點(diǎn)型的取值范圍在-1到1的區(qū)間,所以我們?cè)谟?jì)算時(shí)需要乘以0x7fff來獲得和16位同比例的數(shù)據(jù),達(dá)到同樣的顯示效果。
void getPcmDBFloat(const unsigned char *pcmdata, size_t size) { int db = 0; float value = 0; double sum = 0; for(int i = 0; i < size; i += bit_format/8) { memcpy(&value, pcmdata+i, bit_format/8); //獲取4個(gè)字節(jié)的大?。ㄖ担? sum += abs(value*0x7fff); //絕對(duì)值求和 } sum = sum / (size / (bit_format/8)); if(sum > 0) { db = (int)(20.0*log10(sum)); } memcpy(wave_buffer+wave_index,(char*)&db,1); wave_index++; }
三、實(shí)現(xiàn)效果
以上就是FFmpeg 音頻可視化解碼流程詳解的詳細(xì)內(nèi)容,更多關(guān)于FFmpeg 音頻可視化解碼的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android 自定義驗(yàn)證碼輸入框的實(shí)例代碼(支持粘貼連續(xù)性)
這篇文章主要介紹了Android 自定義驗(yàn)證碼輸入框的實(shí)例代碼(支持粘貼連續(xù)性),代碼簡(jiǎn)單易懂,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-10-10Android編程實(shí)現(xiàn)在Bitmap上涂鴉效果
這篇文章主要介紹了Android編程實(shí)現(xiàn)在Bitmap上涂鴉效果的方法,涉及Android界面布局,事件響應(yīng)及Bitmap操作的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-12-12android 選項(xiàng)卡(TabHost)如何放置在屏幕的底部
如何將TAB放置在屏幕的底端,有很多的新手都想實(shí)現(xiàn)這種效果,本文搜集整理了一些,感興趣的朋友可以參考下哦2013-01-01Android TextSwitcher實(shí)現(xiàn)文字上下翻牌效果(銅板街)
這篇文章主要介紹了Android TextSwitcher實(shí)現(xiàn)文字上下翻牌效果(銅板街),需要的朋友可以參考下2017-05-05Android進(jìn)階Hook攔截系統(tǒng)實(shí)例化View過程實(shí)現(xiàn)App換膚功能
這篇文章主要為大家介紹了Android進(jìn)階Hook攔截系統(tǒng)實(shí)例化View過程實(shí)現(xiàn)App換膚功能詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01淺談Android textview文字對(duì)齊換行的問題
下面小編就為大家分享一篇淺談Android textview文字對(duì)齊換行的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-01-01