FFMpeg對MPEG2 TS流解碼的流程分析

helloxchen發表於2010-11-16

FFMpeg對MPEG2 TS流解碼的流程分析

1.引子
gnxzzz廣告都打出去了,不能沒有反應.現在寫東西很少了,一是年紀大了,好奇心少了
許多,;二則是這幾天又犯了扁桃體炎,每天只要是快睡覺或剛起床,頭暈腦漲,不過功
課還是的做的,是吧:)

2. 從簡單說起
說道具體的音訊或者影片格式,一上來就是理論,那是國內混資歷的所謂教授的做為,對
於我們,不合適,還是用自己的方式理解這些晦澀不已的理論吧。

其實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協議中的一些概念,為理解程式碼做好功課:

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個步驟
中的編碼而已(有些裝置可能會包含前面的取樣和量化)。關於影片編碼的基本理論,還是
請參考其它的資料。

PES(Packetized Elementary Stream):
wiki上說“allows an Elementary stream to be divided into packets”
其實可以理解成,把一個源源不斷的資料(音訊,影片或者其他)流,打斷成一段一段,以
便處理.

TS(Transport Stream):
PS(Program Stream):
這兩個上面已經有所提及,後面會詳細分析TS,我對PS格式興趣不大.

3. 步入正題
才進入正題,恩,看來閒話太多了:(,直接看Code.

前面說過,TS是一種傳輸協議,因此,對應到FFmpeg,可以認為他是一種封裝格式。因此
,對應的程式碼應該先去libavformat裡面找,很容易找到,就是mpegts.c:)

還是逐步看過來:
[libavformat/utils.c]
int av_open_input_file(AVFormatContext **ic_ptr, const char *filename,
AVInputFormat *fmt,
int buf_size,
AVFormatParameters *ap)
{
int err, probe_size;
AVProbeData probe_data, *pd = &probe_data;
ByteIOContext *pb = NULL;

pd->filename = "";
if (filename)
pd->filename = filename;
pd->buf = NULL;
pd->buf_size = 0;

#########################################################################
【1】這段程式碼其實是為了針對不需要Open檔案的容器Format的探測,其實就是使用
AVFMT_NOFILE標記的容器格式單獨處理,現在只有使用了該標記的Demuxer很少,
只有image2_demuxer,rtsp_demuxer,因此我們分析TS時候可以不考慮這部分
#########################################################################
if (!fmt) {
/* guess format if no file can be opened */
fmt = av_probe_input_format(pd, 0);
}

/* Do not open file if the format does not need it. XXX: specific
hack needed to handle RTSP/TCP */
if (!fmt || !(fmt->flags & AVFMT_NOFILE)) {
/* if no file needed do not try to open one */
#####################################################################
【2】這個函式似乎很好理解,無非是帶緩衝的IO的封裝,不過我們既然到此了
,不妨跟蹤下去,看看別人對帶緩衝的IO操作封裝的實現:)
#####################################################################
if ((err=url_fopen(&pb, filename, URL_RDONLY)) < 0) {
goto fail;
}
if (buf_size > 0) {
url_setbufsize(pb, buf_size);
}

for(probe_size= PROBE_BUF_MIN; probe_size<=PROBE_BUF_MAX && !fmt; probe_size<<=1){
int score= probe_size < PROBE_BUF_MAX ? AVPROBE_SCORE_MAX/4 : 0;
/* read probe data */
pd->buf= av_realloc(pd->buf, probe_size + AVPROBE_PADDING_SIZE);
##################################################################
【3】真正將檔案讀入到pd的buffer的地方,實際上最終呼叫FILE protocol
的file_read(),將內容讀入到pd的buf,具體程式碼如果有興趣可以自己跟蹤
##################################################################
pd->buf_size = get_buffer(pb, pd->buf, probe_size);
memset(pd->buf+pd->buf_size, 0, AVPROBE_PADDING_SIZE);
if (url_fseek(pb, 0, SEEK_SET) < 0) {
url_fclose(pb);
if (url_fopen(&pb, filename, URL_RDONLY) < 0) {
pb = NULL;
err = AVERROR(EIO);
goto fail;
}
}
##################################################################
【4】此時的pd已經有了需要分析的原始檔案,只需要查詢相應容器format
的Tag比較,以判斷讀入的究竟為什麼容器格式,這裡
##################################################################
/* guess file format */
fmt = av_probe_input_format2(pd, 1, &score);
}
av_freep(&pd->buf);
}

