当前位置: 首页 > news >正文

音视频入门基础: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(也称为数据包或多媒体包)的信息&#xff0…...

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.(序号&#xf…...

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 标注…...

顶层const和底层const

在C中,const修饰符用于声明常量,有两种常见的形式:顶层const和底层const,它们之间的区别在于它们修饰的对象及其在不同场景中的作用。 1. 顶层const (Top-level const) 顶层const用于修饰变量本身,使其成为常量。这意…...

嵌入式Openharmony系统构建与启动详解

大家好,今天主要给大家分享一下,如何构建Openharmony子系统以及系统的启动过程分解。 第一:OpenHarmony系统构建 首先熟悉一下,构建系统是一种自动化处理工具的集合,通过将源代码文件进行一系列处理,最终生成和用户可以使用的目标文件。这里的目标文件包括静态链接库文件…...

锡林郭勒奶酪品牌呼和浩特市大召店盛大开业

礼献中秋,香飘乳都。为进一步拓展锡林郭勒奶酪区域公用品牌产品销售渠道,9月8日,锡林郭勒奶酪区域公用品牌大召店在呼和浩特市大召广场月明楼隆重开业,现场为第三批新授权的39家奶酪生产经营主体代表授牌。至此,锡林郭…...

【Java算法】模拟

🔥个人主页: 中草药 🔥专栏:【算法工作坊】算法实战揭秘 🧣 一.模拟算法 模拟算法和传统的算法有一些不同之处,更多的是对题目要求的理解,通过代码的方式去模拟实现一道题目在现实中的实现方法…...

标准库标头 <filesystem> (C++17)学习之文件类型

本篇介绍filesystem文件库的文件类型API。 文件类型 is_block_file (C17) 检查给定的路径是否表示块设备 (函数) is_character_file (C17) 检查给定的路径是否表示字符设备 (函数) is_directory (C17) 检查给定的路径是否表示一个目录 (函数) is_empty (C17) 检查给定的路径是…...

基于51单片机的自动转向修复系统的设计与实现

文章目录 前言资料获取设计介绍功能介绍设计清单具体实现截图参考文献设计获取 前言 💗博主介绍:✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师,一名热衷于单片机技术探索与分享的博主、专注于 精通51/STM32/MSP430/AVR等单片机设…...

mysql笔记4(数据类型)

数据库的数据类型应该是数据库架构师(DBA)和产品经理沟通后依据公司的项目、业务而定的,而且会不停地变化。数据类型的选择方面没有一个统一的标准,但是应该符合业务、项目的逻辑标准。 菜鸟教程 Mysql 数据类型 文章目录 1. int类型2. 浮点数3. 定点数4…...

电脑开机出现no operation system found错误原因分析及解决方法

最近有网友问我电脑一启动提示:no operation system found,这个提示意思是未找到操作系统。并且出现bios能认别硬盘,快捷启动时找不到硬盘,出现该提示的原因有很多,下面我们来详细分析一下开机出现no operation system…...

数学建模笔记—— 主成分分析(PCA)

数学建模笔记—— 主成分分析 主成分分析1. 基本原理1.1 主成分分析方法1.2 数据降维1.3 主成分分析原理1.4 主成分分析思想 2. PCA的计算步骤3. 典型例题4. 主成分分析说明5. python代码实现 主成分分析 1. 基本原理 在实际问题研究中,多变量问题是经常会遇到的。变量太多,无…...

@vueup/vue-quill使用quill-better-table报moduleClass is not a constructor

quill官方中文文档:https://www.kancloud.cn/liuwave/quill/1434144 扩展表格的使用 注意:想要使用表格 quill的版本要是2.0以后 升级到这个版本后 其他一些插件就注册不了了。 安装: npm install quilllatest 版本需要大于2.0版本 npm…...