ffmpeg——TS流解析
RTSP(Real Time Streaming Protocol),實時流傳輸協議,是TCP/IP協議體系中的一個應用層協議,該協議定義了一對多應用程式如何有效地通過IP網路傳送多媒體資料。RTSP在體系結構上位於RTP和RTCP之上,它使用TCP或UDP完成資料傳輸。
實時傳輸協議RTP(Real-time Transport Protocol)是一個網路傳輸協議,RTP協議詳細說明了在網際網路上傳遞音訊和視訊的標準資料包格式。
將TS/PS的解析檔案定義在libavformat/mpegts.c檔案中
將音訊、視訊的解碼定義在libavcodec/mpeg12.c檔案中
- MPEG2-TS的demuxer函式
AVInputFormat ff_mpegts_demuxer = {
.name = "mpegts",
.long_name = NULL_IF_CONFIG_SMALL("MPEG-TS (MPEG-2 Transport Stream)"),
.priv_data_size = sizeof(MpegTSContext),
.read_probe = mpegts_probe, //分析是三種TS格式的哪一種
.read_header = mpegts_read_header,//讀資料頭資訊,比如在ts流當中的資料包大小,還ts流中的節目資訊,
//sdt表,pmt表,video pid,audio pid等等,以便後面讀資料時使用。
.read_packet = mpegts_read_packet,
.read_close = mpegts_read_close,
.read_timestamp = mpegts_get_dts,
.flags = AVFMT_SHOW_IDS | AVFMT_TS_DISCONT,
.priv_class = &mpegts_class,
};
該結構通過av_register_all函式註冊到ffmpeg的主框架中,通過mpegts_probe函式來檢測是否是TS流格式,然後通過 mpegts_read_header函式找到一路音訊流和一路視訊流(注意:在該函式中沒有找全所有的音訊流和視訊流),最後呼叫 mpegts_read_packet函式將找到的音訊流和視訊流資料提取出來,通過主框架推入解碼器。
- 解析流中的TS格式
/*
* 出現3種格式,主要原因是:
* TS標準是 188Bytes;
* 日本標準是192Bytes的DVH-S格式;
* 第三種的 204Bytes則是在188Bytes的基礎上,加上16Bytes的FEC(前向糾錯).
*/
#define TS_DVHS_PACKET_SIZE 192
#define TS_FEC_PACKET_SIZE 204
#define TS_PACKET_SIZE 188
mpegts_probe, //分析是三種TS格式的哪一種
- MPEG2-TS頭解析
static int mpegts_read_header(AVFormatContext *s)
{
MpegTSContext *ts = s->priv_data;
AVIOContext *pb = s->pb;
pos = avio_tell(pb);//儲存流的當前位置,便於檢測操作完成後恢復到原來的位置
len = avio_read(pb, buf, sizeof(buf));//從pb中讀入一段流到buf
ts->raw_packet_size = get_packet_size(buf, len);//主要是呼叫analyze,獲得ts包的型別
ts->auto_guess = 0;
if (s->iformat == &ff_mpegts_demuxer)
{
//掛載解析SDT表的回撥函式到ts->pids變數上,這樣在handle_packet函式中根據對應的pid找到對應處理回撥函式。
mpegts_open_section_filter(ts, SDT_PID, sdt_cb, ts, 1);// 向ts->pids中註冊pid為SDT_PID的packet的回撥函式
//同上,只是掛上PAT解析的回撥函式
mpegts_open_section_filter(ts, PAT_PID, pat_cb, ts, 1);// 向ts->pids中註冊pid為PAT_PID的packet的回撥函式
//探測一段流,便於檢測出SDT,PAT,PMT表
handle_packets(ts, probesize / ts->raw_packet_size);
//開啟add pes stream的標誌,這樣在handle_packet函式中發現了pes的pid,就會自動建立該pes的stream。
ts->auto_guess = 1;
}
seek_back(s, pb, pos);//恢復到檢測前的位置。
}
對mpegts_open_section_filter:
就是struct MpegTSContext;中有NB_PID_MAX(8192)個TS的Filter,
而每個struct MpegTSFilter,可能是PES的Filter,或者是Section的Filter。
MpegTSContext::auto_guess /* if true, all pids are analyzed to find streams /
為1時,則在handle_packet的函式中只要發現一個PES的pid就建立該PES的stream
auto_guess主要作用是用來在TS流中沒有業務資訊時,如果被設定成了1的話,那麼就會將任何一個PID的流當做媒體流建立對應的PES資料結構。
在mpegts_read_header函式的過程中發現了PES的pid,但是不建立對應的流,只是分析PSI資訊。相關的程式碼見handle_packet函式的下面的程式碼:
handle_packet(MpegTSContext *ts, const uint8_t *packet)
{
MpegTSFilter *tss;
tss = ts->pids[pid];
if (ts->auto_guess && !tss && is_start) {
add_pes_stream(ts, pid, -1);//若auto_guess為1,建立所有的pid的流
tss = ts->pids[pid];
}
}
pat_cb(MpegTSFilter *filter, const uint8_t *section, int section_len)
{
MpegTSContext *ts = filter->u.section_filter.opaque;
for (;;) {
sid = get16(&p, p_end);
pmt_pid = get16(&p, p_end);
pmt_pid &= 0x1fff;
MpegTSFilter *fil = ts->pids[pmt_pid];
program = av_new_program(ts->stream, sid);//建立一個AVProgram
if (program) {
program->program_num = sid;
program->pmt_pid = pmt_pid;
}
if (!ts->pids[pmt_pid])
mpegts_open_section_filter(ts, pmt_pid, pmt_cb, ts, 1);//開啟pmt_pid對應的filter
add_pat_entry(ts, sid);//在ts->prg中增加一個program
add_pid_to_pmt(ts, sid, 0); // add pat pid to program
add_pid_to_pmt(ts, sid, pmt_pid);在ts中找到節目好為sid的節目,再將pmt_pid增加到該節目的pid中
}
pmt_cb(MpegTSFilter *filter, const uint8_t *section, int section_len)
{
MpegTSContext *ts = filter->u.section_filter.opaque;
for (;;) {
stream_type = get8(&p, p_end);
pid = get16(&p, p_end) & 0x1fff;
if (ts->pids[pid] && ts->pids[pid]->type == MPEGTS_PES)
{
pes = ts->pids[pid]->u.pes_filter.opaque;
if (!pes->st)
{
pes->st = avformat_new_stream(pes->stream, NULL); //建立pes對應的AVStream
if (!pes->st)
goto out;
pes->st->id = pes->pid;
}
st = pes->st;//st為pes的AVStream
}
if (pes && !pes->stream_type)
mpegts_set_stream_info(st, pes, stream_type, prog_reg_desc);//設定上面新建的AVStream的引數
add_pid_to_pmt(ts, h->id, pid);//將pid增加到pmt中
av_program_add_stream_index(ts->stream, h->id, st->index);設定AVProgram的stream_index
}
- MPEG2-TS的包處理
handle_packets函式在兩個地方被呼叫,一個是mpegts_read_header函式中,另外一個是mpegts_read_packet函式中,被mpegts_read_header函式呼叫是用來搜尋PSI業務資訊,nb_packets引數為探測的ts包的個數;在mpegts_read_packet函式中被呼叫用來搜尋補充PSI業務資訊和demux PES流,nb_packets為0,0不是表示處理的包的個數為0。
static int handle_packets(MpegTSContext *ts, int64_t nb_packets)
{
ts->stop_parse = 0;
//該變數指示一次handle_packets處理的結束。在mpegts_read_packet被呼叫的時候,如果發現完一個PES的包,則
//ts->stop_parse = 1,則當前分析結束。
packet_num = 0;
for (;;)
{
packet_num++;
read_packet(s, packet, ts->raw_packet_size, &data);//根據sync_byte(0x47),讀入一個ts包data中
handle_packet(ts, data);//處理一個ts包
}
}
可以說handle_packet是mpegts.c程式碼的核心,所有的其他程式碼都是為這個函式準備的。
在呼叫該函式之前先呼叫read_packet函式獲得一個ts包(通常是188bytes),然後傳給該函式,packet引數就是TS包。
static int handle_packet(MpegTSContext *ts, const uint8_t *packet)
{
MpegTSFilter *tss;
int len, pid, cc, expected_cc, cc_ok, afc, is_start, is_discontinuity,
has_adaptation, has_payload;
const uint8_t *p, *p_end;
int64_t pos;
pid = AV_RB16(packet + 1) & 0x1fff;//獲得該包的PID
if (pid && discard_pid(ts, pid))
return 0;
is_start = packet[1] & 0x40;//是否位pes或section的開頭,即payload_unit_start_indicator
tss = ts->pids[pid];
//ts->auto_guess在mpegts_read_header函式中被設定為0,也就是說在ts檢測過程中是不建立pes stream的。
if (ts->auto_guess && !tss && is_start) {//如果ts->auto_guess為1,則所有的pid都會去分析,尋找pes
add_pes_stream(ts, pid, -1);
tss = ts->pids[pid];
}
//在mpegts_read_header函式中,呼叫handle_packet函式只是處理TS流的業務資訊,
//因為並沒有為對應的PES建立tss,所以tss為空,直接返回。
if (!tss)
return 0;
ts->current_pid = pid;
afc = (packet[3] >> 4) & 3;//adaptation_field_control若位0,表示將來用,無效
if (afc == 0) /* reserved value */
return 0;
has_adaptation = afc & 2;
has_payload = afc & 1;
is_discontinuity = has_adaptation &&
packet[4] != 0 && /* with length > 0 */
(packet[5] & 0x80); /* and discontinuity indicated */
/* continuity check (currently not used) */
cc = (packet[3] & 0xf);
expected_cc = has_payload ? (tss->last_cc + 1) & 0x0f : tss->last_cc;
cc_ok = pid == 0x1FFF || // null packet PID
is_discontinuity ||
tss->last_cc < 0 ||
expected_cc == cc;
tss->last_cc = cc;
if (!cc_ok) {
av_log(ts->stream, AV_LOG_DEBUG,
"Continuity check failed for pid %d expected %d got %d\n",
pid, expected_cc, cc);
if (tss->type == MPEGTS_PES) {
PESContext *pc = tss->u.pes_filter.opaque;
pc->flags |= AV_PKT_FLAG_CORRUPT;
}
}
p = packet + 4;//p到達負載
if (has_adaptation) {
int64_t pcr_h;
int pcr_l;
if (parse_pcr(&pcr_h, &pcr_l, packet) == 0)
tss->last_pcr = pcr_h * 300 + pcr_l;
/* skip adaptation field */
p += p[0] + 1;
}
/* if past the end of packet, ignore */
p_end = packet + TS_PACKET_SIZE;
if (p >= p_end || !has_payload)
return 0;
pos = avio_tell(ts->stream->pb);
if (pos >= 0) {
av_assert0(pos >= TS_PACKET_SIZE);
ts->pos47_full = pos - TS_PACKET_SIZE;
}
//在開始時,ts->pids中,只有PAT 和 SDT的 pid Filter不是null,因此其它的資料全部拋棄,
//只處理PAT 和 SDT packet中的資料
if (tss->type == MPEGTS_SECTION) {//表示當前的TS包包含一個新的業務資訊段
if (is_start) {
/* pointer field present */
len = *p++;
if (len > p_end - p)
return 0;
if (len && cc_ok) {//ts包包含了兩個section的內容
/*這個時候TS的負載有兩個部分構成:
1)從TS負載開始到pointer field欄位指示的位置;
2)從pointer field欄位指示的位置到TS包結束
1)位置代表的是上一個段的末尾部分。
2)位置代表的新的段開始的部分。
下面的程式碼是儲存上一個段末尾部分資料,也就是1)位置的資料。 */
/* write remaining section bytes */
write_section_data(ts, tss, p, len, 0);
/* check whether filter has been closed */
if (!ts->pids[pid])
return 0;
}
p += len;
if (p < p_end) {
write_section_data(ts, tss, p, p_end - p, 1); //保留新的段資料,也就是2)位置的資料。
}
} else {
if (cc_ok) {
write_section_data(ts, tss,p, p_end - p, 0);//儲存段中間的資料。
}
}
} else {//正常的PES資料的處理
int ret;
// Note: The position here points actually behind the current packet.
if (tss->type == MPEGTS_PES) {
if ((ret = tss->u.pes_filter.pes_cb(tss, p, p_end - p, is_start, pos - ts->raw_packet_size)) < 0) //pes型別
return ret;
}
}
return 0;
}
write_section_data(AVFormatContext *s, MpegTSFilter *tss1, const uint8_t *buf, int buf_size, int is_start)//將資料寫入到對應的pid Filter中的uint8_t *section_buf; 中
以PAT為例子,在static int mpegts_read_header(AVFormatContext *s)中,將static void pat_cb(MpegTSFilter *filter, const uint8_t *section, int section_len) 函式賦值給 PAT_PID Filter的section_cb,因此,此處呼叫這個函式,處理寫入到section_buf中的資料。
http://blog.chinaunix.net/uid-24322243-id-2620178.html
http://blog.csdn.net/u013354805/article/details/51546511
http://blog.csdn.net/xiruanliuwei/article/details/28167757
http://blog.csdn.net/hjksfw900/article/details/3784821
相關文章
- 修改ffmpeg原始碼,並用它對多路節目TS流解複用及播放原始碼
- 使用ffmpeg對視訊進行TS切片
- FFmpeg 播放 RTSP/Webcam 流Web
- ffmpeg+rtmp推流/拉流(十)
- Java中使用FFmpeg拉取RTSP流Java
- C# ffmpeg m3u8 ts 影片拼接mp4C#
- FFmpeg開發筆記(三十)解析H.264碼流中的SPS幀和PPS幀筆記
- python利用ffmpeg進行rtmp推流直播Python
- FFmpeg開發筆記(九):ffmpeg解碼rtsp流並使用SDL同步播放筆記
- 使用 FFmpeg 從藍光碟 M2TS 影片中提取 SUP 字幕
- C# 使用ffmpeg讀取監控影片流C#
- Qt+FFmpeg仿VLC接收RTSP流並播放QT
- ffmpeg 推流檔案,採用rtmp協議協議
- Android中使用ffmpeg編碼進行rtmp推流Android
- 使用linux的ffmpeg進行B站直播推流Linux
- FFmpeg開發筆記(十五)詳解MediaMTX的推拉流筆記
- ffmpeg第7篇:資料流選擇神器-map指令
- JS 位元組流 解析JS
- FFmpeg開發筆記(三十七)分析SRS對HLS協議裡TS包的插幀操作筆記協議
- 使用PHP結合Ffmpeg快速搭建流媒體服務實踐PHP
- Java版流媒體編解碼和影像處理(JavaCPP+FFmpeg)Java
- ffmpeg
- [FFMpeg] 非標準解析度視訊Dump YUV注意事項
- TS — 介面
- TS 介面
- Burpsuite中protobuf資料流的解析UI
- Nginx+FFmpeg實現rtsp流轉hls流,在WEB通過H5 video實現視訊播放NginxWebH5IDE
- FFmpeg開發筆記(三十一)使用RTMP Streamer開啟APP直播推流筆記APP
- FFmpeg開發筆記(四十)Nginx整合rtmp模組實現RTMP推拉流筆記Nginx
- 帶貨直播系統,透過ffmpeg推流實現首屏秒開
- TS定義陣列 ts宣告函式陣列函式
- TS基礎應用 & Hook中的TSHook
- 安裝ffmpeg和crontab執行ffmpeg
- FFmpeg開發筆記(四十五)使用SRT Streamer開啟APP直播推流筆記APP
- java流的中間操作原始碼解析Java原始碼
- FFmpeg 使用
- ts---介面
- ts---類