ffmpeg中MPEG2 TS 流解碼的流程分析

 

一、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,
02 AVInputFormat *fmt,
03 int buf_size,
04 AVFormatParameters *ap)
05 {
06 int err, probe_size;
07 AVProbeData probe_data, *pd = &probe_data;
08 ByteIOContext *pb = NULL;
09 pd->filename = "" ;
10 if (filename)
11 pd->filename = filename;
12 pd->buf = NULL;
13 pd->buf_size = 0;
14 ################################################## ##############################
15 【1】這段代碼其實是為了針對不需要Open文件的容器Format 的探測,其實就是使用
16 AVFMT_NOFILE標記的容器格式單獨處理,現在只有使用了該標記的Demuxer很少,
17 只有image2_demuxer,rtsp_demuxer,因此我們分析TS時候可以不考慮這部分
18 ################################################## ##############################
19 if (!fmt) {
20 /* guess format if no file can be opened */
21 fmt = av_probe_input_format(pd, 0);
22 }
23 /* Do not open file if the format does not need it. XXX: specific
24 hack needed to handle RTSP/TCP */
25 if (!fmt || !(fmt->flags & AVFMT_NOFILE)) {
26 /* if no file needed do not try to open one */
27 ################################################## #######################
28 【2】這個函數似乎很好理解,無非是帶緩衝的IO的封裝,不過我們既然此了,
29 不妨跟?下去,看看別人對帶緩衝的IO 操作封裝的實現:)
30 ################################################## #######################
31 if ((err=url_fopen(&pb, filename, URL_RDONLY)) < 0) {
32 goto fail;
33 }
34 if (buf_size > 0) {
35 url_setbufsize(pb, buf_size);
36 }
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;
39 /* read probe data */
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) {
48 url_fclose(pb);
49 if (url_fopen(&pb, filename, URL_RDONLY) < 0) {
50 pb = NULL;
51 err = AVERROR(EIO);
52 goto fail;
53 }
54 }
55 ################################################## ###################
56 【4】此時的pd已經有了需要分析的原始文件,只需要查找相應容器format
57 的Tag 比較,以判斷讀入的究竟為什麼容器格式,這裡
58 ################################################## ###################
59 /* guess file format */
60 fmt = av_probe_input_format2(pd, 1, &score);
61 }
62 av_freep(&pd->buf);
63 }
64 /* if still no format found, error */
65 if (!fmt) {
66 err = AVERROR_NOFMT;
67 goto fail;
68 }
69 /* check filename in case an image number is expected */
70 if (fmt->flags & AVFMT_NEEDNUMBER) {
71 if (!av_filename_number_test(filename)) {
72 err = AVERROR_NUMEXPECTED;
73 goto fail;
74 }
75 }
76 err = av_open_input_stream(ic_ptr, pb, filename, fmt, ap);
77 if (err)
78 goto fail;
79 return 0;
80 fail:
81 av_freep(&pd->buf);
82 if (pb)
83 url_fclose(pb);
84 *ic_ptr = NULL;
85 return err;
86 }
【2】帶緩衝IO的封裝的實現[liavformat/aviobuf.c]
01 int url_fopen(ByteIOContext **s, const char *filename, int flags)
02 {
03 URLContext *h;
04 int err;
05 err = url_open(&h, filename, flags);
06 if (err < 0)
07 return err;
08 err = url_fdopen(s, h);
09 if (err < 0) {
10 url_close(h);
11 return err;
12 }
13 return 0;
14 }

