一、FFMPEG 中MPEG2 TS 流解碼的流程分析
其實MPEG2是一族協議,至少已經成為ISO標準的就有以下幾部分: ISO/IEC-13818-1:系統部分; ISO/IEC-13818-2:視頻編碼格式; ISO/IEC-13818-3:音頻編碼格式; ISO/IEC-13818-4:一致性測試; ISO/IEC-13818-5:軟件部分; ISO/IEC-13818-6:數字存儲媒體命令與控制; ISO/IEC-13818-7:高級音頻編碼; ISO/IEC-13818-8:系統解碼實時接口; 我不是很想說實際的音視頻編碼格式,畢竟協議已經很清楚了,我主要想說說這些部分 怎麼組合起來在實際應用中工作的。 第一部分(系統部分)很重要,是構成以MPEG2為基礎的應用的基礎.很繞口,是吧, 我簡單解釋一下:比如DVD實際上是以系統部分定義的PS流為基礎,加上版權管理等其 他技術構成的。
而我們的故事主角,則是另外種流格式,TS流, 它在現階段最大的應用是在數字電視節目的傳輸存儲上,因此,你可以理解TS實際上是種傳輸協議, 實際傳輸的負載關係不大,只是在TS中傳輸了音頻,視頻或者其他數據。
先說一下為什麼會有這兩種格式的出現, PS適用於沒有損耗的環境下面存儲,而TS則適用於可能出現損耗或者錯誤的各種物理網絡環境, 比如你在公交上看的電視,很有可能就是基於TS的DVB-T的應用
我們再來看MPEG2協議中的一些概念,為理解代碼做好功課: l ES(Elementary Stream):
wiki上 說 An elementary stream (ES) is defined by MPEG communication protocol is usually the output of an audio or video encoder. 恩,很簡單吧,就是編碼器編出的組數據,可能是音頻的,視頻的,或者其他數據。 說到著,其實可以對編碼器的流程思考一下,無非是執行: 採樣,量化,編碼這3個步驟中的編碼而已(有些設備可能會包含前面的採樣和量化)。關於視頻編碼的基本理論,還是請參考其它的資料。
l PES (Packetized Elementary Stream): wiki上說allows an Elementary stream to be divided into packets” 其實可以理解成,把個源源不斷的數據(音頻,視頻或者其他)流,打斷成段段,以便處理. l TS(Transport Stream): l PS(Program Stream): 這兩個上面已經有所提及,後面會詳細分析TS,我對PS格式興趣不大. 步入正題 才進入正題,恩,看來閒話太多了,直接看Code. 前面說過,TS是種傳輸協議,因此,對應FFmpeg,可以認為他是種封裝格式。
因此,對應的代碼應該先去libavformat裡面找,很容易找,就是mpegts.c:)。 還是逐步看 過來:
[libavformat/utils.c]
01 |
int av_open_input_file(AVFormatContext **ic_ptr, const char *filename, |
04 |
AVFormatParameters *ap) |
07 |
AVProbeData probe_data, *pd = &probe_data; |
08 |
ByteIOContext *pb = NULL; |
11 |
pd->filename = filename; |
14 |
################################################## ############################## |
15 |
【1】這段代碼其實是為了針對不需要Open文件的容器Format 的探測,其實就是使用 |
16 |
AVFMT_NOFILE標記的容器格式單獨處理,現在只有使用了該標記的Demuxer很少, |
17 |
只有image2_demuxer,rtsp_demuxer,因此我們分析TS時候可以不考慮這部分 |
18 |
################################################## ############################## |
21 |
fmt = av_probe_input_format(pd, 0); |
25 |
if (!fmt || !(fmt->flags & AVFMT_NOFILE)) { |
27 |
################################################## ####################### |
28 |
【2】這個函數似乎很好理解,無非是帶緩衝的IO的封裝,不過我們既然此了, |
29 |
不妨跟?下去,看看別人對帶緩衝的IO 操作封裝的實現:) |
30 |
################################################## ####################### |
31 |
if ((err=url_fopen(&pb, filename, URL_RDONLY)) < 0) { |
35 |
url_setbufsize(pb, buf_size); |
37 |
for (probe_size= PROBE_BUF_MIN; probe_size<=PROBE_BUF_MAX && !fmt; probe_size<<=1){ |
38 |
int score= probe_size < PROBE_BUF_MAX ? AVPROBE_SCORE_MAX/4 : 0; |
40 |
pd->buf= av_realloc(pd->buf, probe_size + AVPROBE_PADDING_SIZE); |
41 |
################################################## ##################### |
42 |
【3】真正將文件讀入pd的buffer的地方,實際上最終調用 FILE protocol |
43 |
的file_read(),將內容讀入pd 的buf,具體代碼如果有興趣可以自己跟? |
44 |
################################################## ##################### |
45 |
pd->buf_size = get_buffer(pb, pd->buf, probe_size); |
46 |
memset (pd->buf+pd->buf_size, 0, AVPROBE_PADDING_SIZE); |
47 |
if (url_fseek(pb, 0, SEEK_SET) < 0) { |
49 |
if (url_fopen(&pb, filename, URL_RDONLY) < 0) { |
55 |
################################################## ################### |
56 |
【4】此時的pd已經有了需要分析的原始文件,只需要查找相應容器format |
57 |
的Tag 比較,以判斷讀入的究竟為什麼容器格式,這裡 |
58 |
################################################## ################### |
60 |
fmt = av_probe_input_format2(pd, 1, &score); |
70 |
if (fmt->flags & AVFMT_NEEDNUMBER) { |
71 |
if (!av_filename_number_test(filename)) { |
72 |
err = AVERROR_NUMEXPECTED; |
76 |
err = av_open_input_stream(ic_ptr, pb, filename, fmt, ap); |
【2】帶緩衝IO的封裝的實現[liavformat/aviobuf.c]
01 |
int url_fopen(ByteIOContext **s, const char *filename, int flags) |
05 |
err = url_open(&h, filename, flags); |
08 |
err = url_fdopen(s, h); |
可以看,下面的這個函數,先查找是否是FFmpeg支持的protocol的格式,如果文件 名 ??不符合,則默認是FILE protocol格式,很顯然,這裡protocol判斷是以URL的方式判讀 的, ??因此基本上所有的IO接口函數都是url_xxx的形式。 在這也可以看,FFmpeg支持的protocol有: /* protocols */ REGISTER_PROTOCOL (FILE, file); REGISTER_PROTOCOL (HTTP, http); REGISTER_PROTOCOL (PIPE, pipe); REGISTER_PROTOCOL (RTP, rtp); REGISTER_PROTOCOL (TCP, tcp ); REGISTER_PROTOCOL (UDP, udp); 而大部分情況下,如果你不指明類似 file://xxx,http://xxx 格式,它都以FILE protocol 來處理。
[liavformat/avio.c]
01 |
int url_open(URLContext **puc, const char *filename, int flags) |
05 |
char proto_str[128], *q; |
08 |
while (*p != '\0' && *p != ':' ) { |
12 |
if ((q - proto_str) < sizeof (proto_str) - 1) |
17 |
if (*p == '\0' || (q - proto_str) <= 1) { |
19 |
strcpy (proto_str, "file" ); |
25 |
if (! strcmp (proto_str, up->name)) |
26 |
################################################## ############### |
27 |
很顯然,此時已經知道up,filename,flags |
28 |
################################################## ############### |
29 |
return url_open_protocol (puc, up, filename, flags); |
33 |
return AVERROR(ENOENT); |
[libavformat/avio.c]
01 |
int url_open_protocol (URLContext **puc, struct URLProtocol *up, |
02 |
const char *filename, int flags) |
07 |
################################################## ######################## |
09 |
################################################## ######################## |
10 |
uc = av_malloc( sizeof (URLContext) + strlen (filename) + 1); |
12 |
err = AVERROR(ENOMEM); |
15 |
#if LIBAVFORMAT_VERSION_MAJOR >= 53 |
16 |
uc->av_class = &urlcontext_class; |
18 |
################################################## ######################## |
20 |
################################################## ######################## |
21 |
uc->filename = ( char *) &uc[1]; |
22 |
strcpy (uc->filename, filename); |
26 |
uc->max_packet_size = 0; |
27 |
err = up->url_open(uc, filename, flags); |
35 |
if ((flags & (URL_WRONLY | URL_RDWR)) || ! strcmp (up->name, "file" )) |
36 |
if (!uc->is_streamed && url_seek(uc, 0, SEEK_SET) < 0) |
上面這個函數不難理解,但有些地方頗值得玩味,比如上面給出問號的地方,你明白 為什麼這樣Coding麼? 很顯然,此時up->url_open()實際上調用的是file_open() [libavformat/file.c],看完這個函數,對上面的內存分配,是否恍然大悟:) 上面只是分析了url_open(),還沒有分析url_fdopen(s, h);這部分代碼,也留給有好 奇心的你了:)恩,為了追?這個流程,走得有些遠,但不是全然無用:) 於來了【4】 ,我們來看MPEG TS格式的偵測過程,這其實才是我們今天的主角 4. MPEG TS格式的探測過程
[liavformat/mpegts.c]
01 |
static int mpegts_probe(AVProbeData *p) |
04 |
const int size= p->buf_size; |
05 |
int score, fec_score, dvhs_score; |
06 |
#define CHECK_COUNT 10 |
07 |
if (size < (TS_FEC_PACKET_SIZE * CHECK_COUNT)) |
09 |
score = analyze(p->buf, TS_PACKET_SIZE * CHECK_COUNT, TS_PACKET_SIZE, NULL); |
10 |
dvhs_score = analyze(p->buf, TS_DVHS_PACKET_SIZE *CHECK_COUNT, TS_DVHS_PACKET_SIZE, NULL); |
11 |
fec_score= analyze(p->buf, TS_FEC_PACKET_SIZE*CHECK_COUNT, TS_FEC_PACKET_SIZE, NULL); |
14 |
if (score > fec_score && score > dvhs_score && score > 6) |
15 |
return AVPROBE_SCORE_MAX + score - CHECK_COUNT; |
16 |
else if (dvhs_score > score && dvhs_score > fec_score && dvhs_score > 6) |
17 |
return AVPROBE_SCORE_MAX + dvhs_score - CHECK_COUNT; |
18 |
else if (fec_score > 6) |
19 |
return AVPROBE_SCORE_MAX + fec_score - CHECK_COUNT; |
24 |
if (match_ext(p->filename, "ts" )) |
25 |
return AVPROBE_SCORE_MAX; |
之所以會出現3種格式,主要原因是:TS標準是188Bytes,而小日本自己又弄了個 192Bytes的DVH-S格式,第三種的204Bytes則是在188Bytes的基礎上,加上16Bytes的 FEC (前向糾錯).
01 |
static int analyze( const uint8_t *buf, int size, int packet_size, int *index) |
03 |
int stat[packet_size]; |
07 |
memset (stat, 0, packet_size* sizeof ( int )); |
09 |
################################################## ######################## |
10 |
由於查找的特定格式至少3 個Bytes,因此,至少最後3 個Bytes 不用查找 |
11 |
################################################## ######################## |
12 |
for (x=i=0; i<size-3; i++){ |
13 |
################################################## #################### |
15 |
################################################## #################### |
16 |
if (buf[i] == 0x47 && !(buf[i+1] & 0x80) && (buf[i+3] & 0x30)){ |
18 |
if (stat[x] > best_score){ |
這個函數簡單說來,是在size大小的buf中,尋找滿足特定格式,長度為packet_size 的packet的個數,顯然,返回的值越大越可能是相應的格式(188/192/204),其中的這個特 定格式,其實就是協議的規定格式:
01 |
Syntax No. of bits Mnemonic |
04 |
transport_error_indicator 1 bslbf |
05 |
payload_unit_start_indicator 1 bslbf |
06 |
transport_priority 1 bslbf |
08 |
transport_scrambling_control 2 bslbf |
09 |
adaptation_field_control 2 bslbf |
10 |
continuity_counter 4 uimsbf |
11 |
if (adaptation_field_control== '10' || adaptation_field_control== '11' ){ |
14 |
if (adaptation_field_control== '01' || adaptation_field_control== '11' ) { |
其中的sync_byte固定為0x47,即上面的: buf[i] == 0x47 由於transport_error_indicator為1的TS Packet實際有錯誤,表示攜帶的數據無意義, 這樣的Packet顯然沒什麼意義,因此: !(buf[i +1] & 0x80) 對於adaptation_field_control,如果為取值為0x00,則表示為未來保留,現在不用,因此: buf[i+3] & 0x30 這就是MPEG TS的偵測過程,很簡單吧:) 後面我們分析如何從mpegts文件中獲取stream的過程。 5.漸入佳境 恩,前面的基礎因該已近夠了,有點像手剝洋蔥頭的感,我們來看看針對MPEG TS 的相應解析過程。 我們後面的代碼,主要集中在[libavformat/mpegts.c]裡面,毛爺爺說:集 中優勢兵力打圍殲,恩,開始吧,螞蟻啃骨頭。
01 |
static int mpegts_read_header(AVFormatContext *s, |
02 |
AVFormatParameters *ap) |
04 |
MpegTSContext *ts = s->priv_data; |
05 |
ByteIOContext *pb = s->pb; |
11 |
################################################## ################### |
12 |
【1】有了前面分析緩衝IO 的經歷,下面的代碼就不是什麼問題了:) |
13 |
################################################## ################### |
15 |
len = get_buffer(pb, buf, sizeof (buf)); |
16 |
if (len != sizeof (buf)) |
18 |
################################################## ################### |
19 |
【2】前面偵測文件格式時候其實已經知道TS 包的大小了,這裡又偵測次,其實 |
20 |
有些多餘,估計是因為解碼框架的原因,已近偵測的包大小沒能從前面被帶過來, |
21 |
可見框架雖好,卻也會帶來或多或少的些不利影響 |
22 |
################################################## ################### |
23 |
ts->raw_packet_size = get_packet_size(buf, sizeof (buf)); |
24 |
if (ts->raw_packet_size <= 0) |
29 |
if (s->iformat == &mpegts_demuxer) { |
32 |
url_fseek(pb, pos, SEEK_SET); |
40 |
mpegts_set_service(ts); |
44 |
handle_packets(ts, s->probesize); |
48 |
av_log(ts->stream, AV_LOG_DEBUG, "tuning done\n" ); |
50 |
s->ctx_flags |= AVFMTCTX_NOHEADER; |
54 |
url_fseek(pb, pos, SEEK_SET); |
這裡簡單說一下MpegTSContext *ts,從上面可以看,其實這是為了解碼不同容器格式所使用的私有數據, 只有在相應的諸如mpegts.c文件才可以使用的,這樣,增加了這個庫的模塊化,而模塊化的最大好 ??處, 則在於把問題集中了個很小的有限區域裡面,如果你自己構造程序時候,不妨多參考其基本思想--這樣的化, 你之後的代碼,還有你之後的生活,都將輕鬆許多。 【3】【4】其實調用的是同個函數:mpegts_open_section_filter()我們來看看意欲何為。
01 |
static MpegTSFilter *mpegts_open_section_filter(MpegTSContext *ts, unsigned int pid, |
02 |
SectionCallback *section_cb, |
07 |
MpegTSSectionFilter *sec; |
09 |
av_log(ts->stream, AV_LOG_DEBUG, "Filter: pid=0x%x\n" , pid); |
11 |
if (pid >= NB_PID_MAX || ts->pids[pid]) |
13 |
filter = av_mallocz( sizeof (MpegTSFilter)); |
16 |
ts->pids[pid] = filter; |
17 |
filter->type = MPEGTS_SECTION; |
20 |
sec = &filter->u.section_filter; |
21 |
sec->section_cb = section_cb; |
23 |
sec->section_buf = av_malloc(MAX_SECTION_SIZE); |
24 |
sec->check_crc = check_crc; |
25 |
if (!sec->section_buf) { |
要完全明白這部分代碼,其實需要分析作者對數據結構的定義: 依次為: struct MpegTSContext; | V struct MpegTSFilter; | V +---------------+--- ------------+ | | VV MpegTSPESFilter MpegTSSectionFilter
其實很簡單,就是struct MpegTSContext;中有NB_PID_MAX(8192)個TS的Filter,而 每個struct MpegTSFilter可能是PES的Filter或者Section的Filter。 我們先說 ??為什麼是8192,在前面的分析中: 給出過TS的語法結構:
01 |
Syntax No. of bits Mnemonic |
04 |
transport_error_indicator 1 bslbf |
05 |
payload_unit_start_indicator 1 bslbf |
06 |
transport_priority 1 bslbf |
08 |
transport_scrambling_control 2 bslbf |
09 |
adaptation_field_control 2 bslbf |
10 |
continuity_counter 4 uimsbf |
11 |
if (adaptation_field_control== '10' || adaptation_field_control== '11' ){ |
14 |
if (adaptation_field_control== '01' || adaptation_field_control== '11' ) { |
而8192,則是2^13=8192(PID)的最大數目,而為什麼會有PES和Section的區分,請參 考ISO/IEC-13818-1,我實在不太喜歡重複已有的東西. 可見【3】【4】,就是掛載了兩個Section類型的過濾器,其實在TS的兩種負載中,section 是PES的元數據,只有先解析了section,才能進步解析PES數據,因此先掛上section的 過濾器。 掛載上了兩種section過濾器,如下: ====================================== ============================ PID Section Name Callback ================== ================================================ SDT_PID( 0x0011) ServiceDescriptionTable sdt_cb PAT_PID(0x0000) ProgramAssociationTable pat_cb
既然自是掛上Callback,自然是在後面的地方使用,因此,我們還是繼續 【5】處的代碼看看是最重要的地方了,簡單看來: handle_packets() | +->read_packet() | +->handle_packet() | +->write_section_data() read_packet()很簡單,就是去找sync_byte(0x47),而看來handle_packet()才會是我們真正 因該關注的地方了:) 這個函數很重要,我們貼出代碼,以備分析:
002 |
static void handle_packet(MpegTSContext *ts, const uint8_t *packet) |
004 |
AVFormatContext *s = ts->stream; |
006 |
int len, pid, cc, cc_ok, afc, is_start; |
007 |
const uint8_t *p, *p_end; |
009 |
################################################## ######## |
011 |
################################################## ######## |
012 |
pid = AV_RB16(packet + 1) & 0x1fff; |
013 |
if (pid && discard_pid(ts, pid)) |
015 |
################################################## ######## |
016 |
是否是PES 或者Section 的開頭(payload_unit_start_indicator) |
017 |
################################################## ######## |
019 |
is_start = packet[1] & 0x40; |
022 |
################################################## ######## |
023 |
ts->auto_guess 此時為0,因此不考慮下面的代碼 |
024 |
################################################## ######## |
025 |
if (ts->auto_guess && tss == NULL && is_start) { |
026 |
add_pes_stream(ts, pid, -1, 0); |
032 |
################################################## ######## |
033 |
代碼說的很清楚,雖然檢查,但不利用檢查的結果 |
034 |
################################################## ######## |
036 |
cc = (packet[3] & 0xf); |
037 |
cc_ok = (tss->last_cc < 0) || ((((tss->last_cc + 1) & 0x0f) == cc)); |
040 |
################################################## ######## |
041 |
跳adaptation_field_control |
042 |
################################################## ######## |
044 |
afc = (packet[3] >> 4) & 3; |
055 |
################################################## ######## |
057 |
################################################## ######## |
059 |
p_end = packet + TS_PACKET_SIZE; |
063 |
ts->pos47= url_ftell(ts->stream->pb) % ts->raw_packet_size; |
065 |
if (tss->type == MPEGTS_SECTION) { |
067 |
################################################## ############# |
068 |
針對Section,符合部分第個字節為pointer field,該字段如果為0, |
069 |
則表示後面緊跟著的是Section的開頭,否則是某Section的End部分和 |
070 |
另Section 的開頭,因此,這裡的流程實際上由兩個值is_start |
071 |
(payload_unit_start_indicator)和len(pointer field)起來決定 |
072 |
################################################## ############# |
078 |
################################################## ###### |
081 |
負載部分由A Section 的End 部分和B Section 的Start 組成,把A 的 |
083 |
################################################## ###### |
085 |
write_section_data(s, tss, p, len, 0); |
092 |
################################################## ###### |
095 |
負載部分由A Section 的End 部分和B Section 的Start 組成,把B 的 |
101 |
負載部分僅是個Section 的Start 部分,將其寫入 |
102 |
################################################## ###### |
103 |
write_section_data(s, tss, p, p_end - p, 1); |
106 |
################################################## ###### |
108 |
負載部分僅是個Section 的中間部分部分,將其寫入 |
109 |
################################################## ###### |
110 |
write_section_data(s, tss, p, p_end - p, 0); |
112 |
################################################## ######## |
113 |
如果是PES 類型,直接調用其Callback,但顯然,只有Section 部分 |
115 |
################################################## ######## |
116 |
tss->u.pes_filter.pes_cb(tss, p, p_end - p, is_start); |
write_section_data()函數則反複收集buffer中的數據,指導完成相關Section的重組過 程,然後調用之前註冊的兩個section_cb: 後面我們將分析之前掛在的兩個section_cb
二、mpegts.c文件分析
1綜述 ffmpeg框架對應MPEG-2 TS流的解析的代碼在mpegts.c文件中,該文件有兩個解復 用的實例:mpegts_demuxer和mpegtsraw_demuxer,mpegts_demuxer對應的真實的TS流格 式,也就是機頂盒直接處理的TS流,本文主要分析和該種格式相關的代碼; mpegtsraw_demuxer這個格式我沒有遇見過,本文中不做分析。 本文針對的ffmpeg的版本是 0.5版本。 2 mpegts_demuxer結構分析
01 |
AVInputFormat mpegts_demuxer = {au |
03 |
NULL_IF_CONFIG_SMALL( "MPEG-2 transport stream format" ), |
04 |
ONFIG_SMALL 宏,該域返回NULL,也就是取消long_name 域的定義。 |
05 |
sizeof (MpegTSContext), |
12 |
.flags = AVFMT_SHOW_IDS|AVFMT_TS_DISCONT, |
該結構通過av_register_all函數註冊ffmpeg的主框架中,通過mpegts_probe函數來 檢測是否是TS流格式,然後通過mpegts_read_header函數找路音頻流和路視頻流(注 意:在該函數中沒有找全所有的音頻流和視頻流),最後調用mpegts_read_packet函數將找 的音頻流和視頻流數據提取出來,通過主框架推入解碼器。
3 mpegts_probe函數分析 mpegts_probe被av_probe_input_format2調用,根據返回的score來判斷那種格式的可 能性最大。 mpegts_probe調用了analyze函數,我們先分析一下analyze函數。
01 |
static int analyze( const uint8_t *buf, int size, int packet_size, int *index) |
03 |
int stat[TS_MAX_PACKET_SIZE]; |
07 |
memset (stat, 0, packet_size* sizeof ( int )); |
08 |
for (x=i=0; i<size-3; i++){ |
09 |
if (buf[i] == 0x47 && !(buf[i+1] & 0x80) && (buf[i+3] & 0x30)){ |
11 |
if (stat[x] > best_score){ |
analyze函數的思路: buf[i] == 0x47 && !(buf[i+1] & 0x80) && (buf[i+3] & 0x30)是TS流同步開始的模式, 0x47是TS流同步的標誌,(buf[i+1] & 0x80是傳輸錯誤標誌,buf[i+3] & 0x30為0時表示為 ISO/IEC未來使用保留,目前不存在這樣的值。記該模式為TS流同步模式” stat數組變量存儲的是TS流同步模式”在某個位置出現的次數。 analyze函數掃描檢測數據,如果發現該模式,則stat[i%packet_size]++(函數中的x變 量就是用來取模運算的,因為當x==packet_size時,x就被歸零),掃描完後,自然是同步 位置的累加值最大。 mpegts_probe函數通過調用analyze函數來得相應的分數,ffmpeg框架會根據該分 數判斷是否是對應的格式,返回對應的AVInputFormat實例。 4 mpegts_read_header函數分析 下文中省略號代表的是和mpegtsraw_demuxer相關的代碼,暫不涉及。
001 |
<pre class = "cpp" name= "code" > static int mpegts_read_header(AVFormatContext *s, |
002 |
AVFormatParameters *ap) |
004 |
MpegTSContext *ts = s->priv_data; |
005 |
ByteIOContext *pb = s->pb; |
014 |
len = get_buffer(pb, buf, sizeof (buf)); |
015 |
if (len != sizeof (buf)) |
022 |
ts->raw_packet_size = get_packet_size(buf, sizeof (buf)); |
023 |
if (ts->raw_packet_size <= 0) |
041 |
if (s->iformat == &mpegts_demuxer) { |
044 |
url_fseek(pb, pos, SEEK_SET); |
046 |
mpegts_open_section_filter(ts, SDT_PID, sdt_cb, ts, 1); |
048 |
mpegts_open_section_filter(ts, PAT_PID, pat_cb, ts, 1); |
050 |
handle_packets(ts, s->probesize); |
056 |
dprintf(ts->stream, "tuning done\n" ); |
057 |
s->ctx_flags |= AVFMTCTX_NOHEADER; |
062 |
url_fseek(pb, pos, SEEK_SET); |
070 |
<p> 下面介紹被mpegts_read_header直接或者間接調用的幾個函數: mpegts_open_section_filter, handle_packets,handle_packet 5 mpegts_open_section_filter 函數分析這個函數可以解釋mpegts.c 代碼結構的精妙之處,PSI 業務信息表的處理都是通過該函數掛載MpegTSContext 結構的pids 字段上的。 這樣如果你想增加別的業務信息的表處理函數只要通過這個函數來掛載即可,體現了 |
071 |
軟件設計的著名的開閉”原則。下面分析一下他的代碼。 </p> |
072 |
<pre class = "cpp" name= "code" > static MpegTSFilter *mpegts_open_section_filter(MpegTSContext *ts, unsigned int pid, |
073 |
SectionCallback *section_cb, void *opaque, |
076 |
MpegTSFilter *filter; |
077 |
MpegTSSectionFilter *sec; |
078 |
dprintf(ts->stream, "Filter: pid=0x%x\n" , pid); |
079 |
if (pid >= NB_PID_MAX || ts->pids[pid]) |
083 |
filter = av_mallocz( sizeof (MpegTSFilter)); |
087 |
ts->pids[pid] = filter; |
090 |
filter->type = MPEGTS_SECTION; |
094 |
filter->last_cc = -1; |
096 |
sec = &filter->u.section_filter; |
097 |
sec->section_cb = section_cb; |
098 |
sec->opaque = opaque; |
102 |
sec->section_buf = av_malloc(MAX_SECTION_SIZE); |
103 |
sec->check_crc = check_crc; |
104 |
if (!sec->section_buf) { |
113 |
6 handle_packets 函數分析<br> |
114 |
handle_packets 函數在兩個地方被調用, 個是mpegts_read_header 函數中, <br> |
115 |
另外個是mpegts_read_packet 函數中,被mpegts_read_header 函數調用是用<br> |
116 |
來搜索PSI 業務信息,nb_packets 參數為探測的ts 包的個數;在mpegts_read_packet <br> |
117 |
函數中被調用用來搜索補充PSI 業務信息和demux PES 流,nb_packets 為0,0 不<br> |
119 |
<pre class = "cpp" name= "code" > static int handle_packets(MpegTSContext *ts, int nb_packets) |
121 |
AVFormatContext *s = ts->stream; |
122 |
ByteIOContext *pb = s->pb; |
123 |
uint8_t packet[TS_PACKET_SIZE]; |
131 |
if (ts->stop_parse>0) |
134 |
if (nb_packets != 0 && packet_num >= nb_packets) |
137 |
ret = read_packet(pb, packet, ts->raw_packet_size); |
140 |
handle_packet(ts, packet); |
148 |
<p>7 handle_packet 函數分析<br> |
149 |
可以說handle_packet 是mpegts.c 代碼的核心,所有的其他代碼都是為<br> |
151 |
在調用該函數之前先調用read_packet 函數獲得個ts包(通常是188bytes), <br> |
152 |
然後傳給該函數,packet參數就是TS包。 </p> |
153 |
<pre class = "cpp" name= "code" > static int handle_packet(MpegTSContext *ts, const uint8_t *packet) |
155 |
AVFormatContext *s = ts->stream; |
157 |
int len, pid, cc, cc_ok, afc, is_start; |
158 |
const uint8_t *p, *p_end; |
161 |
pid = AV_RB16(packet + 1) & 0x1fff; |
162 |
if (pid && discard_pid(ts, pid)) |
164 |
is_start = packet[1] & 0x40; |
168 |
if (ts->auto_guess && tss == NULL && is_start) { |
169 |
add_pes_stream(ts, pid, -1, 0); |
177 |
cc = (packet[3] & 0xf); |
178 |
cc_ok = (tss->last_cc < 0) || ((((tss->last_cc + 1) & 0x0f) == cc)); |
181 |
afc = (packet[3] >> 4) & 3; |
192 |
p_end = packet + TS_PACKET_SIZE; |
195 |
pos = url_ftell(ts->stream->pb); |
196 |
ts->pos47= pos % ts->raw_packet_size; |
197 |
if (tss->type == MPEGTS_SECTION) { |
214 |
write_section_data(s, tss, p, len, 0); |
222 |
write_section_data(s, tss, p, p_end - p, 1); |
227 |
write_section_data(s, tss, p, p_end - p, 0); |
234 |
if ((ret = tss->u.pes_filter.pes_cb(tss, p, p_end - p, is_start, |
235 |
pos - ts->raw_packet_size)) < 0) |
243 |
8 write_section_data 函數分析<br> |
244 |
PSI 業務信息表在TS流中是以段為單傳輸的。 </p> |
245 |
<pre class = "cpp" name= "code" > static void write_section_data(AVFormatContext *s, MpegTSFilter *tss1, |
246 |
const uint8_t *buf, int buf_size, int is_start) |
248 |
MpegTSSectionFilter *tss = &tss1->u.section_filter; |
253 |
memcpy (tss->section_buf, buf, buf_size); |
255 |
tss->section_index = buf_size; |
257 |
tss->section_h_size = -1; |
259 |
tss->end_of_section_reached = 0; |
262 |
if (tss->end_of_section_reached) |
264 |
len = 4096 - tss->section_index; |
267 |
memcpy (tss->section_buf + tss->section_index, buf, len); |
268 |
tss->section_index += len; |
271 |
if (tss->section_h_size == -1 && tss->section_index >= 3) { |
272 |
len = (AV_RB16(tss->section_buf + 1) & 0xfff) + 3; |
275 |
tss->section_h_size = len; |
278 |
if (tss->section_h_size != -1 && tss->section_index >= tss->section_h_size) { |
279 |
tss->end_of_section_reached = 1; |
280 |
if (!tss->check_crc || |
281 |
av_crc(av_crc_get_table(AV_CRC_32_IEEE), -1, |
282 |
tss->section_buf, tss->section_h_size) == 0) |
283 |
tss->section_cb(tss1, tss->section_buf, tss->section_h_size); |