音视频入门基础:WAV专题(10)——FFmpeg源码中计算WAV音频文件每个packet的pts、dts的实现
一、引言
从文章《音视频入门基础:WAV专题(6)——通过FFprobe显示WAV音频文件每个数据包的信息》中我们可以知道,通过FFprobe命令可以打印WAV音频文件每个packet(也称为数据包或多媒体包)的信息,这些信息包含该packet的pts、dts:

打印出来的“pts”实际是AVPacket结构体中的成员变量pts,是以AVStream->time_base为单位的显示时间戳;“dts”实际是AVPacket结构体中的成员变量dts,是以AVStream->time_base为单位的解码时间戳。音频跟视频不一样,音频没有B帧,所以音频的pts和dts输出顺序一样,即pts等于dts。上述的这些值都是通过fftools/ffprobe.c中的show_packet函数打印出来的:
static void show_packet(WriterContext *w, InputFile *ifile, AVPacket *pkt, int packet_idx)
{
//...print_ts ("pts", pkt->pts);
//...print_ts ("dts", pkt->dts);
//...
}
本文讲述上述pts、dts的值是怎样被计算出来的。如果想直接看结论,可以跳到本文的最后,直接看“总结”。
二、FFmpeg源码中计算WAV音频文件每个packet的pts和dts的实现
FFmpeg得到每个packet的pts和dts的过程,实际也是解封装(解复用)的过程。
(一)对FFFormatContext结构体的AVPacket类型成员变量pkt进行初始化
FFmpeg对WAV音频文件进行解封装(解复用)时,首先会调用avformat_alloc_context函数分配解复用器上下文(AVFormatContext)。而avformat_alloc_context函数内部会调用av_packet_alloc函数给FFFormatContext结构体的AVPacket类型成员变量pkt分配内存,对pkt的成员变量进行初始化:
AVFormatContext *avformat_alloc_context(void)
{FFFormatContext *const si = av_mallocz(sizeof(*si));
//...si->pkt = av_packet_alloc();
//...return s;
}
从文章《FFmpeg源码:av_init_packet、get_packet_defaults、av_packet_alloc函数分析》中可以知道,av_packet_alloc函数内部会调用get_packet_defaults函数。所以执行av_packet_alloc函数后,FFFormatContext结构体的成员变量pkt的成员pts、dts的值会变为AV_NOPTS_VALUE,也就是十进制的:-9223372036854775808。
(二)对FFStream结构体的成员变量cur_dts进行初始化
调用完avformat_alloc_context函数后,FFmpeg会调用avformat_open_input函数打开WAV音频文件。而avformat_open_input函数内部会调用wav_read_header函数解码WAV Header,关于wav_read_header函数具体可以参考:《音视频入门基础:WAV专题(5)——FFmpeg源码中解码WAV Header的实现》。然后wav_read_header函数内部又会调用avformat_new_stream函数创建音频流。avformat_new_stream函数内部会执行语句:sti->cur_dts = RELATIVE_TS_BASE对FFStream结构体的成员变量cur_dts进行初始化:
AVStream *avformat_new_stream(AVFormatContext *s, const AVCodec *c)
{FFFormatContext *const si = ffformatcontext(s);FFStream *sti;
//...sti = av_mallocz(sizeof(*sti));
//...if (s->iformat) {
//.../* we set the current DTS to 0 so that formats without any timestamps* but durations get some timestamps, formats with some unknown* timestamps have their first few packets buffered and the* timestamps corrected before they are returned to the user */sti->cur_dts = RELATIVE_TS_BASE;
//...}return NULL;
}
从《FFmpeg源码:RELATIVE_TS_BASE宏定义和is_relative函数分析》中可以知道,RELATIVE_TS_BASE的值为十进制的9223090561878065151,所以执行avformat_new_stream函数后,FFStream结构体的成员变量cur_dts会被初始化为9223090561878065151。
(三)compute_pkt_fields函数
调用完avformat_open_input函数后,FFmpeg会调用avformat_find_stream_info函数读取媒体的部分packet(数据包)以获取码流信息。而avformat_find_stream_info函数内部会调用read_frame_internal函数,read_frame_internal函数内部又会调用compute_pkt_fields函数。通过compute_pkt_fields函数可以获取每个packet的pts和dts:
static void compute_pkt_fields(AVFormatContext *s, AVStream *st,AVCodecParserContext *pc, AVPacket *pkt,int64_t next_dts, int64_t next_pts)
{FFFormatContext *const si = ffformatcontext(s);FFStream *const sti = ffstream(st);int num, den, presentation_delayed, delay;int onein_oneout = st->codecpar->codec_id != AV_CODEC_ID_H264 &&st->codecpar->codec_id != AV_CODEC_ID_HEVC &&st->codecpar->codec_id != AV_CODEC_ID_VVC;
//.../* do we have a video B-frame ? */delay = sti->avctx->has_b_frames;presentation_delayed = 0;
//.../* Interpolate PTS and DTS if they are not present. We skip H264* currently because delay and has_b_frames are not reliably set. */if ((delay == 0 || (delay == 1 && pc)) && onein_oneout) {if (presentation_delayed) {//...}else if (pkt->pts != AV_NOPTS_VALUE ||pkt->dts != AV_NOPTS_VALUE ||pkt->duration > 0 ) {/* presentation is not delayed : PTS and DTS are the same */if (pkt->pts == AV_NOPTS_VALUE)pkt->pts = pkt->dts;update_initial_timestamps(s, pkt->stream_index, pkt->pts,pkt->pts, pkt);if (pkt->pts == AV_NOPTS_VALUE)pkt->pts = sti->cur_dts;pkt->dts = pkt->pts;if (pkt->pts != AV_NOPTS_VALUE && duration.num >= 0)sti->cur_dts = av_add_stable(st->time_base, pkt->pts, duration, 1);}}
//...
}
compute_pkt_fields函数内部,由于音频的压缩编码格式不可能是H.264、HEVC(H.265)、VVC(H.266),所以局部变量onein_oneout的值为1:
int onein_oneout = st->codecpar->codec_id != AV_CODEC_ID_H264 &&st->codecpar->codec_id != AV_CODEC_ID_HEVC &&st->codecpar->codec_id != AV_CODEC_ID_VVC;
音频跟视频不一样,音频没有B帧,所以局部变量delay的值为0。局部变量presentation_delayed的值为0:
/* do we have a video B-frame ? */
delay = sti->avctx->has_b_frames;
presentation_delayed = 0;
所以表达式:(delay == 0 || (delay == 1 && pc)) && onein_oneout为真,执行大括号里的内容:
/* Interpolate PTS and DTS if they are not present. We skip H264* currently because delay and has_b_frames are not reliably set. */if ((delay == 0 || (delay == 1 && pc)) &&onein_oneout) {
从《音视频入门基础:WAV专题(9)——FFmpeg源码中计算WAV音频文件每个packet的duration和duration_time的实现》中可以知道,音频文件的格式正常的情况下,pkt->duration 肯定是大于0的,所以会执行下面大括号里的内容:
else if (pkt->pts != AV_NOPTS_VALUE ||pkt->dts != AV_NOPTS_VALUE ||pkt->duration > 0 ) {/* presentation is not delayed : PTS and DTS are the same */if (pkt->pts == AV_NOPTS_VALUE)pkt->pts = pkt->dts;update_initial_timestamps(s, pkt->stream_index, pkt->pts,pkt->pts, pkt);if (pkt->pts == AV_NOPTS_VALUE)pkt->pts = sti->cur_dts;pkt->dts = pkt->pts;if (pkt->pts != AV_NOPTS_VALUE && duration.num >= 0)sti->cur_dts = av_add_stable(st->time_base, pkt->pts, duration, 1);}
从上面可以知道FFFormatContext结构体的成员变量pkt的成员pts、dts的值会在avformat_alloc_context函数中被av_packet_alloc函数初始化为AV_NOPTS_VALUE,所以会执行下面语句,让pkt->pts = pkt->dts = AV_NOPTS_VALUE:
/* presentation is not delayed : PTS and DTS are the same */if (pkt->pts == AV_NOPTS_VALUE)pkt->pts = pkt->dts;
然后由于pkt->pts等于AV_NOPTS_VALUE,所以会执行pkt->pts = sti->cur_dts:
if (pkt->pts == AV_NOPTS_VALUE)pkt->pts = sti->cur_dts;
pkt->dts = pkt->pts;
下面分情况讨论:
1.第一个packet的pts和dts
从上面可以知道,执行avformat_new_stream函数后,sti->cur_dts会被初始化为RELATIVE_TS_BASE(9223090561878065151)。所以对于第一个packet,其pkt->pts和pkt->dts的值会变为RELATIVE_TS_BASE(9223090561878065151):
if (pkt->pts == AV_NOPTS_VALUE)pkt->pts = sti->cur_dts;
pkt->dts = pkt->pts;
这时候表达式:pkt->pts != AV_NOPTS_VALUE && duration.num >= 0为真,所以执行语句:sti->cur_dts = av_add_stable(st->time_base, pkt->pts, duration, 1),让sti->cur_dts = pkt->pts + (1 × duration ÷ st->time_base)。关于av_add_stable函数的用法可以参考:《FFmpeg源码:av_rescale_rnd、av_rescale_q_rnd、av_rescale_q、av_add_stable函数分析》:
if (pkt->pts != AV_NOPTS_VALUE && duration.num >= 0)sti->cur_dts = av_add_stable(st->time_base, pkt->pts, duration, 1);
从《FFmpeg源码:compute_frame_duration函数分析》中可以知道,duration.num为该音频packet占用的以AVStream的time_base为单位的时间值,duration.den为该音频的采样频率(单位为Hz);从《音视频入门基础:WAV专题(8)——FFmpeg源码中计算WAV音频文件AVStream的time_base的实现》中可以知道st->time_base.num为1,st->time_base.den为音频采样频率;
所以语句sti->cur_dts = pkt->pts + (1 × duration ÷ st->time_base) 等价于
sti->cur_dts = pkt->pts + duration.num。
sti->cur_dts为下一个音频packet的pts和dts,也就是说下一个音频packet的pts和dts的值是在上一个音频packet的pts和dts基础上增加duration.num。
2.第一个packet之后的packet的pts和dts
对于第一个packet之后的packet,比如第二个packet。再次调用compute_pkt_fields函数时,会继续执行语句: pkt->pts = sti->cur_dts,得到sti->cur_dts中保存的下一个packet的dts和pts:
if (pkt->pts == AV_NOPTS_VALUE)pkt->pts = sti->cur_dts;
(四)av_read_frame函数
调用完avformat_find_stream_info函数后,FFmpeg会调用av_read_frame函数从文件中读取数据包。av_read_frame函数内部会执行:
if (is_relative(pkt->dts))pkt->dts -= RELATIVE_TS_BASE;if (is_relative(pkt->pts))pkt->pts -= RELATIVE_TS_BASE;
让该packet的pts和dts减去RELATIVE_TS_BASE(9223090561878065151)。从而得到最终的pts和dts。
三、总结
1.音频跟视频不一样,音频没有B帧,所以音频的pts和dts输出顺序一样,即pts等于dts。
2.对于音频,其第1个packet的pts和dts的值为0。之后的每个packet的pts和dts值在上一个音频packet的pts和dts基础上增加duration,也就是增加该音频packet占用的以AVStream的time_base为单位的时间值。
举个例子,某音频文件,其第1个packet的pts和dts值为0,duration值为4096。所以第2个packet的pts和dts值为0 + 4096 = 4096。第3个packet的pts和dts值为4096 + 4096 = 8192:

关于duration的概念可以参考:《音视频入门基础:WAV专题(9)——FFmpeg源码中计算WAV音频文件每个packet的duration和duration_time的实现》
相关文章:
音视频入门基础:WAV专题(10)——FFmpeg源码中计算WAV音频文件每个packet的pts、dts的实现
一、引言 从文章《音视频入门基础:WAV专题(6)——通过FFprobe显示WAV音频文件每个数据包的信息》中我们可以知道,通过FFprobe命令可以打印WAV音频文件每个packet(也称为数据包或多媒体包)的信息࿰…...
0.91寸OLED屏幕大小的音频频谱,炫酷
(后文有详细介绍) 频谱扫描: 迷你音频频谱——频率扫描 音乐律动: 迷你音频频谱——频率扫描 迷你音频频谱——音乐2 迷你音频频谱——音乐3 一、简介 音频频谱在最小0.91寸OLED 屏幕上显示,小巧玲珑 二、应用场景 本…...
6. LinkedList与链表
一、ArrayList的缺陷 通过源码知道,ArrayList底层使用数组来存储元素,由于其底层是一段连续空间,当在ArrayList任意位置插入或者删除元素时,就需要将后序元素整体往前或者往后搬移,时间复杂度为O(n),效率比…...
Statcounter Global Stats 提供全球统计数据信息
Statcounter Global Stats 提供全球统计数据信息 1. Statcounter Global Stats2. Mobile & Tablet Android Version Market Share WorldwideReferences Statcounter Global Stats https://gs.statcounter.com/ Statcounter Global Stats are brought to you by Statcounte…...
Linux kernel中的dts dtsi dtb dtc dtb.img dtbo.img
1、问题 kernel与hsm会设置一些gpio,但是某些gpio会在kernel与hsm侧共同设置,导致最终的设置结果失败,将kernel侧在dts文件中设置的gpio注释掉之后,发现hsm设置gpio时还是失败 2、问题原因 因为dts文件不仅仅会影响kernel镜像&…...
微信小程序页面制作——个人信息
✅作者简介:2022年博客新星 第八。热爱国学的Java后端开发者,修心和技术同步精进。 🍎个人主页:Java Fans的博客 🍊个人信条:不迁怒,不贰过。小知识,大智慧。 💞当前专栏…...
使用C++11的`std::async`执行异步任务:实战指南
使用C11的std::async执行异步任务:实战指南 在现代软件开发中,异步编程是提高应用程序性能和响应速度的重要手段。C11引入了std::async,使得编写异步任务变得更加简单和直观。本文将详细介绍如何使用std::async执行异步任务,并提…...
【高阶数据结构】B树、B+树、B*树
B树、B树、B*树 1. 常见的搜索结构2. B树概念3. B树的插入分析4. B树的插入实现4.1 B树的节点设计4.2 B树的部分插入实现14.3 B树的查找4.4 B树的部分插入实现24.5 插入key的过程4.7 B树的插入完整代码4.8 B树的简单验证4.9 B树的删除4.10 B树的性能分析 5. B树6. B*树7. 总结8…...
HBuilderx中vue页面引用scss样式
scss为css样式的预编译器,引入了变量、嵌入、混合、集成、引入等功能,相对于css样式,实现了样式的编程,具有更灵活的样式编写模式。 那么在HBuilderx中,“.vue”格式页面如何调用scss样式呢?详细如下&#…...
粒子群算法原理的示例介绍
一:粒子群优化算法的介绍 粒子群优化算法(PSO)是一种基于群体智能的优化算法,于1995年提出。它受到鸟群狩猎行为的启发,通过模拟鸟群或鱼群的社会行为来进行问题的求解。 基本原理 粒子群算法中,每个解决…...
GNU/Linux - Open函数使用的O_CLOEXEC flag
在 Linux 中,“O_CLOEXEC ”标志与 “open ”系统调用一起使用,用于指定在使用 “exec ”系列函数(如 “execve”、“execl ”等)执行新程序时,“open ”返回的文件描述符应自动关闭。 In Linux, the O_CLOEXEC flag i…...
AWQ量化(Activation-aware Weight Quantization)
论文: AWQ: Activation-aware Weight Quantization for On-Device LLM Compression and Acceleration 中文解读: 深入理解AWQ量化技术 - 知乎 (zhihu.com) 动机:端侧设备用LLM,为了减少显存占用量,所以要用INT4量化&am…...
SprinBoot+Vue体育商品推荐的设计与实现
目录 1 项目介绍2 项目截图3 核心代码3.1 Controller3.2 Service3.3 Dao3.4 application.yml3.5 SpringbootApplication3.5 Vue 4 数据库表设计5 文档参考6 计算机毕设选题推荐7 源码获取 1 项目介绍 博主个人介绍:CSDN认证博客专家,CSDN平台Java领域优质…...
【Python基础】Python函数
本文收录于 《Python编程入门》专栏,从零基础开始,分享一些Python编程基础知识,欢迎关注,谢谢! 文章目录 一、前言二、函数的定义与调用三、函数参数3.1 位置参数3.2 默认参数3.3 可变数量参数(或不定长参数…...
【超简单】1分钟解决ppt全文字体一键设置
省流 ppt的全部字体需要在“幻灯片母版”里面,“自定义字体”去设置好标题与正文的字体之后才算全部设置完毕 “视图”---“幻灯片母版” 找到“字体”---“自定义字体” 设置好中文和西文的字体,都可以按照自己的选择来,保存即可 吐槽 之…...
数组与贪心算法——179、56、57、228(2简2中)
179. 最大数(简单) 给定一组非负整数 nums,重新排列每个数的顺序(每个数不可拆分)使之组成一个最大的整数。 注意:输出结果可能非常大,所以你需要返回一个字符串而不是整数。 解法一、自定义比较…...
WireShark过滤器
文章目录 一、WireShark过滤器概念1. 捕获过滤器(Capture Filters)2. 显示过滤器(Display Filters)3. 捕获过滤器与显示过滤器的区别4. 过滤器语法结构实际应用场景 二、WireShark捕获数据包列表1. **No.(序号…...
2024年全新deepfacelive如何对应使用直播伴侣-腾讯会议等第三方软件
# 2024年全新deepfacelive如何对应使用直播伴侣-腾讯会议等第三方软件 前提按照之前的步骤打开deepfacelive正确配置并且在窗口已经输出了换脸后的视频,不懂步骤可以移步 https://doc.youyacao.com/88/2225 ## 首先下载obs并配置 https://obsproject.com/ 通过…...
告别懵逼——前端项目调试与问题排查方法小结
在日常工作中,我们常常会遇到以下两类典型的挑战: 场景一: 接手无文档的老项目 1、情景描述: 你接手了一个历史久远的项目,项目文档缺失,前任开发者已经离开,而你对当前的业务逻辑和代码结构都…...
[数据集][目标检测]肺炎检测数据集VOC+YOLO格式4983张2类别
数据集格式:Pascal VOC格式YOLO格式(不包含分割路径的txt文件,仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数):4983 标注数量(xml文件个数):4983 标注数量(txt文件个数):4983 标注…...
SpringBoot-17-MyBatis动态SQL标签之常用标签
文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…...
[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?
🧠 智能合约中的数据是如何在区块链中保持一致的? 为什么所有区块链节点都能得出相同结果?合约调用这么复杂,状态真能保持一致吗?本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里…...
ssc377d修改flash分区大小
1、flash的分区默认分配16M、 / # df -h Filesystem Size Used Available Use% Mounted on /dev/root 1.9M 1.9M 0 100% / /dev/mtdblock4 3.0M...
c++ 面试题(1)-----深度优先搜索(DFS)实现
操作系统:ubuntu22.04 IDE:Visual Studio Code 编程语言:C11 题目描述 地上有一个 m 行 n 列的方格,从坐标 [0,0] 起始。一个机器人可以从某一格移动到上下左右四个格子,但不能进入行坐标和列坐标的数位之和大于 k 的格子。 例…...
CMake 从 GitHub 下载第三方库并使用
有时我们希望直接使用 GitHub 上的开源库,而不想手动下载、编译和安装。 可以利用 CMake 提供的 FetchContent 模块来实现自动下载、构建和链接第三方库。 FetchContent 命令官方文档✅ 示例代码 我们将以 fmt 这个流行的格式化库为例,演示如何: 使用 FetchContent 从 GitH…...
Unity | AmplifyShaderEditor插件基础(第七集:平面波动shader)
目录 一、👋🏻前言 二、😈sinx波动的基本原理 三、😈波动起来 1.sinx节点介绍 2.vertexPosition 3.集成Vector3 a.节点Append b.连起来 4.波动起来 a.波动的原理 b.时间节点 c.sinx的处理 四、🌊波动优化…...
html css js网页制作成品——HTML+CSS榴莲商城网页设计(4页)附源码
目录 一、👨🎓网站题目 二、✍️网站描述 三、📚网站介绍 四、🌐网站效果 五、🪓 代码实现 🧱HTML 六、🥇 如何让学习不再盲目 七、🎁更多干货 一、👨…...
计算机基础知识解析:从应用到架构的全面拆解
目录 前言 1、 计算机的应用领域:无处不在的数字助手 2、 计算机的进化史:从算盘到量子计算 3、计算机的分类:不止 “台式机和笔记本” 4、计算机的组件:硬件与软件的协同 4.1 硬件:五大核心部件 4.2 软件&#…...
Proxmox Mail Gateway安装指南:从零开始配置高效邮件过滤系统
💝💝💝欢迎莅临我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:「storms…...
全面解析数据库:从基础概念到前沿应用
在数字化时代,数据已成为企业和社会发展的核心资产,而数据库作为存储、管理和处理数据的关键工具,在各个领域发挥着举足轻重的作用。从电商平台的商品信息管理,到社交网络的用户数据存储,再到金融行业的交易记录处理&a…...