可以看,下面的這個函數,先查找是否是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)
02 {
03 URLProtocol *up;
04 const char *p;
05 char proto_str[128], *q;
06 p = filename;
07 q = proto_str;
08 while (*p != '\0' && *p != ':' ) {
09 /* protocols can only contain alphabetic chars */
10 if (! isalpha (*p))
11 goto file_proto;
12 if ((q - proto_str) < sizeof (proto_str) - 1)
13 *q++ = *p;
14 p++;
15 }
16 /* if the protocol has length 1, we consider it is a dos drive */
17 if (*p == '\0' || (q - proto_str) <= 1) {
18 file_proto:
19 strcpy (proto_str, "file" );
20 } else {
21 *q = '\0' ;
22 }
23 up = first_protocol;
24 while (up != NULL) {
25 if (! strcmp (proto_str, up->name))
26 ################################################## ###############
27 很顯然,此時已經知道up,filename,flags
28 ################################################## ###############
29 return url_open_protocol (puc, up, filename, flags);
30 up = up->next;
31 }
32 *puc = NULL;
33 return AVERROR(ENOENT);
34 }
[libavformat/avio.c]
01 int url_open_protocol (URLContext **puc, struct URLProtocol *up,
02 const char *filename, int flags)
03 {
04 URLContext *uc;
05 int err;
06  
07 ################################################## ########################
08 【a】? 為什麼這樣分配空間
09 ################################################## ########################
10 uc = av_malloc( sizeof (URLContext) + strlen (filename) + 1);
11 if (!uc) {
12 err = AVERROR(ENOMEM);
13 goto fail;
14 }
15 #if LIBAVFORMAT_VERSION_MAJOR >= 53
16 uc->av_class = &urlcontext_class;
17 #endif
18 ################################################## ########################
19 【b】? 這樣的用意又是為什麼
20 ################################################## ########################
21 uc->filename = ( char *) &uc[1];
22 strcpy (uc->filename, filename);
23 uc->prot = up;
24 uc->flags = flags;
25 uc->is_streamed = 0; /* default = not streamed */
26 uc->max_packet_size = 0; /* default: stream file */
27 err = up->url_open(uc, filename, flags);
28 if (err < 0) {
29 av_free(uc);
30 *puc = NULL;
31 return err;
32 }
33 //We must be carefull here as url_seek() could be slow, for example for
34 //http
35 if ((flags & (URL_WRONLY | URL_RDWR)) || ! strcmp (up->name, "file" ))
36 if (!uc->is_streamed && url_seek(uc, 0, SEEK_SET) < 0)
37 uc->is_streamed= 1;
38 *puc = uc;
39 return 0;
40 fail:
41 *puc = NULL;
42 return err;
43 }

