南昌免费网站建站模板,学编程从哪儿入手,网线制作的注意事项,太原网站设计三篇相关联的文章#xff1a;
ffmpeg下HLS解析过程-CSDN博客TS文件格式详解及解封装过程-CSDN博客
FFMPEG解析ts流-CSDN博客
一、简介
关于TS格式解析#xff0c;可以参考《TS文件格式详解及解封装过程-CSDN博客》#xff0c;本文主要代码部分解读。建议大家熟读iso138… 三篇相关联的文章
ffmpeg下HLS解析过程-CSDN博客TS文件格式详解及解封装过程-CSDN博客
FFMPEG解析ts流-CSDN博客
一、简介
关于TS格式解析可以参考《TS文件格式详解及解封装过程-CSDN博客》本文主要代码部分解读。建议大家熟读iso13818-1碰到问题很多情况是因为没有熟悉标准
二、主要结构体
相关结构关系 struct MpegTSContext; | V struct MpegTSFilter; | V ------------------------------ | | V V MpegTSPESFilter MpegTSSectionFilter 测 2.1 ts文件过滤器的结构体 struct MpegTSFilter {//ts的过滤器 int pid; int es_id; int last_cc; /* last cc code (-1 if first packet) */ int64_t last_pcr; enum MpegTSFilterType type;//过滤器类型分辨PES,PCR,SECTION union { MpegTSPESFilter pes_filter; MpegTSSectionFilter section_filter; }u; }; 2.2 section过滤器结构体 typedef struct MpegTSSectionFilter { int section_index; //section的索引 int section_h_size; //头大小 int last_ver; unsigned crc; unsigned last_crc; uint8_t *section_buf; //保存section数据 unsigned int check_crc : 1; unsigned int end_of_section_reached : 1; SectionCallback *section_cb; //回调函数 void *opaque; //类似于类指针的东西 } MpegTSSectionFilter; 2.3 节目的结构体 struct Program { unsigned int id; // program id/service id unsigned int nb_pids; unsigned int pids[MAX_PIDS_PER_PROGRAM]; int pmt_found; //标识pmt是否已经找到 }; 2.4 MpegTSContext struct MpegTSContext { const AVClass *class; /* user data */ AVFormatContext *stream; /** raw packet size, including FEC if present */ int raw_packet_size;//ts格式长度 int size_stat[3];//get_pcr探测ts三种格式分数用的 int size_stat_count;//get_pcr探测ts三种格式次数 #define SIZE_STAT_THRESHOLD 10 int64_t pos47_full; /** 如果为真, 所有的pid将会用来去寻找流 */ int auto_guess; /** 对于每个ts包都进行精确的计算 */ int mpeg2ts_compute_pcr; /** 修复 dvb teletext pts */ int fix_teletext_pts; AVPacket *pkt; /** to detect seek */ int64_t last_pos; int skip_changes; int skip_clear; int scan_all_pmts; int resync_size; /******************************************/ /* private mpegts data */ /* scan context */ /** structure to keep track of Program-pids mapping */ unsigned int nb_prg; struct Program *prg; int8_t crc_validity[NB_PID_MAX]; /** filters for various streams specified by PMT for the PAT and PMT */ MpegTSFilter *pids[NB_PID_MAX]; int current_pid }; 二、demuxer相关对外接口
2.1 相关接口 AVInputFormat mpegtsraw_demuxer { mpegts, NULL_IF_CONFIG_SMALL(MPEG-TS (MPEG-2 Transport Stream)), sizeof(MpegTSContext), mpegts_probe, mpegts_read_header, mpegts_read_close, read_seek, mpegts_get_pcr, .flags AVFMT_SHOW_IDS|AVFMT_TS_DISCONT, }; 2.2 相关接口解析
2.2.1 mpegts_probe
故名思议就是探测流是否是mpegts格式 /* * 函数功能: * 分析流中是三种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); mpegts_probe被av_probe_input_format2调用根据返回的score来判断那种格式的可能性最大。mpegts_probe调用了analyze函数 /* * 函数功能: * 在size大小的buf中寻找满足特定格式长度为packet_size的 * packet的个数; * 显然返回的值越大越可能是相应的格式(188/192/204) */ static int analyze(const uint8_t *buf, int size, int packet_size, int *index){ int stat[TS_MAX_PACKET_SIZE]; int i; int x0; int best_score0; memset(stat, 0, packet_size*sizeof(int)); for (xi0; i size-3; i) { if ((buf[i] 0x47) !(buf[i1] 0x80) (buf[i3] 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; } buf[i] 0x47 !(buf[i1] 0x80) (buf[i3] 0x30)是TS流同步开始的模式
0x47是TS流同步的标志记该模式为“TS流同步模式”
buf[i1] 0x80是传输错误标志
buf[i3] 0x30是adaptation_field_control为0时表示为ISO/IEC未来使用保留目前不存在这样的值。
stat数组变量存储的是“TS流同步模式”在某个位置出现的次数。返回的值越大越多是相应的格式(188/192/204)
size-3这里为什么是-3呢因为同步标志、传输错误标志和adaptation_field_control占了4个字节查找的特定格式至少3 个Bytes所以至少最后3 个Bytes 不用查找再找就超界了
这就是MPEG TS的探测过程
2.2.2 mpegts_read_header /* * 函数功能: * */ int mpegts_read_header(AVFormatContext *s, AVFormatParameters *ap) { /* * MpegTSContext , 是为了解码不同容器格式所使用的私有数据 * 只有在相应的诸如mpegts.c文件才可以使用的. * 这样增加了这个库的模块化. */ MpegTSContext *ts s-priv_data; AVIOContext *pb s-pb; uint8_t buf[8*1024]; int len; int64_t pos; /* read the first 8*1024 bytes to get packet size */ pos avio_tell(pb); // 获取buf的当前位置保存流的当前位置便于检测操作完成后恢复到原来的位置这样在播放的时候就不会浪费一段流 len avio_read(pb, buf, sizeof(buf)); // 从pb-opaque中读取8192个字节到buf if (len ! sizeof(buf)) goto fail; /* * 获得TS包的实际长度继续探测ts包的长度这个步骤是不是重复了 */ ts-raw_packet_size get_packet_size(buf, sizeof(buf)); if (ts-raw_packet_size 0) { av_log(s, AV_LOG_WARNING, Could not detect TS packet size, defaulting to non-FEC/DVHS\n); ts-raw_packet_size TS_PACKET_SIZE; } ts-stream s; ts-auto_guess 0; //判断是否为MPEGTS解复用器如果是则进行解复用 if (s-iformat ff_mpegts_demuxer) { /* normal demux */ /* first do a scaning to get all the services */ if (avio_seek(pb, pos, SEEK_SET) 0) { av_log(s, AV_LOG_ERROR, Unable to seek back to the start\n); } /* * 挂载了两个Section类型的过滤器, * 其实在TS的两种负载中section是PES的元数据 * 只有先解析了section,才能进一步解析PES数据因此先挂上section的过滤器。 * 并设置sdt_cbpat_cb */ mpegts_open_section_filter(ts, SDT_PID, sdt_cb, ts, 1); mpegts_open_section_filter(ts, PAT_PID, pat_cb, ts, 1); /* *处理packets处理的packet个数为s-probesize / ts-raw_packet_size 探测一段流便于检测出SDTPATPMT表 */ handle_packets(ts, s-probesize / ts-raw_packet_size); /* if could not find service, enable auto_guess */ /*并把auto_guess置为1auto_guess 1, 则在handle_packet 的函数中只要发现 个PES 的pid 就 建立该PES 的stream*/ ts-auto_guess 1; av_dlog(ts-stream, tuning done\n); /*将 AVFMTCTX_NOHEADER 标志设置到 s-ctx_flags 中表示header已经解析好不需要再调用 read_header 函数了/ s-ctx_flags | AVFMTCTX_NOHEADER; } else { ... } avio_seek(pb, pos, SEEK_SET); //seek到pos最开始的位置 return 0; fail: return -1; } 关于section的定义先看结构图 每个业务标都有section而PMT的section包含了音视频流 一个表里可能有多个section一个section可能包含多条流
SDT表当中会有节目的名子提供商名子等等
2.2.2.1mpegts_open_section_filter函数分析
这个函数可以解释mpegts.c代码结构的精妙之处PSI业务信息表的处理都是通过该函数挂载到MpegTSContext结构的pids字段上的。这样如果你想增加别的业务信息的表处理函数只要通过这个函数来挂载即可体现了软件设计的著名的“开闭”原则。下面分析一下他的代码。
static MpegTSFilter *mpegts_open_section_filter(MpegTSContext *ts, unsigned int pid,SectionCallback *section_cb, void *opaque,int check_crc)
{MpegTSFilter *filter;MpegTSSectionFilter *sec;dprintf(ts-stream, Filter: pid0x%x\n, pid);if (pid NB_PID_MAX || ts-pids[pid])return NULL;//给filter分配空间挂载到MpegTSContext的pids上//就是该实例filter av_mallocz(sizeof(MpegTSFilter));if (!filter)return NULL;//挂载filter实例ts-pids[pid] filter;//设置filter相关的参数因为业务信息表的分析的单位是段//所以该filter的类型是MPEGTS_SECTIONfilter-type MPEGTS_SECTION;//设置pidfilter-pid pid;filter-last_cc -1;//设置filter回调处理函数sec filter-u.section_filter;sec-section_cb section_cb;sec-opaque opaque;//分配段数据处理的缓冲区调用handle_packet函数后会调用//write_section_data将ts包中的业务信息表的数据存储在这儿//直到一个段收集完成才交付上面注册的回调函数处理。sec-section_buf av_malloc(MAX_SECTION_SIZE);sec-check_crc check_crc;if (!sec-section_buf) {av_free(filter);return NULL;}return filter;
}
2.2.2.2handle_packets函数分析
handle_packets函数在两个地方被调用一个是mpegts_read_header函数中另外一个是mpegts_read_packet函数中被mpegts_read_header函数调用是用来搜索PSI业务信息nb_packets参数为探测的ts包的个数在mpegts_read_packet函数中被调用用来搜索补充PSI业务信息和demux PES流nb_packets为00不是表示处理的包的个数为0。 ts-stop_parse当遇到stop_parse大于0时解析完一个PES时此值为1退出循环。
接下来分析最重要的地方handler_packets简单看来 handle_packets() | -read_packet() | -handle_packet() | -write_section_data() handle_packet是mpegts.c代码的核心所有的其他代码都是为这个函数准备的。在调用该函数之前先调用read_packet函数获得一个ts包通常是188bytes然后传给该函数packet参数就是TS包。
1、将ts包拼装为section解析pat section
2、将ts包拼装为pes依据video pid获取video pesstatic int 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;int64_t pos;//从TS包获得包的PID。pid AV_RB16(packet 1) 0x1fff;if(pid discard_pid(ts, pid))return 0;########################################################## 是不是PES 或者Section 的开头(payload_unit_start_indicator) ##########################################################is_start packet[1] 0x40;tss ts-pids[pid];//ts-auto_guess在mpegts_read_header函数中被设置为0//也就是说在ts检测过程中是不建立pes stream的。if (ts-auto_guess tss NULL is_start) {add_pes_stream(ts, pid, -1, 0);tss ts-pids[pid];}//mpegts_read_header函数调用handle_packet函数只是处理TS流的//业务信息(PAT,PDT等)因为并没有为对应的PES建立tss所以tss为空直接返回。//如果是pestss不为空则继续if (!tss)return 0;/* 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 语法元素* * 00 | Reserved for future use by ISO/IEC* 01 | No adaptation_field, payload only* 10 | Adaptation_field only, no payload* 11 | Adaptation_field follwed by payload* */
————————————————/* skip adaptation field */afc (packet[3] 4) 3;p packet 4;if (afc 0) /* reserved value */return 0;if (afc 2) /* adaptation field only */return 0;if (afc 3) {/* skip adapation field p[0]对应的语法元素为: adaptation_field_length*/p p[0] 1;}########################################################## p已近 达TS 包中的有效负载的地方 ########################################################## /* if past the end of packet, ignore */p_end packet TS_PACKET_SIZE;if (p p_end)return 0;pos url_ftell(ts-stream-pb);ts-pos47 pos % ts-raw_packet_size;if (tss-type MPEGTS_SECTION) {/*** 针对Section, 第一个字节对应的语法元素为:pointer_field(见2.4.4.1),* 它表示在当前TS包中从pointer_field开始到第一个section的第一个字节间的字节数。* 当TS包中有至少一个section的起始时,* payload_unit_start_indicator 1 且 TS负载的第一个字节为pointer_field;* pointer_field 0x00时表示section的起始就在这个字节之后;* 当TS包中没有section的起始时, * payload_unit_start_indicator 0 且 TS负载中没有pointer_field;*/if (is_start) {//获取pointer field字段,//新的段从pointer field字段指示的位置开始len *p;if (p len p_end)return 0;if (len cc_ok) {//这个时候TS的负载有两个部分构成//1)从TS负载开始到pointer field字段指示的位置;//2)从pointer field字段指示的位置到TS包结束//1)位置代表的是上一个段的末尾部分。//2)位置代表的新的段开始的部分。//下面的代码是保存上一个段末尾部分数据也就是//1)位置的数据。######################################################## 1).is_start 1 len 0 负载部分由A Section 的End 部分和B Section 的Start 组成把A 的 End 部分写入 ######################################################## write_section_data(s, tss,p, len, 0);/* check whether filter has been closed */if (!ts-pids[pid])return 0;}p len;//保留新的段数据也就是2)位置的数据。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 {int ret;########################################################## 若是是PES 类型直接调用其Callback----mpegts_push_data但显然只有Section 部分 解析完成后才可能解析PES ########################################################## // Note: The position here points actually behind the current packet.if ((ret tss-u.pes_filter.pes_cb(tss, p, p_end - p, is_start,pos - ts-raw_packet_size)) 0)return ret;}return 0;
} write_section_data()函数则反复收集buffer中的数据指导完成相关Section的重组过 程而后调用以前注册的两个section_cb:
static void write_section_data(AVFormatContext *s, MpegTSFilter *tss1, const uint8_t *buf, int buf_size, int is_start)
{ MpegTSSectionFilter *tss tss1-u.section_filter; int len; //buf 中是 个段的开始部分。 if (is_start) { //将内容复制 tss-section_buf 中保存 memcpy(tss-section_buf, buf, buf_size); //tss-section_index 段索引。 tss-section_index buf_size; //段的长度如今还不知道设置为-1 tss-section_h_size -1; //是否 达段的结尾。 tss-end_of_section_reached 0; } else { //buf 中是段中间的数据。 if (tss-end_of_section_reached) return; len 4096 - tss-section_index; if (buf_size len) len buf_size; memcpy(tss-section_buf tss-section_index, buf, len); tss-section_index len; } //若是条件知足计算段的长度 if (tss-section_h_size -1 tss-section_index 3) { len (AV_RB16(tss-section_buf 1) 0xfff) 3; if (len 4096) return; tss-section_h_size len; } //判断段数据是否收集完毕若是收集完毕调用相应的回调函数处理该段。 if (tss-section_h_size ! -1 tss-section_index tss-section_h_size) { tss-end_of_section_reached 1; if (!tss-check_crc || av_crc(av_crc_get_table(AV_CRC_32_IEEE), -1, tss-section_buf, tss-section_h_size) 0) tss-section_cb(tss1, tss-section_buf, tss-section_h_size); }
} Section_cb是一个回调函数调用的函数有pat_cb,sdt_cb,pmt_cb, Table_id 00代表pat表02代表pmt表
到了这里还是一头雾水还没看到解析PESPTS这些在哪解析的
处理每一个包如果是section包就调用 write_section_data 这个函数里面如果一个PAT, PMT, SDT表已经构成则会调用刚刚看到的pat_cb, pmt_cb, sdt_cb分析到这里已经不用再管section包了只看pes包所以一般会调用 tss-u.pes_filter.pes_cb 这个函数指针到底是什么呢在函数 add_pes_stream 里面可以看到 mpegts_open_pes_filter 函数的一个参数 mpegts_push_data 就是这里的 tss-u.pes_filter.pes_cb。
2.2.2.3 mpegts_push_data
一帧视频就是一个PES包av_read_frame()就是从PES包队列中取出一个PES包。一个PES包是分配在连续的几个TS包中所以如果我们要获得一帧数据那么我们需要把连续的几个TS包里的数据全部取出来才能组合成一个PES。那我们怎么知道一个PES的开始和结尾呢那我们还是一个个遍历每一个TS包寻找包头里payload_unit_start_indicator为1包这个标志位代表着是一个PES的开始那么我从这开始一直到下一个payload_unit_start_indicator为1这中间的TS包组成起来就是一个PES。
/*
*函数功能
*解析PES包,得到时间戳、流索引、PES包长度等数据,并将这个PES包压入到PES包队列
*/
static int mpegts_push_data(MpegTSFilter *filter,const uint8_t *buf, int buf_size, int is_start,int64_t pos)
{PESContext *pes filter-u.pes_filter.opaque;MpegTSContext *ts pes-ts;const uint8_t *p;int len, code;if (!ts-pkt)return 0;if (is_start) {if (pes-state MPEGTS_PAYLOAD pes-data_index 0) { //当前包还没处理完新的包到来。new_pes_packet(pes, ts-pkt); //把pes的数据传给pkt(提交给上层)ts-stop_parse 1; //当前包结束解析} else {reset_pes_packet_state(pes); //新的开始}pes-state MPEGTS_HEADER;pes-ts_packet_pos pos;}p buf;
//4个字节的TS头个PES的头,10为填充头while (buf_size 0) {switch (pes-state) {case MPEGTS_HEADER: //解析pes包头部
if (pes-data_index PES_START_SIZE) {/* we got all the PES orsection header. We can now* decide */if (pes-header[0] 0x00 pes-header[1] 0x00 pes-header[2] 0x01){//前三个为00,00,01。PES起始位/* it must be an MPEG-2 PESstream */code pes-header[3] |0x100;//stream_id//得到pes长度pes-total_size AV_RB16(pes-header 4);/* 分配ES的空间 */pes-buffer av_malloc(pes-total_sizeFF_INPUT_BUFFER_PADDING_SIZE);
/*****************PES解析***********************//* PES packing parsing */case MPEGTS_PESHEADER:case MPEGTS_PESHEADER_FILL:if (pes-data_index pes-pes_header_size) {const uint8_t *r;unsigned int flags, pes_ext, skip;flags pes-header[7];//SYNTAX: PTS_DTS_flagsr pes-header 9;pes-pts AV_NOPTS_VALUE;pes-dts AV_NOPTS_VALUE;//pts一共33bit解析出ptsif ((flags 0xc0) 0x80) {pes-dts pes-pts ff_parse_pes_pts(r);r 5;} else if ((flags 0xc0) 0xc0) {pes-pts ff_parse_pes_pts(r);r 5;pes-dts ff_parse_pes_pts(r);r 5;}case MPEGTS_PAYLOAD:if (pes-buffer) {if (pes-data_index 0 pes-data_index buf_size pes-total_size) { //加上新的数据超出一个pes包的长度(当前包结束) ???new_pes_packet(pes, ts-pkt); //把pes的数据传给pkt(提交给上层)pes-total_size MAX_PES_PAYLOAD;pes-buffer av_buffer_alloc(pes-total_size FF_INPUT_BUFFER_PADDING_SIZE);if (!pes-buffer)return AVERROR(ENOMEM);ts-stop_parse 1; //当前包结束解析} else if (pes-data_index 0 buf_size pes-total_size) {// pes packet size is ts size packet and pes data is padded with 0xff// not sure if this is legal in ts but see issue #2392buf_size pes-total_size;}
/* 取出PES的负载数据组成TS流 */memcpy(pes-buffer-data pes-data_index, p, buf_size); //拷贝数据到pes-buffer(可能还没拷完整个pes packet的数据)pes-data_index buf_size;/* emit complete packets with known packet size* decreases demuxer delay for infrequent packets like subtitles from* a couple of seconds to milliseconds for properly muxed files.* total_size is the number of bytes following pes_packet_length* in the pes header, i.e. not counting the first PES_START_SIZE bytes */if (!ts-stop_parse pes-total_size MAX_PES_PAYLOAD pes-pes_header_size pes-data_index pes-total_size PES_START_SIZE) {ts-stop_parse 1; //数据已经拷贝完停止解析。new_pes_packet(pes, ts-pkt); //把pes的数据传给pkt(提交给上层)}}buf_size 0;break;
ts-stop_parse 1 意味着一个pes包构成了所以上面的函数mpegts_read_packet就返回了这样一个pes包送上去了再送到codec去解码最后送去video或audio输出设置显示了。由些可以看到ts流和avi, mkv这些一样都是一个容器真真的数据都是包含在其中的一个一个的串流。 未完待续。。。。。
输入时间戳不边续时的处理机制 目的: 输入时间戳不连续必须保证输出时间戳的连续。 1. 当视频时间戳连续而音频时间戳不连续时 不强行修改时间戳 用插入静音帧来实现重同步 三、问题
1、pat_cb/pmt_cb是在那里调用的
答write_section_data判断段数据是否收集完毕若是收集完毕调用相应的回调函数处理该段
2、mpegts_push_data是在哪调用的
handle_packet的u.pes_filter.pes_cb对应mpegts_push_data
3、一个完整的pes包是什么时候完成解析的
mpegts_push_data-MPEGTS_PAYLOAD-ts-stop_parse 1