一、解碼流程
解碼流程大緻分為以下三個部分,以FFmpge源碼下的ffmpeg\doc\examples\decode_audio.c為參考。
1.1、解析音頻資訊
avformat_open_input負責打開需要解碼的音頻檔案,如果檔案打開成功的話會初始化AVFormatContext,avformat_find_stream_info開啟音頻流周遊,av_find_best_stream找到最合适解析資料的幀,解析完後我們可以通過傳回的AVStream擷取到我們需要用的解碼器id、通道數、采樣率、位深、音頻時長、資料排列結構。拿到解碼器id我們通過解碼器id擷取解碼器avcodec_find_decoder,有些解碼器并不是FFmpeg内置的,是以有些需要在編譯時就加進去,我之前的文章也有講過AAC和MP3編解碼第三方庫。如果找到了解碼器,下一步就是avcodec_alloc_context3對解碼器上下文AVCodecContext進行初始化,初始化完成後avcodec_parameters_to_context将解碼器參數設定給解碼器上下文,例如通道數,采樣率,采樣位深等等資訊。如果未設定可能會出現invalid argument的錯誤,導緻後續無法繼續。最後通過avcodec_open2打開解碼器,如果打開成功我們就可以開始對音頻資料進行讀取。
【更多音視訊學習資料,點選下方連結免費領取↓↓,先碼住不迷路~】
音視訊開發基礎知識和資料包
1.2、從原始資料packet到frame
我們解碼的目的就是為了拿到底層播放器能播的PCM資料。既然我們已經擷取到了解碼器,那麼下面就是一幀一幀的讀取解碼器解析出來的資料。首先我們需要av_packet_alloc初始化包對象AVPacket,包對象是未解碼的資料,原始的音頻資料被打包成一個一個的包,然後送給解碼器去把包打開,變成幀對象,是以我們又需要通過av_frame_alloc初始化幀對象AVFrame,把它送給解碼器,解碼器用資料把它裝滿後傳回回來。av_read_frame就是從打開的檔案讀取一個資料包,對于AAC/MP3來說他們是未解碼的壓縮資料。然後通過avcodec_send_packet把資料包送給解碼器,傳回0表示解碼器解包成功,接下來就可以從解碼器讀資料,這時的資料就是以幀的形式存在,avcodec_receive_frame讀取幀,因為一個包可能有幾個幀,是以需要循環讀取,當avcodec_receive_frame傳回0時表示讀取成功,可以進行下一步操作,當傳回值是AVERROR_EOF表示讀取完畢可以跳出循環了,傳回AVERROR(EAGAIN)表示解碼器輸出已經是不可用的狀态,必須向解碼器送新包來激活輸出,同樣也可以跳出讀取和解析幀的循環。
1.3、從frame到PCM byte
我們的PCM資料就在frame的data裡,但是我們并不能直接拿,首先我們得知道拿多少,怎麼拿。拿多少取決于采樣位數,通道數和幀裡面的樣本數。例如44100HZ的話一秒就有44100通道數個樣本。那一個幀裡面就一共有 采樣位數/8通道數*樣本數個位元組資料。怎麼拿取決于音頻資料的存儲方式,音頻存儲方式有兩種:
- planar:音頻左右聲道資料分開放置,資料存儲格式為
data[0]:LLLLLLLLLLLLLLLL
data[1]:RRRRRRRRRRRRR
- packet:音頻左右聲道資料交替放置,資料存儲格式為
data[0]:LRLRLRLRLRLRLRLR
最終拿到的資料都是以LRLRLRLRLR的方式排列,到這裡我們可以把它送給播放器或者在送給播放器前加一些我們自己的音頻算法,全部解碼完成後,最後記得釋放掉相關資源。在這裡我們簡單點,計算它的分貝,實作音頻可視化的功能。
二、分貝計算
我們音頻的分貝往往不需要計算每一個樣本的分貝數,第一計算密度太大超出人眼感覺對顯示沒有益處,二是計算量太大會導緻我們的計算時間大大延長。因為聲音具有一定的延續性,是以我們可以計算一個時間段内的平均值來獲得一段音頻範圍的分布值,這樣既減小了工作量又達到了合理可視化的效果。首先是擷取平均值,假設我們每秒想擷取10個分貝值,那麼我們需要計算采樣率通道數采樣位數/8/10個位元組資料的平均值,我們不妨自己把它叫dB采樣區間樣本數,一個16bit位深的音頻每兩個位元組組成一個樣本,将區間内樣本相加再除以樣本數取平均值即可。接下來就是dB的計算,dB其實并不特指分貝,它隻是在音頻描述領域。它描述的是音頻的增益關系,如果想詳細了解db是什麼可以自行百度相關的知識。分貝的計算公式是
20*log10(value)
是以聲音的分貝描述的并不是線性關系而是指數關系,例如70db比50db的聲音大了20倍,例如16bit可以描述的音頻範圍為0-65535那麼它的最大dB值在96.3左右,32bit可以描述音頻範圍在0-4294967296,那麼它的最大dB值在192.6。把我們剛才計算的平均值帶入value就能獲得我們的區間的分貝,把它存起來解析完一起傳回或者逐個回調都可以,看你的業務需求。下面是計算16bit采樣位數的分貝的方法,32bit的處理方法類似,主要注意值的大小,和每次位移的byte步長。拿到了了分貝我們就可以将它們變成條變成塊的繪制到螢幕了。
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個位元組的大小(值)
sum += abs(value); //絕對值求和
}
sum = sum / (size / (bit_format/8)); //求平均值(2個位元組表示一個振幅,是以振幅個數為:size/2個)
if(sum > 0)
{
db = (int)(20.0*log10(sum));
}
memcpy(wave_buffer+wave_index,(char*)&db,1);
wave_index++;
}
需要注意的是我們在解碼時ffmpeg的音頻格式類型除了packet和planar兩個大類外,對于32位的音頻又區分了32位整形和32位浮點型。
【更多音視訊學習資料,點選下方連結免費領取↓↓,先碼住不迷路~】
C++程式員必看,抓住音視訊開發的大浪潮!沖擊年薪60萬
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
};
三、實作效果
如果你對音視訊開發感興趣,覺得文章對您有幫助,别忘了點贊、收藏哦!或者對本文的一些闡述有自己的看法,有任何問題,歡迎在下方評論區讨論!