ffmpeg源碼學習
最近在看
ffmpeg
源碼,希望多瞭解下底層的東西,
mplayer
源碼太過龐大,不利於快速的分析關鍵代碼。
ffmpeg
恰好滿足需要。
主要的分析內容包括如下兩個方面:
demuxer
相關,解碼器選取相關
1
、
demuxer
相關
1.1 ffmpeg
中選擇
demuxer
的過程
av_open_input_file
(
**
)
->ff_probe_input_buffer
(
**
)
-->av_probe_input_format2(***)-->read_probe
(),
其中
read_probe
是函數指標,不同的
demuxer
基本都會實現自己的
read_probe
,舉例來說,
mpegts
對應的函數名是
mpegts_probe,
根據
read_probe
的傳回值來判斷,選擇得分(傳回值)最大的
demuxer
。
其中在
av_open_input_file
函數中首先調用
url_fopen
(
**
)來判斷參數中給定的視頻流是
檔案格式
(
file
)還是網路流格式(
rtsp
、
rtp
等),並調用相應的
open
函數來開啟流,其中
url_alloc(**)
為格式探測函數,
url_connect(**)—>uc->prot->url_open(**)
為相應的流開啟函數,其中
uc->prot->url_open
為函數指標,儲存選中格式的
open
函數。格式判斷方法比較簡單:判斷檔案名稱中是否有
“
:
”
,有再判斷具體是哪種流,沒有的話按照檔案格式來處理。
格式探測完畢並開啟流之後(將
uc->is_connected
設定為
1
)
,url_open
函數結束。接下來執行
url_fdopen(**),
在此過程中通過
URLContext
資料結構(不同的格式會有不同的讀取包的方式)來初始化
ByteIOContext
,
ByteIOContext
是
AVFormatContext
的成員,其成員包括流屬性,已經對流的讀取、寫入、定位等函數指標。經過初始化此結構程式便可以讀取實際的資料包來做進一步分析了。
下面以
mpegts
為例,看具體選擇
demuxer
的過程。過程通過調用
read_probe
()來確定,在
mpegts
中是調用
libavformat/mpegts.c
中的
mpegts_probe
(
**
)
-->analyze(***),
具體原理是讀取一定數量的包(此
處是
2048Byte
,計算下來應該是
10
個包
204*10=2040
),判斷
0x47
出現的次數(
0x47
是
mpegts
的同步位),根據出現次數來計算分數(具體程式碼分析可參考引用的其他文章)。
如果匹配的話,肯定是滿分,故而選擇。
1.2psi
分析
選擇完畢
demuxer
(
format
)之後,下一步開啟視頻流並分析。主要分析
PSI
資訊,即包含的頻道及各個頻道關聯的具體視頻或音頻流的資訊等。具體過程如下:
av_open_input_stream
(
**
)
-->read_header(),
其中
read_header
也是函數指標,在此處我們還以
mpegts
為例,實際調用的函數為
mpegts_read_header
(
**
),此過程主要
是分析
ts
流中有哪些視頻流、音頻流等參數(主要通過分析
ts
流中的
pat sdt
表等資訊得出),調用過程為
:
mpegts_read_header(***)-->handle_packets(**)-->handle_packet(**)
,
其中在
mpegts_read_header
(
**
)中首先通過
pegts_open_section_filter(ts, SDT_PID, sdt_cb, ts, 1);
mpegts_open_section_filter(ts, PAT_PID, pat_cb, ts, 1);
掛接
sdt
以及
pat
的處理函數,即
sdt
和
pat
是最初始的資訊,在
pat
中才能得到
pmt
的
pid
以便進一步分析。在
handle_packets
中讀取一個包然後調用
handle_packet(**)
處理。處理過程中調用
write_section_data
接收
pat
等資訊,當一個完整的
pat
接收完畢後,調用
tss->section_cb(tss1, tss->section_buf,
tss->section_h_size);
來解析,此處
section_cb
便是通過
mpegts_open_section_filter
掛接的
pat_cb(
以
pat
為例,
sdt
分析方式一樣
)
。分析過程得到
pmt
的
pid
並通過
mpegts_open_section_filter(ts,
pmt_pid, pmt_cb, ts, 1);
將
pmt
的解析函數掛接上來,之後便可以讀取
pmt
包並處理了。通過分析
pmt
便可以得到具體的媒體流並通過
add_pes_stream(ts, pid, pcr_pid);
函數來建立各個媒體流的資訊,也得到了此
ts
中包含的媒體流的數量和
id
,在
pmt_cb
中還有一個很重要的函數是
mpegts_set_stream_info(st,
pes, stream_type, prog_reg_desc);
此函數通過
mpegts_find_stream_type(st, pes->stream_type, MISC_types);
可以定位到每個媒體流的具體解碼器(主要是儲存
codec_id,
其他在後面初始化),之後解碼的時候便可以通過
codec_id
來選擇具體的解碼器了。增加流的操作中會調用
tss = mpegts_open_pes_filter(ts, pid, mpegts_push_data, pes);
來掛接實際的解析
pes
的函數,之後便可以接收具體的
pes
包並解析實際的媒體資料。以上內容並不保證準確,需進一步確認。
經過此過程,便得到了基本的流資訊(
video
audio
數量及相應的
ID
),在讀包後分析
ID
即可相應處理,達到解複用的目的了。分析參數得到流資訊的流程說明如下:主要是兩個表
PAT
和
PMT
,其中
PAT
存
儲的頻道資訊,一個頻道便有一個
PMT
,而
PMT
儲存的是此頻道由哪些流組成(視頻流
音頻流
以及相應的
PID
),以及流的具體格式(
h264
等),通過分析完每個
PMT
便得到了總的視頻流及頻道等資訊。具體的關於sdt和pat、pmt的介紹可參照網上資訊或查閱標準文檔。
2
、解碼器相關
2.1
解碼器選擇:
通過如上分析,已經得到瞭解碼器的
codec_id
,根據
codec_id
選擇即可