解密多媒體封裝解封裝框架

        上一篇文章我們搭好了環境並編譯出所需的ffmpeg庫,本篇我們討論如何利用ffmpeg提供的API函數進行多媒體文件的解封裝(demux)過程。在講解之前,我們需要了解一些基本的多媒體文件知識,大蝦請飄過。

  • 容器格式:不管是音頻文件還是視頻格式的文件,都是一個多媒體的容器,即container,比如常見的視頻容器格式有avimp4mkvflvrm/rmvbmovtsvobdat,音頻容器格式有MP3WAVAACAPEFLAC等等,它容納了視頻、音頻、字幕(subtitle)等一個或多個基本流數據,有的甚至一個容器中存放有多個視頻、音頻以及字幕。

  • 壓縮格式:對視頻、音頻數據的基本流進行的壓縮方式就是音視頻的壓縮格式。常見的視頻壓縮格式如mpeg2mpeg4H264VC1Rm/Rmvb,常見音頻壓縮格式如MPAAACAC3DTS。注意這裏的部分名字和上面的一樣,但意義不同,上面是封裝格式,這裏是壓縮格式。爲什麼要壓縮呢?因爲不壓縮的話,要存儲圖像或聲音就需要非常多的空間,比如mpeg2壓縮比能達到25:1左右,而H264甚至能達到102:1的驚人程度!

  • ES:也就是ElementaryStream,也稱爲基本流、組件流等稱呼,就是單獨的一路視頻、一條音頻、一個subtitle字幕或者單個的附加數據。顯然常見的多媒體文件一個都有一個視頻ES、音頻ES,有的也含有多個視頻ES和音頻ES以及subtitleES。比如藍光原版的TS一般都含有多個音軌ES和字幕ES,但不是所有有字幕都有字幕ES,可能字幕已經內嵌進視頻,這樣的字幕其實成了視頻的一部分。

  • Demux:在播放時,需要把這些視音頻以及字幕等基本流分離出來,這個過程就叫Demux,或者解封裝,也稱爲解複用。分離出來的各個基本流(ES)分別送給視頻解碼器、音頻解碼器等解碼後才能得到圖像聲音。Demux過程如下圖(subtitle也可能需要解碼):


  • Remux:當然Demux反過來把基本的音頻、視頻、字幕等組合成一個完整的多媒體就是Remux或者封裝,也稱爲複用。比如很多電影網站的音視頻壓制的人就需要先做Demux,分離成ES,在加入必要的中文字幕和音軌後、重新封裝。所有的轉碼工具也都必須有Remux和重新Demux的過程。複用與解複用的概念對於熟悉DVB行業的讀者來說應該比較清楚。

  • PTS:也就是顯示時間戳,指圖像或者聲音在解碼後應該顯示或者發聲的時間點。音視頻不是一解碼出來就播出來,否則就亂了,性能好的解碼器播放的快,差的播放的慢,並且視頻和音頻也對不上號。所有這些都是靠PTS來同步的。至於DTS解碼時間戳在現在相對以前較大解碼內存緩衝下,顯得不那麼重要了。

有了這些基本的多媒體知識,我們就可以繼續講解如何利用ffmpeg來進行Demux這個過程。首先介紹一下主要的幾個API函數:

intavformat_open_input(AVFormatContext **ps, const char *filename,

AVInputFormat *fmt, AVDictionary **options)

這個函數用於打開多媒體文件,並讀取相關文件頭信息。

voidavformat_close_input(AVFormatContext **ps)

這個函數用於關閉上面打開的多媒體文件,釋放相關資源。

intavformat_find_stream_info(AVFormatContext *ic, AVDictionary**options);

這個函數通過註冊的文件格式解析器讀取文件的取各種信息,比如播放持續時間、音視頻壓縮格式、音軌信息、字幕信息、幀率、採樣率等等。

int av_read_frame(AVFormatContext*s, AVPacket *pkt);

這個函數對於Demux過程是最重要的一個函數,它從文件中讀取一幀視頻、一幀或多幀音頻、字幕等ES數據包,除了數據本身之外,還包括PTS、持續時間、參考幀等重要信息。

void av_free_packet(AVPacket *pkt)

這個函數用於釋放ES數據包,與上面的函數成對使用。

有了這些函數和上面的基本知識,下面我們來實現一個簡單的Demux框架實例。這個實例的功能是把多媒體文件中的音視頻ES數據抽出來分別寫入不同文件。我們爲了簡單,這裏不處理返回錯誤,在實際項目中自己添加錯誤處理機制。本文力求用最簡單最原始的方式把ffmpeg解封裝的基本框架講解清楚。

#include <stdio.h>
#include "libavformat/avformat.h"

static const char *media_file = "test_media.mp4";
int main(void)
{
int i, vid_idx, aud_idx;
FILE *fp_vides = NULL, *fp_audes = NULL;
AVFormatContext *pFormatCtx = NULL;
AVPacket pkt;

av_register_all();
avformat_open_input(&pFormatCtx, media_file, NULL, NULL);
avformat_find_stream_info(pFormatCtx, NULL);

fp_vides = fopen("vid_es.dat", "wb");
fp_audes = fopen("aud_es.dat", "wb");
// 1, handle stream info
for (i=0; i<pFormatCtx->nb_streams; i++)
{
if (pFormatCtx->streams[i]->codec->codec_type ==AVMEDIA_TYPE_VIDEO)
vid_idx = i;
else if (pFormatCtx->streams[i]->codec->codec_type ==AVMEDIA_TYPE_AUDIO)
aud_idx = i;
else
;//such as subtitile
}
while (av_read_frame(pFormatCtx, &pkt) >= 0)
{
// 2, handle pkt data
if (pkt.stream_index == vid_idx)
fwrite(pkt.data, pkt.size, 1, fp_vides);
else if (pkt.stream_index == aud_idx)
fwrite(pkt.data, pkt.size, 1, fp_audes);
else
;// such as subtitile
av_free_packet(&pkt);
}
fclose(fp_vides);
fclose(fp_audes);
avformat_close_input(&pFormatCtx);
return 0;
}

在註釋1的地方,需要處理基本流索引與音視頻對應的關係和重要信息記錄,這個關係會在註釋2的地方用到,並且也是後續的多音軌、字幕切換的憑據,本例只處理了最簡單的只有一路音視頻的情況,且沒有對其他信息進行記錄,比如幀率、視頻寬高、編碼類型、時間標度、第一個PTS等等。原則上這些跟Demux的框架沒有關係,且每個人有有自己的處理方式,就不在這裏貼出來。

第一時間獲得博客更新,獲得更詳細信息和Demo代碼,請關注微信號:程序員互動聯盟,掃一掃下方二維碼或者搜索微信號coder_online即可關注,我們可以在線交流。


如需轉載請註明出處:謝謝合作!


發佈了60 篇原創文章 · 獲贊 13 · 訪問量 26萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章