深圳网站营销公司,门户网站开发平台,中国建设银行龙卡网站,同江佳木斯网站制作一、引言
从《音视频入门基础#xff1a;MPEG2-PS专题#xff08;3#xff09;——MPEG2-PS格式简介》中可以知道#xff0c;PS流由一个个pack#xff08;包装#xff09;组成。一个pack 一个pack_header 一个或多个PES_packet。pack_header中还可能存在system header…一、引言
从《音视频入门基础MPEG2-PS专题3——MPEG2-PS格式简介》中可以知道PS流由一个个pack包装组成。一个pack 一个pack_header 一个或多个PES_packet。pack_header中还可能存在system header。
但是pack_header和system header中并没有什么重要信息所以FFmpeg源码在解析PS流时会跳过pack_header和system header直接解析PES packet。
FFmpeg源码中通过mpegps_read_pes_header函数解析PS流中的PES packet。 二、mpegps_read_pes_header函数的定义
mpegps_read_pes_header函数定义在FFmpeg源码本文演示用的FFmpeg源码版本为7.0.1的源文件libavformat/mpeg.c中
/* read the next PES header. Return its position in ppos* (if not NULL), and its start code, pts and dts.*/
static int mpegps_read_pes_header(AVFormatContext *s,int64_t *ppos, int *pstart_code,int64_t *ppts, int64_t *pdts)
{MpegDemuxContext *m s-priv_data;int len, size, startcode, c, flags, header_len;int pes_ext, ext2_len, id_ext, skip;int64_t pts, dts;int64_t last_sync avio_tell(s-pb);error_redo:avio_seek(s-pb, last_sync, SEEK_SET);
redo:/* next start code (should be immediately after) */m-header_state 0xff;size MAX_SYNC_SIZE;startcode find_next_start_code(s-pb, size, m-header_state);last_sync avio_tell(s-pb);if (startcode 0) {if (avio_feof(s-pb))return AVERROR_EOF;// FIXME we should remember header_statereturn FFERROR_REDO;}if (startcode PACK_START_CODE)goto redo;if (startcode SYSTEM_HEADER_START_CODE)goto redo;if (startcode PADDING_STREAM) {avio_skip(s-pb, avio_rb16(s-pb));goto redo;}if (startcode PRIVATE_STREAM_2) {if (!m-sofdec) {/* Need to detect whether this from a DVD or a Sofdec stream */int len avio_rb16(s-pb);int bytesread 0;uint8_t *ps2buf av_malloc(len);if (ps2buf) {bytesread avio_read(s-pb, ps2buf, len);if (bytesread ! len) {avio_skip(s-pb, len - bytesread);} else {uint8_t *p 0;if (len 6)p memchr(ps2buf, S, len - 5);if (p)m-sofdec !memcmp(p1, ofdec, 5);m-sofdec - !m-sofdec;if (m-sofdec 0) {if (len 980 ps2buf[0] 0) {/* PCI structure? */uint32_t startpts AV_RB32(ps2buf 0x0d);uint32_t endpts AV_RB32(ps2buf 0x11);uint8_t hours ((ps2buf[0x19] 4) * 10) (ps2buf[0x19] 0x0f);uint8_t mins ((ps2buf[0x1a] 4) * 10) (ps2buf[0x1a] 0x0f);uint8_t secs ((ps2buf[0x1b] 4) * 10) (ps2buf[0x1b] 0x0f);m-dvd (hours 23 mins 59 secs 59 (ps2buf[0x19] 0x0f) 10 (ps2buf[0x1a] 0x0f) 10 (ps2buf[0x1b] 0x0f) 10 endpts startpts);} else if (len 1018 ps2buf[0] 1) {/* DSI structure? */uint8_t hours ((ps2buf[0x1d] 4) * 10) (ps2buf[0x1d] 0x0f);uint8_t mins ((ps2buf[0x1e] 4) * 10) (ps2buf[0x1e] 0x0f);uint8_t secs ((ps2buf[0x1f] 4) * 10) (ps2buf[0x1f] 0x0f);m-dvd (hours 23 mins 59 secs 59 (ps2buf[0x1d] 0x0f) 10 (ps2buf[0x1e] 0x0f) 10 (ps2buf[0x1f] 0x0f) 10);}}}av_free(ps2buf);/* If this isnt a DVD packet or no memory* could be allocated, just ignore it.* If we did, move back to the start of the* packet (plus length field) */if (!m-dvd || avio_skip(s-pb, -(len 2)) 0) {/* Skip back failed.* This packet will be lost but that cant be helped* if we cant skip back*/goto redo;}} else {/* No memory */avio_skip(s-pb, len);goto redo;}} else if (!m-dvd) {int len avio_rb16(s-pb);avio_skip(s-pb, len);goto redo;}}if (startcode PROGRAM_STREAM_MAP) {mpegps_psm_parse(m, s-pb);goto redo;}/* find matching stream */if (!((startcode 0x1c0 startcode 0x1df) ||(startcode 0x1e0 startcode 0x1ef) ||(startcode 0x1bd) ||(startcode PRIVATE_STREAM_2) ||(startcode 0x1fd)))goto redo;if (ppos) {*ppos avio_tell(s-pb) - 4;}len avio_rb16(s-pb);pts dts AV_NOPTS_VALUE;if (startcode ! PRIVATE_STREAM_2){/* stuffing */for (;;) {if (len 1)goto error_redo;c avio_r8(s-pb);len--;/* XXX: for MPEG-1, should test only bit 7 */if (c ! 0xff)break;}if ((c 0xc0) 0x40) {/* buffer scale size */avio_r8(s-pb);c avio_r8(s-pb);len - 2;}if ((c 0xe0) 0x20) {dts pts get_pts(s-pb, c);len - 4;if (c 0x10) {dts get_pts(s-pb, -1);len - 5;}} else if ((c 0xc0) 0x80) {/* mpeg 2 PES */flags avio_r8(s-pb);header_len avio_r8(s-pb);len - 2;if (header_len len)goto error_redo;len - header_len;if (flags 0x80) {dts pts get_pts(s-pb, -1);header_len - 5;if (flags 0x40) {dts get_pts(s-pb, -1);header_len - 5;}}if (flags 0x3f header_len 0) {flags 0xC0;av_log(s, AV_LOG_WARNING, Further flags set but no bytes left\n);}if (flags 0x01) { /* PES extension */pes_ext avio_r8(s-pb);header_len--;/* Skip PES private data, program packet sequence counter* and P-STD buffer */skip (pes_ext 4) 0xb;skip skip 0x9;if (pes_ext 0x40 || skip header_len) {av_log(s, AV_LOG_WARNING, pes_ext %X is invalid\n, pes_ext);pes_ext skip 0;}avio_skip(s-pb, skip);header_len - skip;if (pes_ext 0x01) { /* PES extension 2 */ext2_len avio_r8(s-pb);header_len--;if ((ext2_len 0x7f) 0) {id_ext avio_r8(s-pb);if ((id_ext 0x80) 0)startcode ((startcode 0xff) 8) | id_ext;header_len--;}}}if (header_len 0)goto error_redo;avio_skip(s-pb, header_len);} else if (c ! 0xf)goto redo;}if (startcode PRIVATE_STREAM_1) {int ret ffio_ensure_seekback(s-pb, 2);if (ret 0)return ret;startcode avio_r8(s-pb);m-raw_ac3 0;if (startcode 0x0b) {if (avio_r8(s-pb) 0x77) {startcode 0x80;m-raw_ac3 1;avio_skip(s-pb, -2);} else {avio_skip(s-pb, -1);}} else {len--;}}if (len 0)goto error_redo;if (dts ! AV_NOPTS_VALUE ppos) {int i;for (i 0; i s-nb_streams; i) {if (startcode s-streams[i]-id (s-pb-seekable AVIO_SEEKABLE_NORMAL) /* index useless on streams anyway */) {ff_reduce_index(s, i);av_add_index_entry(s-streams[i], *ppos, dts, 0, 0,AVINDEX_KEYFRAME /* FIXME keyframe? */);}}}*pstart_code startcode;*ppts pts;*pdts dts;return len;
}
该函数的作用就是解析PS流中的一个PES packet将其PES packet header里面的信息解析出来。 形参s既是输入型参数也是输出型参数指向一个AVFormatContext类型的变量。s-pb包含需要被解析的PS流的二进制数据。mpegps_read_pes_header函数解析的是s-pb-buf_ptr指向的PS流数据中的下一个PES packet。 形参ppos输出型参数*ppos为读取到的PES packet相对于文件首的偏移字节数。 形参pstart_code输出型参数。如果读取到了PES packet且该PES packet里面包含的不是的private_stream_1*pstart_code为其PES packet header中的stream_id属性的值加0x100。 形参ppts输出型参数。*ppts为从该PES packet的PES packet header中读取到的pts。 形参pdts输出型参数。*pdts为从该PES packet的PES packet header中读取到的dts。 返回值解析成功返回该PES packet去掉PES packet header后的大小即基本码流ES数据的大小。解析失败返回一个负数。 三、mpegps_read_pes_header函数的内部实现分析
mpegps_read_pes_header函数中首先通过find_next_start_code函数找到s-pb-buf_ptr指向的PS流数据中的下一个pack header的起始码 或 下一个system header的起始码 或 下一个PES packet header的起始码
redo:/* next start code (should be immediately after) */m-header_state 0xff;size MAX_SYNC_SIZE;startcode find_next_start_code(s-pb, size, m-header_state);last_sync avio_tell(s-pb);if (startcode 0) {if (avio_feof(s-pb))return AVERROR_EOF;// FIXME we should remember header_statereturn FFERROR_REDO;} 如果找到的是pack header的起始码 或 system header的起始码 或 找到的PES packet中包含padding_stream数据通过goto语句跳转然后重新通过find_next_start_code函数查找下一个起始码 if (startcode PACK_START_CODE)goto redo;if (startcode SYSTEM_HEADER_START_CODE)goto redo;if (startcode PADDING_STREAM) {avio_skip(s-pb, avio_rb16(s-pb));goto redo;} 如果找到了符合要求的PES packet header的起始码通过avio_tell函数读取该PES packet相对于文件首的偏移字节数赋值给*ppos。关于avio_tell函数的用法可以参考《FFmpeg源码avio_tell函数分析》 /* find matching stream */if (!((startcode 0x1c0 startcode 0x1df) ||(startcode 0x1e0 startcode 0x1ef) ||(startcode 0x1bd) ||(startcode PRIVATE_STREAM_2) ||(startcode 0x1fd)))goto redo;if (ppos) {*ppos avio_tell(s-pb) - 4;} 读取PES packet header中的PES_packet_length属性赋值给变量len。关于avio_rb16函数的用法可以参考《FFmpeg源码avio_r8、avio_rl16、avio_rl24、avio_rl32、avio_rl64函数分析》 len avio_rb16(s-pb); 读取PES packet header中的PES_scrambling_control、PES_priority、data_alignment_indicator、copyright、original_or_copy属性赋值给变量c c avio_r8(s-pb); 如果上述读取到的PES_packet_length属性后面的值为10(c 0xc0) 0x80为真表示读取到的PES packet header的格式正确 执行大括号内的内容
else if ((c 0xc0) 0x80) {
//...
} 读取PTS_DTS_flags、ESCR_flag、ES_rate_flag、DSM_trick_mode_flag、additional_copy_info_flag、PES_CRC_flag、PES_extension_flag这7个属性赋值给变量flags flags avio_r8(s-pb); 读取PES_header_data_length属性赋值给变量header_len header_len avio_r8(s-pb); 如果PTS_DTS_flags属性的值为10表示PES packet header中会存在PTS读取PTS值为11时表示PES packet header中会同时存在PTS和DTS读取PTS和DTS分别赋值给变量dts和pts if (flags 0x80) {dts pts get_pts(s-pb, -1);header_len - 5;if (flags 0x40) {dts get_pts(s-pb, -1);header_len - 5;}} 如果PES_extension_flag属性的值为1表示PES packet header有PES_extension域读取PES_extension域 if (flags 0x01) { /* PES extension */pes_ext avio_r8(s-pb);header_len--;/* Skip PES private data, program packet sequence counter* and P-STD buffer */skip (pes_ext 4) 0xb;skip skip 0x9;if (pes_ext 0x40 || skip header_len) {av_log(s, AV_LOG_WARNING, pes_ext %X is invalid\n, pes_ext);pes_ext skip 0;}avio_skip(s-pb, skip);header_len - skip;if (pes_ext 0x01) { /* PES extension 2 */ext2_len avio_r8(s-pb);header_len--;if ((ext2_len 0x7f) 0) {id_ext avio_r8(s-pb);if ((id_ext 0x80) 0)startcode ((startcode 0xff) 8) | id_ext;header_len--;}}} 最后返回从该PES packet的PES packet header中读取到的pts、dts、该PES packet去掉PES packet header后的大小等信息 *pstart_code startcode;*ppts pts;*pdts dts;return len; 四、FFmpeg源码中解析TS流中的PES流的实现
FFmpeg源码中解析TS流中的PES流所使用的函数不一样是通过mpegts_push_data函数进行解析的具体可以参考《音视频入门基础MPEG2-TS专题19——FFmpeg源码中解析TS流中的PES流的实现》。