/* if still no format found, error */
if (!fmt) {
err = AVERROR_NOFMT;
goto fail;
}

/* check filename in case an image number is expected */
if (fmt->flags & AVFMT_NEEDNUMBER) {
if (!av_filename_number_test(filename)) {
err = AVERROR_NUMEXPECTED;
goto fail;
}
}
err = av_open_input_stream(ic_ptr, pb, filename, fmt, ap);
if (err)
goto fail;
return 0;
fail:
av_freep(&pd->buf);
if (pb)
url_fclose(pb);
*ic_ptr = NULL;
return err;

}

【2】帶緩衝IO的封裝的實現
[liavformat/aviobuf.c]
int url_fopen(ByteIOContext **s, const char *filename, int flags)
{
URLContext *h;
int err;

err = url_open(&h, filename, flags);
if (err < 0)
return err;
err = url_fdopen(s, h);
if (err < 0) {
url_close(h);
return err;
}
return 0;
}

可以看到,下面的這個函式,先查詢是否是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,格式,它都以FILE protocol
來處理。

[liavformat/avio.c]
int url_open(URLContext **puc, const char *filename, int flags)
{
URLProtocol *up;
const char *p;
char proto_str[128], *q;

p = filename;
q = proto_str;
while (*p != '' && *p != ':') {
/* protocols can only contain alphabetic chars */
if (!isalpha(*p))
goto file_proto;
if ((q - proto_str) < sizeof(proto_str) - 1)
*q++ = *p;
p++;
}
/* if the protocol has length 1, we consider it is a dos drive */
if (*p == '' || (q - proto_str) <= 1) {
file_proto:
strcpy(proto_str, "file");
} else {
*q = '';
}

up = first_protocol;
while (up != NULL) {
if (!strcmp(proto_str, up->name))
#################################################################
很顯然,此時已經知道up,filename,flags
#################################################################
return url_open_protocol (puc, up, filename, flags);
up = up->next;
}
*puc = NULL;
return AVERROR(ENOENT);
}

[libavformat/avio.c]
int url_open_protocol (URLContext **puc, struct URLProtocol *up,
const char *filename, int flags)
{
URLContext *uc;
int err;

##########################################################################
【a】? 為什麼這樣分配空間
##########################################################################
uc = av_malloc(sizeof(URLContext) + strlen(filename) + 1);
if (!uc) {
err = AVERROR(ENOMEM);
goto fail;
}
#if LIBAVFORMAT_VERSION_MAJOR >= 53
uc->av_class = &urlcontext_class;
#endif
##########################################################################
【b】? 這樣的用意又是為什麼
##########################################################################
uc->filename = (char *) &uc[1];
strcpy(uc->filename, filename);
uc->prot = up;
uc->flags = flags;
uc->is_streamed = 0; /* default = not streamed */
uc->max_packet_size = 0; /* default: stream file */
err = up->url_open(uc, filename, flags);
if (err < 0) {
av_free(uc);
*puc = NULL;
return err;
}

//We must be carefull here as url_seek() could be slow, for example for
//http
if( (flags & (URL_WRONLY | URL_RDWR))
|| !strcmp(up->name, "file"))
if(!uc->is_streamed && url_seek(uc, 0, SEEK_SET) < 0)
uc->is_streamed= 1;
*puc = uc;
return 0;
fail:
*puc = NULL;
return err;
}

上面這個函式不難理解,但有些地方頗值得玩味,比如,上面給出問號的地方,你明白為
什麼這樣Coding麼:)

很顯然,此時up->url_open()實際上呼叫的是 file_open()[libavformat/file.c],看完
這個函式,對上面的記憶體分配,是否恍然大悟:)