上面這個函數不難理解,但有些地方頗值得玩味,比如上面給出問號的地方,你明白 為什麼這樣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)
02 {
03 #if 1
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))
08 return -1;
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);
12 // av_log(NULL, AV_LOG_DEBUG, "score: %d, dvhs_score: %d, fec_score: %d \n", score, dvhs_score, fec_score);
13 // we need a clear definition for the returned score otherwise things will become messy sooner or later
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;
20 else
21 return -1;
22 #else
23 /* only use the extension for safer guess */
24 if (match_ext(p->filename, "ts" ))
25 return AVPROBE_SCORE_MAX;
26 else
27 return 0;
28 #endif
29 }
之所以會出現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)
02 {
03 int stat[packet_size];
04 int i;
05 int x=0;
06 int best_score=0;
07 memset (stat, 0, packet_size* sizeof ( int ));
08  
09 ################################################## ########################
10 由於查找的特定格式至少3 個Bytes,因此,至少最後3 個Bytes 不用查找
11 ################################################## ########################
12 for (x=i=0; i<size-3; i++){
13 ################################################## ####################
14 參看後面的協議說明
15 ################################################## ####################
16 if (buf[i] == 0x47 && !(buf[i+1] & 0x80) && (buf[i+3] & 0x30)){
17 stat[x]++;
18 if (stat[x] > best_score){
19 best_score= stat[x];
20 if (index)
21 *index= x;
22 }
23 }
24 x++;
25 if (x == packet_size)
26 x= 0;
27 }
28 return best_score;
29 }
這個函數簡單說來,是在size大小的buf中,尋找滿足特定格式,長度為packet_size 的packet的個數,顯然,返回的值越大越可能是相應的格式(188/192/204),其中的這個特 定格式,其實就是協議的規定格式:
01 Syntax No. of bits Mnemonic
02 transport_packet(){
03 sync_byte 8 bslbf
04 transport_error_indicator 1 bslbf
05 payload_unit_start_indicator 1 bslbf
06 transport_priority 1 bslbf
07 PID 13 uimsbf
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' ){
12 adaptation_field()
13 }
14 if (adaptation_field_control== '01' || adaptation_field_control== '11' ) {
15 for (i=0;i<N;i++){
16 data_byte 8 bslbf
17 }
18 }
19
其中的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)
03 {
04 MpegTSContext *ts = s->priv_data;
05 ByteIOContext *pb = s->pb;
06 uint8_t buf[1024];
07 int len;
08 int64_t pos;
09 ......
10 /* read the first 1024 bytes to get packet size */
11 ################################################## ###################
12 【1】有了前面分析緩衝IO 的經歷,下面的代碼就不是什麼問題了:)
13 ################################################## ###################
14 pos = url_ftell(pb);
15 len = get_buffer(pb, buf, sizeof (buf));
16 if (len != sizeof (buf))
17 goto fail;
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)
25 goto fail;
26 ts->stream = s;
27 ts->auto_guess = 0;
28  
29 if (s->iformat == &mpegts_demuxer) {
30 /* normal demux */
31 /* first do a scaning to get all the services */
32 url_fseek(pb, pos, SEEK_SET);
33 #########
34 【3】
35 #########
36 mpegts_scan_sdt(ts);
37 #########
38 【4 】
39 #########
40 mpegts_set_service(ts);
41 #########
42 【5】
43 #########
44 handle_packets(ts, s->probesize);
45 /* if could not find service, enable auto_guess */
46 ts->auto_guess = 1;
47 #ifdef DEBUG_SI
48 av_log(ts->stream, AV_LOG_DEBUG, "tuning done\n" );
49 #endif
50 s->ctx_flags |= AVFMTCTX_NOHEADER;
51 } else {
52 ......
53 }
54 url_fseek(pb, pos, SEEK_SET);
55 return 0;
56 fail:
57 return -1;
58 }
這裡簡單說一下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,
03 void *opaque,
04 int check_crc)
05 {
06 MpegTSFilter *filter;
07 MpegTSSectionFilter *sec;
08 #ifdef DEBUG_SI
09 av_log(ts->stream, AV_LOG_DEBUG, "Filter: pid=0x%x\n" , pid);
10 #endif
11 if (pid >= NB_PID_MAX || ts->pids[pid])
12 return NULL;
13 filter = av_mallocz( sizeof (MpegTSFilter));
14 if (!filter)
15 return NULL;
16 ts->pids[pid] = filter;
17 filter->type = MPEGTS_SECTION;
18 filter->pid = pid;
19 filter->last_cc = -1;
20 sec = &filter->u.section_filter;
21 sec->section_cb = section_cb;
22 sec->opaque = opaque;
23 sec->section_buf = av_malloc(MAX_SECTION_SIZE);
24 sec->check_crc = check_crc;
25 if (!sec->section_buf) {
26 av_free(filter);
27 return NULL;
28 }
29 return filter;

要完全明白這部分代碼,其實需要分析作者對數據結構的定義: 依次為: 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
02 transport_packet(){
03 sync_byte 8 bslbf
04 transport_error_indicator 1 bslbf
05 payload_unit_start_indicator 1 bslbf
06 transport_priority 1 bslbf
07 PID 13 uimsbf
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' ){
12 adaptation_field()
13 }
14 if (adaptation_field_control== '01' || adaptation_field_control== '11' ) {
15 for (i=0;i<N;i++){
16 data_byte 8 bslbf
17 }
18 }
19 }

而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()才會是我們真正 因該關注的地方了:) 這個函數很重要,我們貼出代碼,以備分析:

