音视频入门基础: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的实现》

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

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

相关文章

程序员要失业了,一行代码没写,就完成了一个个人简历网页模版的创建

今天发现了一个好用的工——Cursor, 真的一行代码都没有写,完成了一个个人简历网页的创建,快来体验一下吧! 官网: https://www.cursor.com/ 价格(近乎免费) 先来看一下价格,cursor 非免费,但是…

[数据集][目标检测]肺炎检测数据集VOC+YOLO格式4983张2类别

数据集格式:Pascal VOC格式YOLO格式(不包含分割路径的txt文件,仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数):4983 标注数量(xml文件个数):4983 标注数量(txt文件个数):4983 标注…

springboot个性化大学生线上聊天交友系统

基于springbootvue实现的个性化大学生线上聊天交友系统 (源码L文ppt)4-017 4系统设计 4.1 软件功能模块设计 个性化大学生线上聊天交友分为两个模块,分别是管理员功能模块和用户功能模块。主要功能模块包括&#xff…

如何实现输入手机号查询座位号或桌号?

如何通过关键词查询信息? 一、简介 在大型活动中,如公司年会,快速定位座位或桌号对于参与者来说非常重要。本文将指导您如何使用云分组小程序,通过输入手机号来查询座位号或桌号,确保每位参与者都能轻松找到自己的座位…

uniapp交互反馈

页面交互反馈可以通过:uni.showToast(object)实现,常用属性有 ioc值说明 值说明success显示成功图标,此时 title 文本在小程序平台最多显示 7 个汉字长度,App仅支持单行显示。error显示错误图标,此时 title 文本在小程序平台最多显示 7 个汉字…

51单片机个人学习笔记11(AT24C02-I2C总线)

前言 本篇文章属于STC89C52单片机(以下简称单片机)的学习笔记,来源于B站教学视频。下面是这位up主的视频链接。本文为个人学习笔记,只能做参考,细节方面建议观看视频,肯定受益匪浅。 [1-1] 课程简介_哔哩…

【Qt| 入门知识】怎样创建一个最简单 Qt 界面程序

😁博客主页😁:🚀https://blog.csdn.net/wkd_007🚀 🤑博客内容🤑:🍭嵌入式开发、Linux、C语言、C、数据结构、音视频🍭 ⏰发布时间⏰: 2024-09-09 …

开启计算机的ssh 22端口

当我们使用一台服务器的时候,经常需要开启ssh 服务,有些系统没有默认开启服务,需要需要我们做一些配置,以下是完整配置过程和错误解决方法: 以下过程适合于当我们购买一台云主机时候配置远程登录 开启本地计算机的22…

孕期全攻略·如何成为一位准爸爸准妈妈

【智慧孕育,科学护航】 步入孕期,每一位准妈妈、准爸爸都渴望给予宝宝最好的开始。《孕期全攻略》汇集权威专家建议,为您打造一站式智慧孕育指南。从孕期各阶段的身体变化解析,到孕期疾病的预防与应对;从科学胎教方法&…

【Java】解析方法的调用关系

目录 一、方法的定义二、方法的声明格式三、方法的调用四、特殊方法:程序入口main方法五、方法的局部变量六、类的静态变量七、类的静态方法八、方法的传参九、方法的重载 一、方法的定义 方法就是 将功能重复的代码封装成一段独立的代码,通过调用方法的…

【网络】UDP协议的简单使用

目录 服务器 客户端 测试 code for Udp_echo_serve Udp_dict_serve UDP是基于socket(基于IP和port进行通信就叫做socket通信)进行网络通信的,那我们这篇博客就来介绍一下基于UDP通信的基本流程,先让服务端和客户端进行简单的…

深入探索Unity协程:揭开CSharp迭代器背后的神秘面纱

协程是一种特殊类型的迭代器方法,允许你在多个帧之间分段执行代码。可以用来处理时间延迟、异步操作和顺序执行的任务,而不阻塞主线程。Unity协程的实现依赖于C#语言提供的迭代器相关的语言特性,所以想要弄清楚Unity协程的底层原理&#xff0…

单例的饿汉式,懒汉式的线程安全问题

1 单例的饿汉式 对象在类加载的时候就创建了,线程安全,速度块,但是浪费空间, public class Hungry {//唯一对象private static final Hungry HUNGRY new Hungry();byte byte1[]new byte[1024];byte byte2[]new byte[1024];byte…

Java的时间复杂度和空间复杂度和常见排序

目录 一丶时间复杂度 二丶空间复杂度 三丶Java常见排序 1. 冒泡排序(Bubble Sort) 2.插入排序(Insertion Sort) 3.希尔排序(Shell Sort) 4.选择排序(Selection Sort) 5.堆排序&am…

qmt量化交易策略小白学习笔记第61期【qmt编程之期权行情数据--get_market_data_ex函数】

qmt编程之获取期权数据 期权行情数据 qmt更加详细的教程方法,会持续慢慢梳理。 也可找寻博主的历史文章,搜索关键词查看解决方案 ! 获取期权行情数据 获取期权最新数据,首先需要进行数据订阅。完成合约订阅后,用g…

【OpenCV】灰度化处理

文章目录 1. 图像灰度化处理对比2. 代码示例3. 二值化处理 1. 图像灰度化处理对比 2. 代码示例 #include <opencv2/opencv.hpp> using namespace cv;int main() {Mat currentImage imread("path_to_image.jpg"); // 读取彩色图像Mat grayImage;// 将彩色图像…

Rust的常数、作用域与所有权

【图书介绍】《Rust编程与项目实战》-CSDN博客 《Rust编程与项目实战》(朱文伟&#xff0c;李建英)【摘要 书评 试读】- 京东图书 (jd.com) Rust到底值不值得学&#xff0c;之一 -CSDN博客 Rust到底值不值得学&#xff0c;之二-CSDN博客 Rust的数据类型-CSDN博客 3.7 常…

HTTP 二、进阶

四、安全 1、TLS是什么 &#xff08;1&#xff09;为什么要有HTTPS ​ 简单的回答是“因为 HTTP 不安全”。由于 HTTP 天生“明文”的特点&#xff0c;整个传输过程完全透明&#xff0c;任何人都能够在链路中截获、修改或者伪造请求 / 响应报文&#xff0c;数据不具有可…

阿里不认命

​ 转载&#xff1a;新熵 原创 作者丨萧维 编辑丨影蕨 国家定调了&#xff01;一系列积极信号为平台经济注入一剂强心针&#xff0c;阿里迎来新生。 最近&#xff0c;阿里捷报频传&#xff01; 先是8月28日&#xff0c;阿里巴巴完成香港双重主要上市。紧接着&#xff0c;8月…

基于聚类与LSTM对比特币价格深度分析与预测

1.项目背景 比特币作为全球最具影响力的加密货币之一&#xff0c;其价格受到多种复杂因素的共同作用&#xff0c;包括市场情绪、政策变化、大型机构的投资行为等&#xff0c;这些因素在不同的市场阶段对比特币价格波动产生直接或间接的影响。通过对比特币市场的深入分析&#…