ffmpeg解析TS流(转)
MPEG的系统层编码为不同的应用场景设计了两种格式:
TS(Transport Stream) 和PS(Program Stream),
它们两者之间不具有层级关系,
在逻辑上,它们两者都是由PES(Packetized Elementary Stream)包组成的,
所以可以很方便地实现相互转换.
TS(Transport Stream):
是将具有一个或多个独立时间基的一个或多个节目(包括音频和视频)组成一个流,
组成同一个节目的基本流(如一个视频流,多个音频流)的PES包有一个共用的时间基。
TS的包长标准为188bytes.
从上面的定义可以分成三层来看TS/PS。
ES层 : 由单独的音频(如mp3),视频流(如h.264)组成基本的ES(Elementary Stream)。
PES层 : 将基本的ES按一定的规则(如H.264以AU)进行封装,并打上时间戳,组成PES。
TS/PS层: 将PES包进行切分后再封装成188bytes大小的TS包,
同时还将一些节目信息也封装成TS包(称为section), 两者共同组成TS层。
从上面的总结,TS/PS总体上来说,是一种封装格式,用来承载数据。
所以FFmpeg
将TS/PS的解析文件定义在libavformat/mpegts.c文件中
将音频,视频的解码定义在libavcodec/mpeg12.c文件中
下面来看FFmpeg是如何进行TS的demuxer的。
1. MPEG2-TS的demuxer函数
- AVInputFormat ff_mpegts_demuxer = {
- "mpegts",
- NULL_IF_CONFIG_SMALL("MPEG-2 transport stream format"),
- sizeof(MpegTSContext),
- mpegts_probe,
- mpegts_read_header,
- mpegts_read_packet,
- mpegts_read_close,
- read_seek,
- mpegts_get_pcr,
- .flags = AVFMT_SHOW_IDS|AVFMT_TS_DISCONT,
- #ifdef USE_SYNCPOINT_SEARCH
- .read_seek2 = read_seek2,
- #endif
- };
2. 解析流中的TS格式
- /*
- * 出现3种格式,主要原因是:
- * TS标准是 188Bytes;
- * 日本标准是192Bytes的DVH-S格式;
- * 第三种的 204Bytes则是在188Bytes的基础上,加上16Bytes的FEC(前向纠错).
- */
- #define TS_PACKET_SIZE 188
- #define TS_DVHS_PACKET_SIZE 192
- #define TS_FEC_PACKET_SIZE 204
- #define TS_MAX_PACKET_SIZE 204
- //< maximum score, half of that is used for file-extension-based detection
- #define AVPROBE_SCORE_MAX 100
- /*
- * 函数功能:
- * 分析流中是三种TS格式的哪一种
- */
- static int mpegts_probe(AVProbeData *p)
- {
- #define CHECK_COUNT 10
- const int size= p->buf_size;
- int score, fec_score, dvhs_score;
- int check_count= size / TS_FEC_PACKET_SIZE;
- if (check_count < CHECK_COUNT)
- return -1;
- score = analyze(p->buf, TS_PACKET_SIZE *check_count, TS_PACKET_SIZE , NULL)
- * CHECK_COUNT / check_count;
- dvhs_score= analyze(p->buf, TS_DVHS_PACKET_SIZE*check_count, TS_DVHS_PACKET_SIZE, NULL)
- * CHECK_COUNT / check_count;
- fec_score = analyze(p->buf, TS_FEC_PACKET_SIZE *check_count, TS_FEC_PACKET_SIZE , NULL)
- * CHECK_COUNT / check_count;
- /*
- * 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;
- }
- /*
- * 函数功能:
- * 在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 x=0;
- int best_score=0;
- memset(stat, 0, packet_size*sizeof(int));
- for (x=i=0; i < size-3; 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;
- }
buf[i] == 0x47
其中的sync_byte固定为0x47,即上面的.
!(buf[i+1] & 0x80)
由于transport_error_indicator为1的TS Packet实际有错误,
表示携带的数据无意义, 这样的Packet显然没什么意义.
buf[i+3] & 0x30
对于adaptation_field_control, 如果取值为0x00,则表示为未来保留,现在不用.
这就是MPEG TS的侦测过程.
3. MPEG2-TS头解析
- #define NB_PID_MAX 8192
- #define MAX_SECTION_SIZE 4096
- /* pids */
- #define PAT_PID 0x0000
- #define SDT_PID 0x0011
- /* table ids */
- #define PAT_TID 0x00
- #define PMT_TID 0x02
- #define SDT_TID 0x42
- /*
- * 函数功能:
- *
- */
- 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中读取sizeof(buf)个字节到buf
- if (len != sizeof(buf))
- goto fail;
- /*
- * 获得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;
- 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的过滤器。
- */
- mpegts_open_section_filter(ts, SDT_PID, sdt_cb, ts, 1);
- mpegts_open_section_filter(ts, PAT_PID, pat_cb, ts, 1);
- /*
- */
- handle_packets(ts, s->probesize / ts->raw_packet_size);
- /* if could not find service, enable auto_guess */
- ts->auto_guess = 1;
- av_dlog(ts->stream, "tuning done\n");
- s->ctx_flags |= AVFMTCTX_NOHEADER;
- }
- else
- {
- ...
- }
- avio_seek(pb, pos, SEEK_SET);
- return 0;
- fail:
- return -1;
- }
- MpegTSFilter *mpegts_open_section_filter(MpegTSContext* ts,
- unsigned int pid,
- SectionCallback* section_cb,
- void* opaque,
- int check_crc)
- {
- MpegTSFilter *filter;
- MpegTSSectionFilter *sec;
- av_dlog(ts->stream, "Filter: pid=0x%x\n", pid);
- 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。
为什么NB_PID_MAX 是 8192,
需要看TS的语法结构(ISO/IEC 138138-1 page 19):
- 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<N;i++)
- {
- data_byte 8 bslbf
- }
- }
- }
而8192,是2^13=8192(PID)的最大数目,
为什么会有PES和Section的区分,更详细的可以参考ISO/IEC-13818-1.
挂载上了两种section过滤器,如下:
=========================================================================
PID |Section Name |Callback
=========================================================================
SDT_PID(0x0011) |ServiceDescriptionTable|sdt_cb
| |
PAT_PID(0x0000) |ProgramAssociationTable|pat_cb
=========================================================================
设计成回调函数,是为了在后面使用。
4. MPEG2-TS的包处理
- int handle_packets(MpegTSContext *ts, int nb_packets)
- {
- AVFormatContext *s = ts->stream;
- uint8_t packet[TS_PACKET_SIZE];
- int packet_num, ret;
- ts->stop_parse = 0;
- packet_num = 0;
- for ( ; ; )
- {
- packet_num++;
- if (nb_packets != 0 && packet_num >= nb_packets ||
- ts->stop_parse > 1)
- {
- ret = AVERROR(EAGAIN);
- break;
- }
- if (ts->stop_parse > 0)
- break;
- ret = read_packet(s, packet, ts->raw_packet_size);
- if (ret != 0)
- return ret;
- ret = handle_packet(ts, packet);
- if (ret != 0)
- return ret;
- }
- return 0;
- }
它的代码结构很简单:
handle_packets()
|
+->read_packet()
|
+->handle_packet()
|
+->write_section_data()
read_packet(), 很简单, 就是去找sync_byte(0x47),
handle_packet(),是真正处理数据的地方.它的代码如下:
- /*
- * 功能: handle one TS packet
- */
- int handle_packet(MpegTSContext *ts, const uint8_t *packet)
- {
- AVFormatContext *s = ts->stream;
- MpegTSFilter *tss;
- int len, pid, cc, expected_cc, cc_ok, afc, is_start;
- const uint8_t *p, *p_end;
- int64_t pos;
- /* 获取该包的PID */
- pid = AV_RB16(packet + 1) & 0x1fff;
- if (pid && discard_pid(ts, pid))
- return 0;
- /*
- * 是否是PES或者Section的开头
- * 即syntax element: 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);
- tss = ts->pids[pid];
- }
- if (!tss)
- return 0;
- /*
- * continuity check (currently not used)
- * 虽然检查,但不利用检查的结果
- */
- cc = (packet[3] & 0xf);
- expected_cc = (packet[3] & 0x10) ? (tss->last_cc + 1) & 0x0f : tss->last_cc;
- cc_ok = (tss->last_cc < 0) || (expected_cc == 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
- * =======================================================
- */
- 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)
- {
- /*
- * 跳过 adapation field
- * p[0]对应的语法元素为: adaptation_field_length
- */
- p += p[0] + 1;
- }
- /*
- * if past the end of packet, ignore
- * p已近到达TS包中的有效负载的地方
- */
- p_end = packet + TS_PACKET_SIZE;
- if (p >= p_end)
- return 0;
- pos = avio_tell(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 present */
- len = *p++;
- if (p + len > p_end)
- return 0;
- if (len && cc_ok)
- {
- /*
- * write remaining section bytes
- * TS包的负载部分由Section A的End部分和Section B的Start组成,
- * 先把Section A的End部分写入
- */
- write_section_data(s, tss, p, len, 0);
- /* check whether filter has been closed */
- if (!ts->pids[pid])
- return 0;
- }
- p += len;
- if (p < p_end)
- {
- /*
- * 再将Section B的Start部分写入
- */
- write_section_data(s, tss, p, p_end - p, 1);
- }
- }
- else
- {
- /* TS包负载仅是一个Section的中间部分部分,将其写入*/
- if (cc_ok)
- {
- write_section_data(s, tss, p, p_end - p, 0);
- }
- }
- }
- else
- {
- int ret;
- /*
- * 如果是PES类型,直接调用其Callback,
- * 但显然,只有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.
5. 节目指定信息的解析
- /*
- * PAT(Program Association Table) 节目相关表
- * 提供了节目号与PID值的对应关系
- * 见ISO/IEC 13818-1 2.4.4.3 Table 2-30
- */
- void pat_cb(MpegTSFilter *filter, const uint8_t *section, int section_len);
- /*
- * PMT(Program Map Table) 节目映射表
- * 提供了节目号与组成节目的元素之间的映射关系--或者称为"节目定义"
- * 见ISO/IEC 13818-1 2.4.4.8 Table 2-33
- */
- void pmt_cb(MpegTSFilter *filter, const uint8_t *section, int section_len);
- /*
- * SDT(Transport Stream Description Table) TS描述表
- * 用于定义TS描述子的表
- * 见ISO/IEC 13818-1 2.4.4.12 Table 2-36
- */
- void sdt_cb(MpegTSFilter *filter, const uint8_t *section, int section_len)
6. 解析PES包
- /*
- * 见ISO/IEC 13818-1 2.4.3.6 Table 2-21
- */
- int mpegts_push_data(MpegTSFilter* filter,
- const uint8_t* buf,
- int buf_size,
- int is_start,
- int64_t pos);
至此,整个TS层的解析基本完成。
提高技能如同提升自信心。