上面只是分析了url_open(),還沒有分析url_fdopen(s, h);這部分程式碼,也留給有好奇
心的你了:)

恩,為了追蹤這個流程,走得有些遠,但不是全然無用:)

終於來到了【4】,我們來看MPEG TS格式的偵測過程,這其實才是我們今天的主角

4. MPEG TS格式的探測過程
[liavformat/mpegts.c]
static int mpegts_probe(AVProbeData *p)
{
#if 1
const int size= p->buf_size;
int score, fec_score, dvhs_score;
#define CHECK_COUNT 10

if (size < (TS_FEC_PACKET_SIZE * CHECK_COUNT))
return -1;

score = analyze(p->buf, TS_PACKET_SIZE *CHECK_COUNT, TS_PACKET_SIZE, NULL);
dvhs_score = analyze(p->buf, TS_DVHS_PACKET_SIZE *CHECK_COUNT, TS_DVHS_PACKET_SIZE, NULL);
fec_score= analyze(p->buf, TS_FEC_PACKET_SIZE*CHECK_COUNT, TS_FEC_PACKET_SIZE, NULL);
// av_log(NULL, AV_LOG_DEBUG, "score: %d, dvhs_score: %d, fec_score: %d n", score, dvhs_score, fec_score);

// we need a clear definition for the returned score otherwise things will become messy sooner or later
if (score > fec_score && score > dvhs_score && score > 6) return AVPROBE_SCORE_MAX + score - CHECK_COUNT;
else if(dvhs_score > score && dvhs_score > fec_score && dvhs_score > 6) return AVPROBE_SCORE_MAX + dvhs_score - CHECK_COUNT;
else if( fec_score > 6) return AVPROBE_SCORE_MAX + fec_score - CHECK_COUNT;
else return -1;
#else
/* only use the extension for safer guess */
if (match_ext(p->filename, "ts"))
return AVPROBE_SCORE_MAX;
else
return 0;
#endif
}
之所以會出現3種格式,主要原因是:
TS標準是188Bytes,而小日本自己又弄了一個192Bytes的DVH-S格式,第三種的 204Bytes則
是在188Bytes的基礎上,加上16Bytes的FEC(前向糾錯).

static int analyze(const uint8_t *buf, int size, int packet_size, int *index)
{
int stat[packet_size];
int i;
int x=0;
int best_score=0;

memset(stat, 0, packet_size*sizeof(int));

##########################################################################
由於查詢的特定格式至少3個Bytes,因此,至少最後3個Bytes不用查詢
##########################################################################
for(x=i=0; i######################################################################
參看後面的協議說明
######################################################################
if(buf[i] == 0x47 && !(buf[i+1] & 0x80) && (buf[i+3] & 0x30)){
stat[x]++;
if(stat[x] > best_score){
best_score= stat[x];
if(index) *index= x;
}
}

x++;
if(x == packet_size) x= 0;
}

return best_score;
}

這個函式簡單說來,是在size大小的buf中,尋找滿足特定格式,長度為packet_size的
packet的個數,顯然,返回的值越大越可能是相應的格式(188/192/204)

其中的這個特定格式,其實就是協議的規定格式:

Syntax

No. of bits

Mnemonic

transport_packet(){

sync_byte

8

bslbf

transport_error_indicator

1

bslbf

payload_unit_start_indicator

1

bslbf

transport_priority

1

bslbf

PID

13

uimsbf

transport_scrambling_control

2

bslbf

adaptation_field_control

2

bslbf

continuity_counter

4

uimsbf

if(adaptation_field_control=='10' || adaptation_field_control=='11'){

adaptation_field()

}

if(adaptation_field_control=='01' || adaptation_field_control=='11') {

for (i=0;i

data_byte

8

bslbf

}

}

}


其中的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的過程,待續......

參考:
1.

2.ISO/IEC-13818-1

http://hi.baidu.com/bohryan/blog/item/9fac3a920685fa86a877a426.html[@more@]

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/24790158/viewspace-1041569/,如需轉載,請註明出處,否則將追究法律責任。

相關文章