檔案參考
http://www.diybl.com/course/6_system/linux/Linuxjs/2008924/145267.html
FFMpeg MPEG2 TS 串流解碼的流程分析
FFMpeg對MPEG2 TS流解碼的流程分析 1.引言 每天只要是快睡覺或剛起床,頭暈腦漲,不過功課還是要做的,是吧。 2.從簡單說起 說到具體的音頻或者視頻格式,一上來就是理論,那是國內混資歷的所謂教授的做為, 對於我們,不合適,還是用自己的模式理解這些晦澀不已的理論吧。 其實MPEG2是一種協議,至少已經成為ISO標準的就有以下幾部分: ISO/IEC-13818-1︰系統部分; 我不是很想說實際的音視頻編碼格式,畢竟協議已經很清楚了, 我主要想說說這些部分怎麼組合起來在實際應用中工作的。 第一部分(系統部分)很重要,是構成以MPEG2為基礎的應用的基礎。 簡單解釋一下: 比如DVD實際上是以系統部分定義的PS流為基礎,加上版權管理等其他技術構成的。 而我們的故事主角,則是另外一種流格式,TS流,它在現階段最大的應用是在數位電視節目的傳輸與存儲上。 因此你可以理解TS實際上是一種傳輸協議,與實際傳輸的負載關係不大,只是在TS中傳輸了音頻,視頻或者其他數據。 先說一下為什麼會有這兩種格式的出現。 PS適用於沒有損耗的環境下面存儲,而TS則適用於可能出現損耗或者錯誤的各種物理網路環境。 比如你在公交上看到的電視,很有可能就是基於TS的DVB-T的應用 我們再來看MPEG2協議中的一些概念,為理解代碼做好功課: wiki上說 "An elementary stream (ES) is defined by MPEG communication protocol is usually the output of an audio or video encoder" 恩,很簡單吧,就是編碼器編出的一組數據,可能是音頻的,視頻的,或者其他數據。 說到這,其實可以對編碼器的流程思考一下,無非是執行:採樣,量化,編碼這3個步驟中的編碼而已(有些設備可能會包含前面的採樣和量化)。 關於視頻編碼的基本理論,還是請參考其它的資料。 wiki上說"allows an Elementary stream to be divided into packets" 其實可以理解成,把一個源源不斷的數據(音頻,視頻或者其他)流,打斷成一段一段,以便處理。 這兩個上面已經有所提及,後面會詳細分析TS,我對PS格式興趣不大。 進入正題,直接看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) ByteIOContext *pb = NULL; if( !fmt ) { hack needed to handle RTSP/TCP */ if( !fmt !(fmt->flags & AVFMT_NOFILE) ) { if( (err=url_fopen(&pb, filename, URL_RDONLY)) < 0 ) { if( buf_size > 0 ) { for( probe_size= PROBE_BUF_MIN; probe_size<=PROBE_BUF_MAX && !fmt; probe_size<<=1 ) { /* read probe data */ pd->buf_size = get_buffer(pb, pd->buf, probe_size); /* guess file format */ /* if still no format found, error */ /* check filename in case an image number is expected */ err = av_open_input_stream(ic_ptr, pb, filename, fmt, ap); if( err ) { return 0; fail: if( pb ) { *ic_ptr = NULL; return err; if( err < 0 ) err = url_fdopen(s, h); if( err < 0 ) { return 0; 如果檔案名不符合,則預設是FILE protocol格式。 很顯然,這裡protocol判斷是以URL的模式判讀的,因此基本上所有的IO界面函數都是url_xxx的形式。 在這也可以看到,FFmpeg支援的protocol有︰ /* protocols */ 而大部分情況下,如果你不指明類似 file://xxx,http: //xxx 格式,它都以 FILE protocol 來處理。 p = filename; while( *p != '\0' && *p != ':' ) { /* if the protocol has length 1, we consider it is a dos drive */ up = first_protocol; while( up != NULL ) { return url_open_protocol (puc, up, filename, flags); up = up->next; *puc = NULL; uc = av_malloc(sizeof(URLContext) + strlen(filename) + 1); if( !uc ) { #if LIBAVFORMAT_VERSION_MAJOR >= 53 uc->filename = (char *) &uc[1]; if( err < 0 ) { if( (flags & (URL_WRONLY URL_RDWR)) || !strcmp(up->name, "file") ) *puc = uc; fail: return err; 上面這個函數不難理解,但有些地方頗值得玩味,比如上面給出問號的地方, 你明白為什麼這樣Coding嗎? 很顯然,此時up->url_open()實際上調用的是file_open()[libavformat/file.c], 看完這個函數,對上面的內存分發,是否恍然大悟。 上面只是分析了url_open(),還沒有分析url_fdopen(s, h); 這部分代碼,也留給有好奇心的你了。 恩,為了追蹤這個流程,走得有些遠,但不是全然無用。 終於來到了【4】,我們來看MPEG TS格式的偵測過程,這其實才是我們今天的主角。 [liavformat/mpegts.c] if( score > fec_score && score > dvhs_score && score > 6 ) { 之所以會出現3種格式,主要原因是︰ TS標準是188Bytes,而小日本自己又弄了一個192Bytes的DVH-S格式, 第三種的204Bytes則是在188Bytes的基礎上,加上16Bytes的FEC(前向糾錯)。 static int analyze(const uint8_t *buf, int size, int packet_size, int *index) for( x=i=0; i<size-3; i++ ) { if( buf[i] == 0x47 && !(buf[i+1] & 0x80) && (buf[i+3] & 0x30) ) { 這個函數簡單說來,是在size大小的buf中,尋找滿足特定格式, 長度為packet_size的packet的個數,顯然返回的值越大,越可能是相應的格式(188/192/204)。 其中的這個特定格式,其實就是協議的規定格式: Syntax No. of bits Mnemonic transport_packet() 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的偵測過程。
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︰系統解碼實時界面;
ES(Elementary Stream):
PES(Packetized Elementary Stream):
TS(Transport Stream):
PS(Program Stream):
3.步入正題
{
int err, probe_size;
AVProbeData probe_data, *pd = &probe_data;
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時候可以不考慮這部分
#########################################################################
/* 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
/* if no file needed do not try to open one */
#####################################################################
【2】這個函數似乎很好理解,無非是帶緩衝的IO的封裝,不過我們既然到此了
,不妨跟蹤下去,看看別人對帶緩沖的IO操作封裝的實現
#####################################################################
goto fail;
}
url_setbufsize(pb, buf_size);
}
int score= probe_size < PROBE_BUF_MAX ? AVPROBE_SCORE_MAX/4 : 0;
pd->buf= av_realloc(pd->buf, probe_size + AVPROBE_PADDING_SIZE);
##################################################################
【3】真正將檔案讀入到pd的buffer的地方,實際上最終呼叫 FILE protocol
的file_read(),將內容讀入到pd的buf,具體代碼如果有興趣可以自己跟蹤
##################################################################
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比較,以判斷這裡讀入的究竟為什麼容器格式
##################################################################
fmt = av_probe_input_format2(pd, 1, &score);
}
av_freep(&pd->buf);
}
if( !fmt ) {
err = AVERROR_NOFMT;
goto fail;
}
if( fmt->flags & AVFMT_NEEDNUMBER ) {
if( !av_filename_number_test(filename) ) {
err = AVERROR_NUMEXPECTED;
goto fail;
}
}
goto fail;
}
av_freep(&pd->buf);
url_fclose(pb);
}
}
【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);
return err;
url_close(h);
return err;
}
}
可以看到下面的這個函數,先查找是否是FFmpeg支援的protocol的格式,
REGISTER_PROTOCOL (FILE, file);
REGISTER_PROTOCOL (HTTP, http);
REGISTER_PROTOCOL (PIPE, pipe);
REGISTER_PROTOCOL (RTP, rtp);
REGISTER_PROTOCOL (TCP, tcp);
REGISTER_PROTOCOL (UDP, udp);
[liavformat/avio.c]
int url_open(URLContext **puc, const char *filename, int flags)
{
URLProtocol *up;
const char *p;
char proto_str[128], *q;
q = proto_str;
/* protocols can only contain alphabetic chars */
if( !isalpha(*p) )
goto file_proto;
if( (q - proto_str) < sizeof(proto_str) - 1 )
*q++ = *p;
p++;
}
if( *p == '\0' || (q - proto_str) <= 1) {
file_proto:
strcpy(proto_str, "file");
} else {
*q = '\0';
}
if( !strcmp(proto_str, up->name) ) {
#################################################################
很顯然,此時已經知道up,filename,flags
#################################################################
}
}
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】? 為什麼這樣分發空間
##########################################################################
err = AVERROR(ENOMEM);
goto fail;
}
uc->av_class = &urlcontext_class;
#endif
##########################################################################
【b】? 這樣的用意又是為什麼
##########################################################################
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);
av_free(uc);
*puc = NULL;
return err;
}
//We must be carefull here as url_seek() could be slow, for example for http
if( !uc->is_streamed && url_seek(uc, 0, SEEK_SET) < 0 )
uc->is_streamed= 1;
return 0;
*puc = NULL;
}
4. MPEG TS格式的探測過程
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
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
}
{
int stat[packet_size];
int i;
int x=0;
int best_score=0;
memset(stat, 0, packet_size*sizeof(int));
##########################################################################
由於查找的特定格式至少3個Bytes,因此,至少最後3個Bytes不用查找
##########################################################################
######################################################################
參看後面的協議說明
######################################################################
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;
}
{
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();
}
for (i=0;i<N;i++) {
data_byte 8 bslbf
};
}
}
參考:
1.http://en.wikipedia.org/wiki/MPEG_transport_stream
2.ISO/IEC-13818-1
留言列表