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

一、引言

从《音视频入门基础: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.rhkb.cn/news/502457.html

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

相关文章

记一次k8s下容器启动失败,容器无日志问题排查

问题 背景 本地开发时&#xff0c;某应用增加logback-spring.xml配置文件&#xff0c;加入必要的依赖&#xff1a; <dependency><groupId>net.logstash.logback</groupId><artifactId>logstash-logback-encoder</artifactId><version>8…

【问题记录】npm create vue@latest报错

1&#xff0c;错误日志 npm error code EPERM npm error syscall mkdir npm error path D:\Program Files\nodejs\node_cache\_cacache npm error errno EPERM npm error FetchError: Invalid response body while trying to fetch https://registry.npmjs.org/create-vue: EP…

JavaWeb开发(六)XML介绍

1. XML介绍 1.1. 什么是XML &#xff08;1&#xff09;XML 指可扩展标记语言(EXtensible Markup Language)XML 是一种很像HTML的标记语言。   &#xff08;2&#xff09;XML 的设计宗旨是传输数据(目前主要是作为配置文件)&#xff0c;而不是显示数据。   &#xff08;3&a…

Vue3实现PDF在线预览功能

​&#x1f308;个人主页&#xff1a;前端青山 &#x1f525;系列专栏&#xff1a;Vue篇 &#x1f516;人终将被年少不可得之物困其一生 依旧青山,本期给大家带来Vue篇专栏内容:Vue3现PDF在线预览功能 前言 在开发中&#xff0c;PDF预览和交互功能是一个常见的需求。无论是管理…

win32汇编环境,在窗口程序中画简单图形

;运行效果 ;win32汇编环境&#xff0c;在窗口程序中画简单图形 ;展示点、直线、圆、椭圆、正方形、矩形、圆角矩形的画法 ;将代码复制进radasm软件里&#xff0c;直接编译可运行.重要部分加备注。 ;>>>>>>>>>>>>>>>>>>&…

[7种方法] 如何修复iPad无法连接到App Store

“现在已经 3 天了&#xff0c;我无法从 iPad 连接到 App Store。起初&#xff0c;我以为这是服务器问题&#xff0c;但已经过去 3 天了&#xff0c;问题仍然存在。请帮助我&#xff01;” 如果您的iPad 无法连接到 App Store&#xff0c;可能有多种原因。即使是日期和时间设置…

weblogic安装 12.2.1.4.0 单机

fmw_12.2.1.4.0_wls.jar和jdk-8u341-linux-x64.tar.gz&#xff0c;安装weblogic 安装JDK 8 # 1. 创建Java安装目录 mkdir -p /usr/java cd /usr/java# 2. 解压JDK tar -zxvf jdk-8u341-linux-x64.tar.gz# 3. 配置环境变量 cat >> /etc/profile << EOF export JAV…

vue elementUI Plus实现拖拽流程图,不引入插件,纯手写实现。

vue elementUI Plus实现拖拽流程图&#xff0c;不引入插件&#xff0c;纯手写实现。 1.设计思路&#xff1a;2.设计细节3.详细代码实现 1.设计思路&#xff1a; 左侧button列表是要拖拽的组件。中间是拖拽后的流程图。右侧是拖拽后的数据列表。 我们拖动左侧组件放入中间的流…

SpringBoot+Vue养老院管理系统设计与实现【开题报告+程序+安装部署+售后讲解】

【1】系统介绍 一、研究背景 随着中国人口老龄化的加剧&#xff0c;养老问题已成为社会关注的热点话题。传统的养老服务模式难以满足日益增长的老年人群体的需求&#xff0c;尤其是在信息化和智能化方面存在明显不足。为了提高养老服务效率&#xff0c;改善老年人生活质量&am…

【网络安全 | 漏洞挖掘】通过模拟功能实现提权(Bugcrowd)

未经许可,不得转载。 我将与大家分享我在 Bugcrowd 的某个项目中发现的一个漏洞,该项目中有一个“用户模拟”功能。 什么是用户模拟? 用户模拟允许管理员在不知晓用户凭据的情况下“以用户身份登录”。这种功能常见于管理员需要调试问题、审查用户权限或解决投诉的平台中。…

