FFMpeg對MPEG2 TS流解碼的流程分析[2]
5.漸入佳境
恩,前面的基礎因該已近夠了,有點像手剝洋蔥頭的感覺,我們來看看針對MPEG TS的相
應解析過程
我們後面的程式碼,主要集中在[libavformat/mpegts.c]裡面,毛爺爺說:集中優勢兵力打
圍殲,恩,開始吧,螞蟻啃骨頭。
static int mpegts_read_header(AVFormatContext *s,
AVFormatParameters *ap)
{
MpegTSContext *ts = s->priv_data;
ByteIOContext *pb = s->pb;
uint8_t buf[1024];
int len;
int64_t pos;
......
/* read the first 1024 bytes to get packet size */
#####################################################################
【1】有了前面分析緩衝IO的經歷,下面的程式碼就不是什麼問題了:)
#####################################################################
pos = url_ftell(pb);
len = get_buffer(pb, buf, sizeof(buf));
if (len != sizeof(buf))
goto fail;
#####################################################################
【2】前面偵測檔案格式時候其實已經知道TS包的大小了,這裡又偵測一次,其實
有些多餘,估計是因為解碼框架的原因,已近偵測的包大小沒能從前面被帶過來,
可見框架雖好,卻也會帶來或多或少的一些不利影響
#####################################################################
ts->raw_packet_size = get_packet_size(buf, sizeof(buf));
if (ts->raw_packet_size <= 0)
goto fail;
ts->stream = s;
ts->auto_guess = 0;
if (s->iformat == &mpegts_demuxer) {
/* normal demux */
/* first do a scaning to get all the services */
url_fseek(pb, pos, SEEK_SET);
##################################################################
【3】
##################################################################
mpegts_scan_sdt(ts);
##################################################################
【4】
##################################################################
mpegts_set_service(ts);
##################################################################
【5】
##################################################################
handle_packets(ts, s->probesize);
/* if could not find service, enable auto_guess */
ts->auto_guess = 1;
#ifdef DEBUG_SI
av_log(ts->stream, AV_LOG_DEBUG, "tuning donen");
#endif
s->ctx_flags |= AVFMTCTX_NOHEADER;
} else {
......
}
url_fseek(pb, pos, SEEK_SET);
return 0;
fail:
return -1;
}
這裡簡單說一下MpegTSContext *ts,從上面可以看到,其實這是為了解碼不同容器格式
所使用的私有資料,只有在相應的諸如mpegts.c 檔案才可以使用的,這樣,增加了這個庫
的模組化,而模組化的最大好處,則在於把問題集中到了一個很小的有限區域裡面,如果
你自己構造程式時候,不妨多參考其基本思想--這樣的化,你之後的程式碼,還有你之後的
生活,都將輕鬆許多。
【3】【4】其實呼叫的是同一個函式:mpegts_open_section_filter()
我們來看看意欲何為。
static
MpegTSFilter *mpegts_open_section_filter(MpegTSContext *ts, unsigned int pid,
SectionCallback *section_cb,
void *opaque,
int check_crc)
{
MpegTSFilter *filter;
MpegTSSectionFilter *sec;
#ifdef DEBUG_SI
av_log(ts->stream, AV_LOG_DEBUG, "Filter: pid=0x%xn", pid);
#endif
if (pid >= NB_PID_MAX || ts->pids[pid])
return NULL;
filter = av_mallocz(sizeof(MpegTSFilter));
if (!filter)
return NULL;
ts->pids[pid] = filter;
filter->type = MPEGTS_SECTION;
filter->pid = pid;
filter->last_cc = -1;
sec = &filter->u.section_filter;
sec->section_cb = section_cb;
sec->opaque = opaque;
sec->section_buf = av_malloc(MAX_SECTION_SIZE);
sec->check_crc = check_crc;
if (!sec->section_buf) {
av_free(filter);
return NULL;
}
return filter;
}
要完全明白這部分程式碼,其實需要分析作者對資料結構的定義:
依次為:
struct MpegTSContext;
|
V
struct MpegTSFilter;
|
V
+---------------+---------------+
| |
V V
MpegTSPESFilter MpegTSSectionFilter
其實很簡單,就是struct MpegTSContext;中有NB_PID_MAX(8192)個TS的Filter,而每個
struct MpegTSFilter可能是PES的Filter或者Section的Filter。
我們先說為什麼是8192,在前面的分析中:
給出過TS的語法結構:
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
}
}
}
而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()才會是我們真
正因該關注的地方了:)
這個函式很重要,我們貼出程式碼,以備分析:
/* handle one TS packet */
static void handle_packet(MpegTSContext *ts, const uint8_t *packet)
{
AVFormatContext *s = ts->stream;
MpegTSFilter *tss;
int len, pid, cc, cc_ok, afc, is_start;
const uint8_t *p, *p_end;
##########################################################
獲取該包的PID
##########################################################
pid = AV_RB16(packet + 1) & 0x1fff;
if(pid && discard_pid(ts, pid))
return;
##########################################################
是否是PES或者Section的開頭(payload_unit_start_indicator)
##########################################################
is_start = packet[1] & 0x40;
tss = ts->pids[pid];
##########################################################
ts->auto_guess此時為0,因此不考慮下面的程式碼
##########################################################
if (ts->auto_guess && tss == NULL && is_start) {
add_pes_stream(ts, pid, -1, 0);
tss = ts->pids[pid];
}
if (!tss)
return;
##########################################################
程式碼說的很清楚,雖然檢查,但不利用檢查的結果
##########################################################
/* continuity check (currently not used) */
cc = (packet[3] & 0xf);
cc_ok = (tss->last_cc < 0) || ((((tss->last_cc + 1) & 0x0f) == cc));
tss->last_cc = cc;
##########################################################
跳到adaptation_field_control
##########################################################
/* skip adaptation field */
afc = (packet[3] >> 4) & 3;
p = packet + 4;
if (afc == 0) /* reserved value */
return;
if (afc == 2) /* adaptation field only */
return;
if (afc == 3) {
/* skip adapation field */
p += p[0] + 1;
}
##########################################################
p已近到達TS包中的有效負載的地方
##########################################################
/* if past the end of packet, ignore */
p_end = packet + TS_PACKET_SIZE;
if (p >= p_end)
return;
ts->pos47= url_ftell(ts->stream->pb) % ts->raw_packet_size;
if (tss->type == MPEGTS_SECTION) {
if (is_start) {
#############################################################
針對Section,符合部分第一個位元組為pointer field,該欄位如果為0,
則表示後面緊跟著的是Section的開頭,否則是某Section的End部分和
另一Section的開頭,因此,這裡的流程實際上由兩個值is_start
(payload_unit_start_indicator)和len(pointer field)一起來決定
#############################################################
/* pointer field present */
len = *p++;
if (p + len > p_end)
return;
if (len && cc_ok) {
########################################################
1).is_start == 1
len > 0
負載部分由A Section的End部分和B Section的Start組成,把A的
End部分寫入
########################################################
/* write remaining section bytes */
write_section_data(s, tss,
p, len, 0);
/* check whether filter has been closed */
if (!ts->pids[pid])
return;
}
p += len;
if (p < p_end) {
########################################################
2).is_start == 1
len > 0
負載部分由A Section的End部分和B Section的Start組成,把B的
Start部分寫入
或者:
3).
is_start == 1
len == 0
負載部分僅是一個Section的Start部分,將其寫入
########################################################
write_section_data(s, tss,
p, p_end - p, 1);
}
} else {
if (cc_ok) {
########################################################
4).is_start == 0
負載部分僅是一個Section的中間部分部分,將其寫入
########################################################
write_section_data(s, tss,
p, p_end - p, 0);
}
}
} else {
##########################################################
如果是PES型別,直接呼叫其Callback,但顯然,只有Section部分
解析完成後才可能解析PES
##########################################################
tss->u.pes_filter.pes_cb(tss,
p, p_end - p, is_start);
}
}
write_section_data()函式則反覆收集buffer中的資料,指導完成相關Section的重組過
程,然後呼叫之前註冊的兩個section_cb:
後面我們將分析之前掛在的兩個section_cb,待續......
http://hi.baidu.com/bohryan/blog/item/95b49bc65fcdcaa28326ac20.html[@more@]
恩,前面的基礎因該已近夠了,有點像手剝洋蔥頭的感覺,我們來看看針對MPEG TS的相
應解析過程
我們後面的程式碼,主要集中在[libavformat/mpegts.c]裡面,毛爺爺說:集中優勢兵力打
圍殲,恩,開始吧,螞蟻啃骨頭。
static int mpegts_read_header(AVFormatContext *s,
AVFormatParameters *ap)
{
MpegTSContext *ts = s->priv_data;
ByteIOContext *pb = s->pb;
uint8_t buf[1024];
int len;
int64_t pos;
......
/* read the first 1024 bytes to get packet size */
#####################################################################
【1】有了前面分析緩衝IO的經歷,下面的程式碼就不是什麼問題了:)
#####################################################################
pos = url_ftell(pb);
len = get_buffer(pb, buf, sizeof(buf));
if (len != sizeof(buf))
goto fail;
#####################################################################
【2】前面偵測檔案格式時候其實已經知道TS包的大小了,這裡又偵測一次,其實
有些多餘,估計是因為解碼框架的原因,已近偵測的包大小沒能從前面被帶過來,
可見框架雖好,卻也會帶來或多或少的一些不利影響
#####################################################################
ts->raw_packet_size = get_packet_size(buf, sizeof(buf));
if (ts->raw_packet_size <= 0)
goto fail;
ts->stream = s;
ts->auto_guess = 0;
if (s->iformat == &mpegts_demuxer) {
/* normal demux */
/* first do a scaning to get all the services */
url_fseek(pb, pos, SEEK_SET);
##################################################################
【3】
##################################################################
mpegts_scan_sdt(ts);
##################################################################
【4】
##################################################################
mpegts_set_service(ts);
##################################################################
【5】
##################################################################
handle_packets(ts, s->probesize);
/* if could not find service, enable auto_guess */
ts->auto_guess = 1;
#ifdef DEBUG_SI
av_log(ts->stream, AV_LOG_DEBUG, "tuning donen");
#endif
s->ctx_flags |= AVFMTCTX_NOHEADER;
} else {
......
}
url_fseek(pb, pos, SEEK_SET);
return 0;
fail:
return -1;
}
這裡簡單說一下MpegTSContext *ts,從上面可以看到,其實這是為了解碼不同容器格式
所使用的私有資料,只有在相應的諸如mpegts.c 檔案才可以使用的,這樣,增加了這個庫
的模組化,而模組化的最大好處,則在於把問題集中到了一個很小的有限區域裡面,如果
你自己構造程式時候,不妨多參考其基本思想--這樣的化,你之後的程式碼,還有你之後的
生活,都將輕鬆許多。
【3】【4】其實呼叫的是同一個函式:mpegts_open_section_filter()
我們來看看意欲何為。
static
MpegTSFilter *mpegts_open_section_filter(MpegTSContext *ts, unsigned int pid,
SectionCallback *section_cb,
void *opaque,
int check_crc)
{
MpegTSFilter *filter;
MpegTSSectionFilter *sec;
#ifdef DEBUG_SI
av_log(ts->stream, AV_LOG_DEBUG, "Filter: pid=0x%xn", pid);
#endif
if (pid >= NB_PID_MAX || ts->pids[pid])
return NULL;
filter = av_mallocz(sizeof(MpegTSFilter));
if (!filter)
return NULL;
ts->pids[pid] = filter;
filter->type = MPEGTS_SECTION;
filter->pid = pid;
filter->last_cc = -1;
sec = &filter->u.section_filter;
sec->section_cb = section_cb;
sec->opaque = opaque;
sec->section_buf = av_malloc(MAX_SECTION_SIZE);
sec->check_crc = check_crc;
if (!sec->section_buf) {
av_free(filter);
return NULL;
}
return filter;
}
要完全明白這部分程式碼,其實需要分析作者對資料結構的定義:
依次為:
struct MpegTSContext;
|
V
struct MpegTSFilter;
|
V
+---------------+---------------+
| |
V V
MpegTSPESFilter MpegTSSectionFilter
其實很簡單,就是struct MpegTSContext;中有NB_PID_MAX(8192)個TS的Filter,而每個
struct MpegTSFilter可能是PES的Filter或者Section的Filter。
我們先說為什麼是8192,在前面的分析中:
給出過TS的語法結構:
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
}
}
}
而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()才會是我們真
正因該關注的地方了:)
這個函式很重要,我們貼出程式碼,以備分析:
/* handle one TS packet */
static void handle_packet(MpegTSContext *ts, const uint8_t *packet)
{
AVFormatContext *s = ts->stream;
MpegTSFilter *tss;
int len, pid, cc, cc_ok, afc, is_start;
const uint8_t *p, *p_end;
##########################################################
獲取該包的PID
##########################################################
pid = AV_RB16(packet + 1) & 0x1fff;
if(pid && discard_pid(ts, pid))
return;
##########################################################
是否是PES或者Section的開頭(payload_unit_start_indicator)
##########################################################
is_start = packet[1] & 0x40;
tss = ts->pids[pid];
##########################################################
ts->auto_guess此時為0,因此不考慮下面的程式碼
##########################################################
if (ts->auto_guess && tss == NULL && is_start) {
add_pes_stream(ts, pid, -1, 0);
tss = ts->pids[pid];
}
if (!tss)
return;
##########################################################
程式碼說的很清楚,雖然檢查,但不利用檢查的結果
##########################################################
/* continuity check (currently not used) */
cc = (packet[3] & 0xf);
cc_ok = (tss->last_cc < 0) || ((((tss->last_cc + 1) & 0x0f) == cc));
tss->last_cc = cc;
##########################################################
跳到adaptation_field_control
##########################################################
/* skip adaptation field */
afc = (packet[3] >> 4) & 3;
p = packet + 4;
if (afc == 0) /* reserved value */
return;
if (afc == 2) /* adaptation field only */
return;
if (afc == 3) {
/* skip adapation field */
p += p[0] + 1;
}
##########################################################
p已近到達TS包中的有效負載的地方
##########################################################
/* if past the end of packet, ignore */
p_end = packet + TS_PACKET_SIZE;
if (p >= p_end)
return;
ts->pos47= url_ftell(ts->stream->pb) % ts->raw_packet_size;
if (tss->type == MPEGTS_SECTION) {
if (is_start) {
#############################################################
針對Section,符合部分第一個位元組為pointer field,該欄位如果為0,
則表示後面緊跟著的是Section的開頭,否則是某Section的End部分和
另一Section的開頭,因此,這裡的流程實際上由兩個值is_start
(payload_unit_start_indicator)和len(pointer field)一起來決定
#############################################################
/* pointer field present */
len = *p++;
if (p + len > p_end)
return;
if (len && cc_ok) {
########################################################
1).is_start == 1
len > 0
負載部分由A Section的End部分和B Section的Start組成,把A的
End部分寫入
########################################################
/* write remaining section bytes */
write_section_data(s, tss,
p, len, 0);
/* check whether filter has been closed */
if (!ts->pids[pid])
return;
}
p += len;
if (p < p_end) {
########################################################
2).is_start == 1
len > 0
負載部分由A Section的End部分和B Section的Start組成,把B的
Start部分寫入
或者:
3).
is_start == 1
len == 0
負載部分僅是一個Section的Start部分,將其寫入
########################################################
write_section_data(s, tss,
p, p_end - p, 1);
}
} else {
if (cc_ok) {
########################################################
4).is_start == 0
負載部分僅是一個Section的中間部分部分,將其寫入
########################################################
write_section_data(s, tss,
p, p_end - p, 0);
}
}
} else {
##########################################################
如果是PES型別,直接呼叫其Callback,但顯然,只有Section部分
解析完成後才可能解析PES
##########################################################
tss->u.pes_filter.pes_cb(tss,
p, p_end - p, is_start);
}
}
write_section_data()函式則反覆收集buffer中的資料,指導完成相關Section的重組過
程,然後呼叫之前註冊的兩個section_cb:
後面我們將分析之前掛在的兩個section_cb,待續......
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/24790158/viewspace-1041570/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- FFMpeg對MPEG2 TS流解碼的流程分析
- ffmpeg——TS流解析
- 修改ffmpeg原始碼,並用它對多路節目TS流解複用及播放原始碼
- ffmpeg解碼基本流程
- ffmpeg解碼音訊流音訊
- FFmpeg開發筆記(五):ffmpeg解碼的基本流程詳解(ffmpeg3新解碼api)筆記API
- MPEG2-TS流檔案的簡介
- FFmpeg開發筆記(九):ffmpeg解碼rtsp流並使用SDL同步播放筆記
- FFmpeg開發筆記(三十七)分析SRS對HLS協議裡TS包的插幀操作筆記協議
- ffmpeg分析系列之七(開啟輸入的流)
- 關於TS流的解析
- RxJava2原始碼分析(一):基本流程分析RxJava原始碼
- obs推流核心流程分析
- FFmpeg開發筆記(三十三)分析ZLMediaKit對H.264流的插幀操作筆記
- TS流解析之TS包頭解析(轉)
- Activiti 流程啟動及節點流轉原始碼分析原始碼
- 使用 FFmpeg 從藍光碟 M2TS 影片中提取 SUP 字幕
- ffmpeg軟解碼遇到的坑
- 關於FFMPEG的解碼模型模型
- FFmpeg音訊解碼音訊
- Java版流媒體編解碼和影像處理(JavaCPP+FFmpeg)Java
- FFmpeg開發筆記(十五)詳解MediaMTX的推拉流筆記
- ffmpeg+nginx 實現拉流轉碼播放Nginx
- FFmpeg libswscale原始碼分析2-轉碼命令列與濾鏡圖原始碼命令列
- FFmpeg的IO分析
- FFmpeg 播放 RTSP/Webcam 流Web
- spark core原始碼分析2 master啟動流程Spark原始碼AST
- MediaCodec解碼FFmpeg AvPacket
- Apache 流框架 Flink,Spark Streaming,Storm對比分析(2)Apache框架SparkORM
- Scrapy原始碼閱讀分析_2_啟動流程原始碼
- Glide 系列-2:主流程原始碼分析(4.8.0)IDE原始碼
- Android中使用ffmpeg編碼進行rtmp推流Android
- TS流解析之PMT表格解析(轉)
- TS流解析之PAT表格解析(轉)
- NDK開發——FFmpeg視訊解碼
- FFmpeg和avconv編解碼工具
- ts程式碼提示很慢問題解決
- Java servlet執行的完整流程(圖解含原始碼分析)JavaServlet圖解原始碼