001 /* handle one TS packet */
002 static void handle_packet(MpegTSContext *ts, const uint8_t *packet)
003 {
004 AVFormatContext *s = ts->stream;
005 MpegTSFilter *tss;
006 int len, pid, cc, cc_ok, afc, is_start;
007 const uint8_t *p, *p_end;
008  
009 ################################################## ########
010 獲取該包的PID
011 ################################################## ########
012 pid = AV_RB16(packet + 1) & 0x1fff;
013 if (pid && discard_pid(ts, pid))
014 return ;
015 ################################################## ########
016 是否是PES 或者Section 的開頭(payload_unit_start_indicator)
017 ################################################## ########
018  
019 is_start = packet[1] & 0x40;
020 tss = ts->pids[pid];
021  
022 ################################################## ########
023 ts->auto_guess 此時為0,因此不考慮下面的代碼
024 ################################################## ########
025 if (ts->auto_guess && tss == NULL && is_start) {
026 add_pes_stream(ts, pid, -1, 0);
027 tss = ts->pids[pid];
028 }
029 if (!tss)
030 return ;
031  
032 ################################################## ########
033 代碼說的很清楚,雖然檢查,但不利用檢查的結果
034 ################################################## ########
035 /* continuity check (currently not used) */
036 cc = (packet[3] & 0xf);
037 cc_ok = (tss->last_cc < 0) || ((((tss->last_cc + 1) & 0x0f) == cc));
038 tss->last_cc = cc;
039  
040 ################################################## ########
041 跳adaptation_field_control
042 ################################################## ########
043 /* skip adaptation field */
044 afc = (packet[3] >> 4) & 3;
045 p = packet + 4;
046 if (afc == 0) /* reserved value */
047 return ;
048 if (afc == 2) /* adaptation field only */
049 return ;
050 if (afc == 3) {
051 /* skip adapation field */
052 p += p[0] + 1;
053 }
054  
055 ################################################## ########
056 p已近達TS 包中的有效負載的地方
057 ################################################## ########
058 /* if past the end of packet, ignore */
059 p_end = packet + TS_PACKET_SIZE;
060 if (p >= p_end)
061 return ;
062  
063 ts->pos47= url_ftell(ts->stream->pb) % ts->raw_packet_size;
064  
065 if (tss->type == MPEGTS_SECTION) {
066 if (is_start) {
067 ################################################## #############
068 針對Section,符合部分第個字節為pointer field,該字段如果為0,
069 則表示後面緊跟著的是Section的開頭,否則是某Section的End部分和
070 另Section 的開頭,因此,這裡的流程實際上由兩個值is_start
071 (payload_unit_start_indicator)和len(pointer field)起來決定
072 ################################################## #############
073 /* pointer field present */
074 len = *p++;
075 if (p + len > p_end)
076 return ;
077 if (len && cc_ok) {
078 ################################################## ######
079 1).is_start == 1
080 len > 0
081 負載部分由A Section 的End 部分和B Section 的Start 組成,把A 的
082 End 部分寫入
083 ################################################## ######
084 /* write remaining section bytes */
085 write_section_data(s, tss, p, len, 0);
086 /* check whether filter has been closed */
087 if (!ts->pids[pid])
088 return ;
089 }
090 p += len;
091 if (p < p_end) {
092 ################################################## ######
093 2).is_start == 1
094 len > 0
095 負載部分由A Section 的End 部分和B Section 的Start 組成,把B 的
096 Start 部分寫入
097 或者:
098 3).
099 is_start == 1
100 len == 0
101 負載部分僅是個Section 的Start 部分,將其寫入
102 ################################################## ######
103 write_section_data(s, tss, p, p_end - p, 1);
104 }
105 } else if (cc_ok) {
106 ################################################## ######
107 4).is_start == 0
108 負載部分僅是個Section 的中間部分部分,將其寫入
109 ################################################## ######
110 write_section_data(s, tss, p, p_end - p, 0);
111 } else {
112 ################################################## ########
113 如果是PES 類型,直接調用其Callback,但顯然,只有Section 部分
114 解析完成後才可能解析PES
115 ################################################## ########
116 tss->u.pes_filter.pes_cb(tss, p, p_end - p, is_start);
117 }
118 }

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
02 "mpegts" , //demux的名稱
03 NULL_IF_CONFIG_SMALL( "MPEG-2 transport stream format" ), //如果定義了
04 ONFIG_SMALL 宏,該域返回NULL,也就是取消long_name 域的定義。
05 sizeof (MpegTSContext), //每個demuxer的結構的私有域的大小
06 mpegts_probe, //檢測是否是TS流格式
07 mpegts_read_header, //下文介紹
08 mpegts_read_packet, //下文介紹
09 mpegts_read_close, //關閉demuxer
10 read_seek, //下文介紹
11 mpegts_get_pcr, //下文介紹
12 .flags = AVFMT_SHOW_IDS|AVFMT_TS_DISCONT, //下文介紹
13 };

