音视频入门基础:MPEG2-PS专题(5)——FFmpeg源码中,解析PS流中的PES流的实现

=================================================================

音视频入门基础:MPEG2-PS专题系列文章:

音视频入门基础:MPEG2-PS专题(1)——MPEG2-PS官方文档下载

音视频入门基础:MPEG2-PS专题(2)——使用FFmpeg命令生成ps文件

音视频入门基础:MPEG2-PS专题(3)——MPEG2-PS格式简介

音视频入门基础:MPEG2-PS专题(4)——FFmpeg源码中,判断某文件是否为PS文件的实现

音视频入门基础:MPEG2-PS专题(5)——FFmpeg源码中,解析PS流中的PES流的实现

音视频入门基础:MPEG2-PS专题(6)——FFmpeg源码中,获取PS流的视频信息的实现

音视频入门基础:MPEG2-PS专题(7)——通过FFprobe显示PS流每个packet的信息

=================================================================

一、引言

从《音视频入门基础: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(p+1, "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 isn't 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 can't be helped* if we can't 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流的实现》。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/65591.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

国标GB28181-2022视频平台EasyGBS小知识:局域网ip地址不够用怎么解决?

在局域网中&#xff0c;IP地址不足的问题通常不会在小型网络中出现&#xff0c;但在拥有超过255台设备的大型局域网中&#xff0c;就需要考虑如何解决IP地址不够用的问题了。 在企业局域网中&#xff0c;经常会出现私有IP地址如192.168.1.x到192.168.1.255不够用的情况。由于0…

spring boot启动源码分析(三)之Environment准备

上一篇《spring-boot启动源码分析&#xff08;二&#xff09;之SpringApplicationRunListener》 环境介绍&#xff1a; spring boot版本&#xff1a;2.7.18 主要starter:spring-boot-starter-web 本篇开始讲启动过程中Environment环境准备&#xff0c;Environment是管理所有…

springmvc前端传参,后端接收

RequestMapping注解 Target({ElementType.METHOD, ElementType.TYPE}) Retention(RetentionPolicy.RUNTIME) Documented Mapping public interface RequestMapping {String name() default "";AliasFor("path")String[] value() default {};AliasFor(&quo…

【Excel/WPS】根据平均值,生成两列/多列指定范围的随机数/随机凑出两列数据

原理就是通过随机生成函数和平均值函数。 适用场景&#xff1a;在总体打分后&#xff0c;需要在小项中随机生成小分数 第一列&#xff1a;固定的平均值A2第二列&#xff1a; RANDBETWEEN(A2-10,A210)第三列&#xff1a;根据第二列用平均值函数算除 A2*2-B2这是随机值1的公式&am…

运动相机拍摄的视频打不开怎么办

3-10 GoPro和大疆DJI运动相机的特点&#xff0c;小巧、高清、续航长、拍摄稳定&#xff0c;很多人会在一些重要场合用来拍摄视频&#xff0c;比如可以用来拿在手里拍摄快速运动中的人等等。 但是毕竟是电子产品&#xff0c;有时候是会出点问题的&#xff0c;比如意外断电、摔重…

智能化文档开发(DI)

这个文档涉及到多模态&#xff08;文本、发票、订单、语音&#xff09; 对于普通的文本&#xff0c;我们希望对某些实体的某些属性挖空生成文档模版&#xff0c;并根据预设字段填空最后生成正式文件对于发票、订单&#xff0c;我们想提取它的字段信息&#xff0c;写入DB对于一些…

【轻松学C:编程小白的大冒险】--- C语言简介 02

在编程的艺术世界里&#xff0c;代码和灵感需要寻找到最佳的交融点&#xff0c;才能打造出令人为之惊叹的作品。而在这座秋知叶i博客的殿堂里&#xff0c;我们将共同追寻这种完美结合&#xff0c;为未来的世界留下属于我们的独特印记。 【轻松学C&#xff1a;编程小白的大冒险】…

零基础 监控数据可视化 Spring Boot 2.x(Actuator + Prometheus + Grafana手把手) (上)

一、安装Prometheus Releases prometheus/prometheus GitHubhttps://github.com/prometheus/prometheus/releases 或 https://prometheus.io/download/https://prometheus.io/download/ 1. 下载适用于 Windows 的二进制文件&#xff1a; 找到最新版本的发布页面&#xf…

Idea日志乱码

问题描述 前提&#xff1a;本人使用windows Idea运行sh文件&#xff0c;指定了utf-8编码&#xff0c;但是运行过程中还是存在中文乱码 Idea的相关配置都已经调整 字体调整为雅黑 文件编码均调整为UTF-8 调整Idea配置文件 但是还是存在乱码&#xff0c;既然Idea相关配置已经…

MySQL 数据表与索引设计艺术:打造高效数据存取架构

&#x1f407;明明跟你说过&#xff1a;个人主页 &#x1f3c5;个人专栏&#xff1a;《MySQL技术精粹》&#x1f3c5; &#x1f516;行路有良友&#xff0c;便是天堂&#x1f516; 目录 一、引言 1、什么是MySQL 2、MySQL适用场景 二、MySQL的数据存储与检索 1、数据表…

安卓硬件加速hwui

安卓硬件加速 本文基于安卓11。 从 Android 3.0 (API 级别 11) 开始&#xff0c;Android 2D 渲染管道支持硬件加速&#xff0c;这意味着在 View 的画布上执行的所有绘图操作都使用 GPU。由于启用硬件加速所需的资源增加&#xff0c;你的应用程序将消耗更多内存。 软件绘制&am…

海信116英寸RGB-Mini LED:一朵绽放在科技穹顶的中国花火

东方古镇的打铁花&#xff0c;拉斯维加斯的烟花秀&#xff0c;盛大的花火表演总会在岁末年初的时候&#xff0c;吸引世界各地人们的目光。一年一度的科技展会&#xff0c;也起到烟花秀一样的作用&#xff0c;让人们提前望见未知的精彩。 CES还没开始&#xff0c;CES 2025展会的…

积分漏斗模型中5个指标统计

缘起 最近遇到一个积分漏斗模型的设计&#xff0c;这里记录一下。以防止以后忘记了。其中毕竟关键的属性是&#xff1a; 获得积分可用积分已有积分 积分漏斗模型 这里随着【当前日期】也就是今天日期。随着时间一天天过去&#xff0c;积分也一天天过去。上面那个【填报时间】…

Ubuntu挂载Windows 磁盘,双系统

首先我们需要在终端输入这个命令&#xff0c;来查看磁盘分配情况 lsblk -f 找到需要挂载的磁盘&#xff0c;检查其类型&#xff08; 我的/dev/nvme2n1p1类型是ntfs&#xff0c;名字叫3500winData&#xff09; 然后新建一个挂载磁盘的目录&#xff0c;我的是/media/zeqi/3500wi…

Web渗透测试之XSS跨站脚本攻击 跨域是什么?同源机制又是什么? cors以及Jsonp是什么 一篇文章给你说明白

目录 Cookie的Httponly属性和逃过方式 浏览器同源机制 cors跨域和jsonp跨域和跨域标签 Cors跨域 - 跨源 Jsonp 跨域 jsonp跨域原理&#xff1a; 说明: Cookie的Httponly属性和逃过方式 Xss攻击手段 最常用的目的获取cookie Cookie中设置了 httponlyTrue 方式js操作获…

【C++】字符串的 += 和 + 运算详解

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C 文章目录 &#x1f4af;前言&#x1f4af;1. 字符串的 和 基本用法1.1 的用法1.2 的用法 &#x1f4af;2. 示例代码的剖析与解释代码分析 &#x1f4af;3. 底层实现与性能分析3.1 的实现原理3.2 的实现原理3.…

CCLINK转MODBUS-TCP协议转换网关模块应用案例

大家好&#xff0c;今天我们要聊的是生产管理系统中的CCLINK和MODBUS-TCP协议&#xff0c;它们的不同使得数据互通比较困难&#xff0c;但捷米特JM-CCLK-TCP网关的出现改变了这一切。 为了实现整个生产线的协同工作&#xff0c;需要这些设备之间能够进行有效的数据交换和指令传…

Go学习:多重赋值与匿名变量

1. 变量的多重赋值 1.1 基本语法格式 go语言中&#xff0c;可以将多个赋值语句 合并成 一句&#xff0c;比如&#xff1a; a : 10 b : 20 c : 30//a,b,c三个变量的赋值语句可以简练成以下格式a, b, c : 10, 20, 30 1.2 交换变量值 当需要交换两个变量的值时&#…

Spring——依赖注入之p命名空间和c命名空间

p命名空间 其实就是Set注入 只不过p命名空间写法更简洁 p可以理解为 property标签的首字母p p命名空间依赖于set方法 依赖引入 使用前需要再配置文件头文件中引入p命名空间的依赖&#xff1a; ** xmlns:p“http://www.springframework.org/schema/p” ** 用法 在bean标签…

【Linux】Linux常见指令(上)

个人主页~ 初识Linux 一、Linux基本命令1、ls指令2、pwd命令3、cd指令4、touch指令5、mkdir指令6、rmdir指令7、rm指令8、man指令9、cp指令10、mv命令 Linux是一个开源的、稳定的、安全的、灵活的操作系统&#xff0c;Linux下的操作都是通过指令来实现的 一、Linux基本命令 先…