ValuesRAG:以检索增强情境学习强化文化对齐

随着大型语言模型&#xff08;LLMs&#xff09;的迅猛发展&#xff0c;其在各个领域展现出强大的能力。然而&#xff0c;训练数据中西方中心主义的倾向&#xff0c;使得 LLMs 在文化价值观一致性方面面临严峻挑战&#xff0c;这一问题在跨文化场景中尤为突出&#xff0c;可能导…

【CSS】第二天 画盒子、文字控制属性

【CSS】第二天 1. 画盒子2. 文字控制属性2.1 字体大小2.2 字体粗细2.3 字体样式是否倾斜2.4 行高2.5 行高-垂直居中2.6 字体族2.7 字体复合属性 font 1. 画盒子 目标&#xff1a;使用合适的选择器画盒子。 新属性 <!DOCTYPE html> <html> <head><meta …

东京大学联合Adobe提出基于指令的图像编辑模型InstructMove,可通过观察视频中的动作来实现基于指令的图像编辑。

东京大学联合Adobe提出的InstructMove是一种基于指令的图像编辑模型&#xff0c;使用多模态 LLM 生成的指令对视频中的帧对进行训练。该模型擅长非刚性编辑&#xff0c;例如调整主体姿势、表情和改变视点&#xff0c;同时保持内容一致性。此外&#xff0c;该方法通过集成蒙版、…

一文讲明白朴素贝叶斯算法及其计算公式(入门普及)

1、贝叶斯算法 贝叶斯定理由英国数学家托马斯贝叶斯 ( Thomas Bayes) 提出的&#xff0c;用来描述两个条件概率之间的关系。通常&#xff0c;事件A在事件B 发生的条件下与事件 B 在事件 A 发生的条件下&#xff0c;它们两者的概率并不相同&#xff0c;但是它们两者之间存在一定…

SpringBoot Maven 项目 pom 中的 plugin 插件用法整理

把 SpringBoot Maven 项目打包成 jar 文件时&#xff0c;我们通常用到 spring-boot-maven-plugin 插件。 前面也介绍过&#xff0c;在 spring-boot-starter-parent POM 和 spring-boot-starter POM 中都有插件的管理&#xff0c;现在我们就撸一把构建元素中插件的用法。 一、…

PDF文件提示-文档无法打印-的解决办法

背景信息 下载了几个签名的PDF文件&#xff0c;想要打印纸质版&#xff0c;结果打印时 Adobe Acrobat Reader 提示【文档无法打印】: 解决办法 网上的方案是使用老版本的PDF阅读器&#xff0c; 因为无法打印只是一个标识而已。 PDF文件不能打印的五种解决方案-zhihu 这些方…

SQL Server 数据库 忘记密码

1、先用windows 身份验证 连接 2、安全性--登录名 3、设置 身份验证 4、重启电脑 5、登录 登陆成功!!! ------------------------------------------------------------------ --1、查询登录账号信息 ------------------------------------------------------------------ -- …

oscp备考 oscp系列——Kioptix Level 1靶场 古老的 Apache Vuln

目录 前言 1. 主机发现 2. 端口扫描 3. 指纹识别 4. 目录扫描 5. 漏洞搜索和利用 前言 oscp备考&#xff0c;oscp系列——Kioptix Level 1靶场 Kioptix Level 1难度为简单靶场&#xff0c;主要考察 nmap的使用已经是否会看输出&#xff0c;以及是否会通过应用查找对应漏…

OpenGL材质系统和贴图纹理

上一篇文章当中笔者为大家介绍了风氏光照模型&#xff0c;相信大家也发现了光照着色器当中有设置有很多控制光照强度的参数&#xff0c;而所谓的材质系统就是我们可以人为的去调节这些参数&#xff0c;让一个物体的反光效果能够更加接近我们现实生活当中的一些物体。 材质系统…

光缆生产车间可视化,让智能制造更透明

图扑光缆生产车间可视化系统通过实时监控和数据分析&#xff0c;优化生产流程&#xff0c;提升产品质量和产能效率&#xff0c;为光缆制造业提供智能化解决方案&#xff0c;推动产业升级。