該結構通過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)
02 {
03 int stat[TS_MAX_PACKET_SIZE]; //積分統計結果
04 int i;
05 int x=0;
06 int best_score=0;
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)){
10 stat[x]++;
11 if (stat[x] > best_score){
12 best_score= stat[x];
13 if (index)
14 *index= x;
15 }
16 }
17 x++;
18 if (x == packet_size)
19 x= 0;
20 }
21 return best_score;
22 }
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)
003 {
004 MpegTSContext *ts = s->priv_data;
005 ByteIOContext *pb = s->pb;
006 uint8_t buf[5*1024];
007 int len;
008 int64_t pos;
009 ......
010 //保存流的當前位置,便於檢測操作完成後恢復原來的位置,
011 //這樣在播放的時候就不會浪費段流。
012 pos = url_ftell(pb);
013 //讀取段流來檢測TS 包的大小
014 len = get_buffer(pb, buf, sizeof (buf));
015 if (len != sizeof (buf))
016 goto fail;
017 //得TS 流包的大小,通常是188bytes,我目前見過的都是188 個字節的。
018 //TS 包的大小有三種:
019 //1) 通常情況下的188 字節
020 //2) 日本弄了個192Bytes 的DVH-S 格式
021 //3)在188Bytes 的基礎上,加上16Bytes 的FEC(前向糾錯),也就是204bytes
022 ts->raw_packet_size = get_packet_size(buf, sizeof (buf));
023 if (ts->raw_packet_size <= 0)
024 goto fail;
025 ts->stream = s;
026  
027 //auto_guess = 1, 則在handle_packet 的函數中只要發現個PES 的pid 就
028 //建立該PES 的stream
029 //auto_guess = 0, 則忽略。
030 //auto_guess 主要作用是用來在TS 流中沒有業務信息時,如果被設置成了1 的話,
031 //那麼就會將任何個PID 的流當做媒體流建立對應的PES 數據結構。
032 //在mpegts_read_header 函數的過程中發現了PES 的pid,但
033 //是不建立對應的流,只是分析PSI 信息。
034 //相關的代碼見handle_packet 函數的下面的代碼:
035 //tss = ts->pids[pid];
036 //if (ts->auto_guess && tss == NULL && is_start) {
037 // add_pes_stream(ts, pid, -1, 0);
038 // tss = ts->pids[pid];
039 //}
040 ts->auto_guess = 0;
041 if (s->iformat == &mpegts_demuxer) {
042 /* normal demux */
043 /* first do a scaning to get all the services */
044 url_fseek(pb, pos, SEEK_SET);
045 //掛載解析SDT 表的回調函數ts->pids 變量上, //這樣在handle_packet 函數中根據對應的pid 找對應處理回調函數。
046 mpegts_open_section_filter(ts, SDT_PID, sdt_cb, ts, 1);
047 //同上,只是掛上PAT 表解析的回調函數
048 mpegts_open_section_filter(ts, PAT_PID, pat_cb, ts, 1);
049 //探測段流,便於檢測出SDT,PAT,PMT 表
050 handle_packets(ts, s->probesize);
051 /* if could not find service, enable auto_guess */
052  
053 //打開add pes stream 的標誌,這樣在handle_packet 函數中發現了pes 的
054 //pid,就會自動建立該pes 的stream。
055 ts->auto_guess = 1;
056 dprintf(ts->stream, "tuning done\n" );
057 s->ctx_flags |= AVFMTCTX_NOHEADER;
058 } else {
059 ......
060 }
061 //恢復檢測前的位置。
062 url_fseek(pb, pos, SEEK_SET);
063 return 0;
064 fail:
065 return -1;
066 }
067 </pre>
068 <pre></pre>
069 <p> </p>
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,
074 int check_crc)
075 {
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])
080 return NULL;
081 //給filter 分配空間,掛載MpegTSContext 的pids 上
082 //就是該實例
083 filter = av_mallocz( sizeof (MpegTSFilter));
084 if (!filter)
085 return NULL;
086 //掛載filter 實例
087 ts->pids[pid] = filter;
088 //設置filter 相關的參數,因為業務信息表的分析的單是段,
089 //所以該filter 的類型是MPEGTS_SECTION
090 filter->type = MPEGTS_SECTION;
091  
092 //設置pid
093 filter->pid = pid;
094 filter->last_cc = -1;
095 //設置filter 回調處理函數
096 sec = &filter->u.section_filter;
097 sec->section_cb = section_cb;
098 sec->opaque = opaque;
099 //分配段數據處理的緩衝區,調用handle_packet 函數後會調用
100 //write_section_data 將ts 包中的業務信息表的數據存儲在這兒,
101 //直個段收集完成才交付上面註冊的回調函數處理。
102 sec->section_buf = av_malloc(MAX_SECTION_SIZE);
103 sec->check_crc = check_crc;
104 if (!sec->section_buf) {
105 av_free(filter);
106 return NULL;
107 }
108 return filter;
109 }
110  
111 </pre>
112 <p><br>
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>
118 是表示處理的包的個數為0。 </p>
119 <pre class = "cpp" name= "code" > static int handle_packets(MpegTSContext *ts, int nb_packets)
120 {
121 AVFormatContext *s = ts->stream;
122 ByteIOContext *pb = s->pb;
123 uint8_t packet[TS_PACKET_SIZE];
124 int packet_num, ret;
125 //該變量指示次handle_packets 處理的結束。
126 //在mpegts_read_packet 被調用的時候,如果發現完個PES 的包,則
127 // ts->stop_parse = 1 ,則當前分析結束。
128 ts->stop_parse = 0;
129 packet_num = 0;
130 for (;;) {
131 if (ts->stop_parse>0)
132 break ;
133 packet_num++;
134 if (nb_packets != 0 && packet_num >= nb_packets)
135 break ;
136 //讀取個ts 包,通常是188bytes
137 ret = read_packet(pb, packet, ts->raw_packet_size);
138 if (ret != 0)
139 return ret;
140 handle_packet(ts, packet);
141 }
142 return 0;
143 }
144  
145 </pre>
146 <p><br>
147 </p>
148 <p>7 handle_packet 函數分析<br>
149 可以說handle_packet 是mpegts.c 代碼的核心,所有的其他代碼都是為<br>
150 這個函數準備的。 <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)
154 {
155 AVFormatContext *s = ts->stream;
156 MpegTSFilter *tss;
157 int len, pid, cc, cc_ok, afc, is_start;
158 const uint8_t *p, *p_end;
159 int64_t pos;
160 //從TS 包獲得包的PID。
161 pid = AV_RB16(packet + 1) & 0x1fff;
162 if (pid && discard_pid(ts, pid))
163 return 0;
164 is_start = packet[1] & 0x40;
165 tss = ts->pids[pid];
166 //ts->auto_guess 在mpegts_read_header 函數中被設置為0,
167 //也就是說在ts 檢測過程中是不建立pes stream 的。
168 if (ts->auto_guess && tss == NULL && is_start) {
169 add_pes_stream(ts, pid, -1, 0);
170 tss = ts->pids[pid];
171 }
172 //mpegts_read_header 函數調用handle_packet 函數只是處理TS 流的
173 //業務信息,因為並沒有為對應的PES 建立tss,所以tss 為空,直接返回。
174 if (!tss)
175 return 0;
176 /* continuity check (currently not used) */
177 cc = (packet[3] & 0xf);
178 cc_ok = (tss->last_cc < 0) || ((((tss->last_cc + 1) & 0x0f) == cc));
179 tss->last_cc = cc;
180 /* skip adaptation field */
181 afc = (packet[3] >> 4) & 3;
182 p = packet + 4;
183 if (afc == 0) /* reserved value */
184 return 0;
185 if (afc == 2) /* adaptation field only */
186 return 0;
187 if (afc == 3) {
188 /* skip adapation field */
189 p += p[0] + 1;
190 }
191 /* if past the end of packet, ignore */
192 p_end = packet + TS_PACKET_SIZE;
193 if (p >= p_end)
194 return 0;
195 pos = url_ftell(ts->stream->pb);
196 ts->pos47= pos % ts->raw_packet_size;
197 if (tss->type == MPEGTS_SECTION) {
198 //表示當前的TS 包包含個新的業務信息段
199 if (is_start) {
200 //獲取pointer field 字段,
201 //新的段從pointer field 字段指示的位置開始
202 len = *p++;
203 if (p + len > p_end)
204 return 0;
205 if (len && cc_ok) {
206 //這個時候TS 的負載有兩個部分構成:
207 //1)從TS 負載開始pointer field 字段指示的位置;
208 //2)從pointer field 字段指示的位置TS 包結束
209  
210 //1)位置代表的是上個段的末尾部分。
211 //2)位置代表的新的段開始的部分。
212 //下面的代碼是保存上個段末尾部分數據,也就是
213 //1)位置的數據。
214 write_section_data(s, tss, p, len, 0);
215 /* check whether filter has been closed */
216 if (!ts->pids[pid])
217 return 0;
218 }
219 p += len;
220 //保留新的段數據,也就是2)位置的數據。
221 if (p < p_end) {
222 write_section_data(s, tss, p, p_end - p, 1);
223 }
224 } else {
225 //保存段中間的數據。
226 if (cc_ok) {
227 write_section_data(s, tss, p, p_end - p, 0);
228 }
229 }
230 } else {
231 int ret;
232 //正常的PES 數據的處理
233 // Note: The position here points actually behind the current packet.
234 if ((ret = tss->u.pes_filter.pes_cb(tss, p, p_end - p, is_start,
235 pos - ts->raw_packet_size)) < 0)
236 return ret;
237 }
238 return 0;
239 }
240  
241 </pre>
242 <p><br>
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)
247 {
248 MpegTSSectionFilter *tss = &tss1->u.section_filter;
249 int len;
250 //buf 中是個段的開始部分。
251 if (is_start) {
252 //將內容複製tss->section_buf 中保存
253 memcpy (tss->section_buf, buf, buf_size);
254 //tss->section_index 段索引。
255 tss->section_index = buf_size;
256 //段的長度,現在還不知道,設置為-1
257 tss->section_h_size = -1;
258 //是否達段的結尾。
259 tss->end_of_section_reached = 0;
260 } else {
261 //buf 中是段中間的數據。
262 if (tss->end_of_section_reached)
263 return ;
264 len = 4096 - tss->section_index;
265 if (buf_size < len)
266 len = buf_size;
267 memcpy (tss->section_buf + tss->section_index, buf, len);
268 tss->section_index += len;
269 }
270 //如果條件滿足,計算段的長度
271 if (tss->section_h_size == -1 && tss->section_index >= 3) {
272 len = (AV_RB16(tss->section_buf + 1) & 0xfff) + 3;
273 if (len > 4096)
274 return ;
275 tss->section_h_size = len;
276 }
277 //判斷段數據是否收集完畢,如果收集完畢,調用相應的回調函數處理該段。
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);
284 }
285 }
286  
 
arrow
arrow
    全站熱搜

    BB 發表在 痞客邦 留言(0) 人氣()