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

音视频入门基础:WAV专题(9)——FFmpeg源码中计算WAV音频文件每个packet的duration和duration_time的实现

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

音视频入门基础:WAV专题系列文章:

音视频入门基础:WAV专题(1)——使用FFmpeg命令生成WAV音频文件

音视频入门基础:WAV专题(2)——WAV格式简介

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

音视频入门基础:WAV专题(4)——FFmpeg源码中获取WAV文件音频压缩编码格式、采样频率、声道数量、采样位数、码率的实现

音视频入门基础:WAV专题(5)——FFmpeg源码中解码WAV Header的实现

音视频入门基础:WAV专题(6)——通过FFprobe显示WAV音频文件每个数据包的信息

音视频入门基础:WAV专题(7)——FFmpeg源码中计算WAV音频文件每个packet的size值的实现

音视频入门基础:WAV专题(8)——FFmpeg源码中计算WAV音频文件AVStream的time_base的实现

音视频入门基础:WAV专题(9)——FFmpeg源码中计算WAV音频文件每个packet的duration和duration_time的实现

音视频入门基础:WAV专题(10)——FFmpeg源码中计算WAV音频文件每个packet的pts、dts的实现

音视频入门基础:WAV专题(11)——FFmpeg源码中计算WAV音频文件每个packet的pts_time、dts_time的实现

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

一、引言

从文章《音视频入门基础:WAV专题(6)——通过FFprobe显示WAV音频文件每个数据包的信息》中我们可以知道,通过FFprobe命令可以显示WAV音频文件每个packet(也称为数据包或多媒体包)的信息,这些信息包含该packet的duration和duration_time:

这个“duration”实际是AVPacket结构体中的成员变量duration,为该音频packet占用的以AVStream的time_base为单位的时间值。而“duration_time”为该音频packet占用的以秒为单位的时间值。这两个值通过fftools/ffprobe.c中的show_packet函数打印出来:

static void show_packet(WriterContext *w, InputFile *ifile, AVPacket *pkt, int packet_idx)
{
//...print_duration_ts("duration",        pkt->duration);print_duration_time("duration_time", pkt->duration, &st->time_base);
//...
}

本文讲述“duration”和“duration_time”的值是怎样被计算出来的。如果想直接看结论,可以跳到本文的最后,直接看“总结”。

二、FFmpeg源码中计算WAV音频文件每个packet的duration和duration_time的实现

(一)得到每个packet的duration

FFmpeg对WAV音频文件进行解封装(解复用)时,会调用avformat_find_stream_info函数,而该函数底层会调用compute_pkt_fields函数:

static void compute_pkt_fields(AVFormatContext *s, AVStream *st,AVCodecParserContext *pc, AVPacket *pkt,int64_t next_dts, int64_t next_pts)
{
//...if (pkt->duration <= 0) {compute_frame_duration(s, &num, &den, st, pc, pkt);if (den && num) {duration = (AVRational) {num, den};pkt->duration = av_rescale_rnd(1,num * (int64_t) st->time_base.den,den * (int64_t) st->time_base.num,AV_ROUND_DOWN);}}
//...
}

compute_pkt_fields函数内部,由于AVPacket结构体被初始化后,其成员变量duration会是0,(新版本的FFmpeg源码一般使用get_packet_defaults函数进行初始化,具体可以参考:《FFmpeg源码:av_init_packet、get_packet_defaults、av_packet_alloc函数分析》),所以会执行下面if语句为真时括号里的内容:

if (pkt->duration <= 0) {
//...
}

通过compute_frame_duration函数,让变量num被赋值为该音频packet占用的以AVStream的time_base为单位的时间值,让变量den被赋值为该音频的采样频率(单位为Hz):

compute_frame_duration(s, &num, &den, st, pc, pkt);

从文章《FFmpeg源码:compute_frame_duration函数分析》中可以知道,compute_frame_duration函数内部调用了av_get_audio_frame_duration2函数。而从《FFmpeg源码:get_audio_frame_duration、av_get_audio_frame_duration2函数分析》中可以知道,av_get_audio_frame_duration2函数内部又通过get_audio_frame_duration函数来计算某个音频packet占用的时间值。计算公式是:该音频packet占用的以AVStream的time_base为单位的时间值 = packet的大小(单位为字节)×8÷(音频的采样位数×声道数量),比如,某个音频packet的大小为16384字节、音频的采样位数为16位、声道数为2,则该音频packet占用的时间值(以AVStream的time_base为单位)为:16384×8÷(16×2)= 4096。

关于av_rescale_rnd函数的用法可以参考:《FFmpeg源码:av_rescale_rnd、av_rescale_q_rnd、av_rescale_q、av_add_stable函数分析》。最后通过av_rescale_rnd函数得到AVPacket结构体的成员变量duration。下面语句相当于执行了:pkt->duration = 1 × num × st->time_base.den ÷ (den × st->time_base.num):

pkt->duration = av_rescale_rnd(1,num * (int64_t) st->time_base.den,den * (int64_t) st->time_base.num,AV_ROUND_DOWN);

而从上面我们可以知道,变量num为该音频packet占用的以AVStream的time_base为单位的时间值,变量den为该音频的采样频率(单位为Hz)。根据《音视频入门基础:WAV专题(8)——FFmpeg源码中计算WAV音频文件AVStream的time_base的实现》我们又可以知道WAV音频文件AVStream的time_base(st->time_base)为音频采样频率的倒数。

所以语句pkt->duration = 1 × num × st->time_base.den ÷ (den × st->time_base.num)等价于

pkt->duration = num。

从而让AVPacket结构体中的成员变量duration可以被赋值为该音频packet占用的以AVStream的time_base为单位的时间值。

(二)得到每个packet的duration_time

duration和duration_time是通过fftools/ffprobe.c中的show_packet函数打印出来:

​
static void show_packet(WriterContext *w, InputFile *ifile, AVPacket *pkt, int packet_idx)
{
//...print_duration_ts("duration",        pkt->duration);print_duration_time("duration_time", pkt->duration, &st->time_base);
//...
}

print_time宏定义如下,可以看到其实质是调用writer_print_time函数:

#define print_duration_time(k, v, tb) writer_print_time(w, k, v, tb, 1)

而writer_print_time函数的定义为:

static void writer_print_time(WriterContext *wctx, const char *key,int64_t ts, const AVRational *time_base, int is_duration)
{char buf[128];if ((!is_duration && ts == AV_NOPTS_VALUE) || (is_duration && ts == 0)) {writer_print_string(wctx, key, "N/A", PRINT_STRING_OPT);} else {double d = ts * av_q2d(*time_base);struct unit_value uv;uv.val.d = d;uv.unit = unit_second_str;value_string(buf, sizeof(buf), uv);writer_print_string(wctx, key, buf, 0);}
}

表达式writer_print_time(w, k, v, tb, 1)中,“v”为该音频packet占用的以AVStream的time_base为单位的时间值,“tb”为AVStream的time_base。

关于av_q2d函数的用法可以参考:《FFmpeg有理数相关的源码:AVRational结构体和其相关的函数分析》。duration_time实际上是由writer_print_time函数中的下面语句计算出来的:

double d = ts * av_q2d(*time_base);

简单点来讲duration_time = duration × time_base。

三、总结

对于WAV音频文件:

AVPacket的“duration”为该音频packet占用的以AVStream的time_base为单位的时间值,其值等于: packet的大小(单位为字节)×8÷(音频的采样位数×声道数量),比如,某个音频packet的大小为16384字节、音频的采样位数为16位、声道数为2,则该音频packet的duration等于:16384×8÷(16×2)= 4096。关于音频packet大小的计算方式可以参考:《音视频入门基础:WAV专题(7)——FFmpeg源码中计算WAV音频文件每个packet的size值的实现》。

而“duration_time”为该音频packet占用的以秒为单位的时间值,其值等于:duration × time_base。比如,某个音频packet的duration为4096,time_base为44100分之一,其duration_time为4096乘以44100分之一,等于0.092880。关于音频time_base的计算方式可以参考:《音视频入门基础:WAV专题(8)——FFmpeg源码中计算WAV音频文件AVStream的time_base的实现》。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 网络原理(3)—— 应用层、传输层(TCP)
  • Redis 是否存在线程安全问题:深入解析与技术分析
  • Robust Image Denoising through Adversarial Frequency Mixup
  • “他人笑我太疯癫,我笑他人看不穿“,关于做知识分享,被Diss,哇哦,真厉害
  • MongoDB 的适用场景
  • SM7015非隔离电磁炉/电饭煲电源芯片12V/18V输出
  • Java设计模式之责任链模式详细讲解和案例示范
  • 1.初识ChatGPT:AI聊天机器人的革命(1/10)
  • 一段代码搞懂String被final修饰的影响
  • 【UI】element ui table(表格)expand实现点击一行展开功能
  • 【Moveit2官方教程】使用 MoveIt Task Constructor (MTC) 框架来定义和执行一个机器人任务
  • Windows下SDL2创建最简单的一个窗口
  • laravel 11 区分多模块的token
  • Debian项目实战——环境搭建篇
  • 如何查看macos是x86还是arm
  • [笔记] php常见简单功能及函数
  • CSS 提示工具(Tooltip)
  • es6--symbol
  • Eureka 2.0 开源流产,真的对你影响很大吗?
  • Material Design
  • MYSQL 的 IF 函数
  • node学习系列之简单文件上传
  • php中curl和soap方式请求服务超时问题
  • Python 使用 Tornado 框架实现 WebHook 自动部署 Git 项目
  • Redis学习笔记 - pipline(流水线、管道)
  • Vim Clutch | 面向脚踏板编程……
  • vue 配置sass、scss全局变量
  • Vue--数据传输
  • Webpack入门之遇到的那些坑,系列示例Demo
  • Web设计流程优化:网页效果图设计新思路
  • weex踩坑之旅第一弹 ~ 搭建具有入口文件的weex脚手架
  • 阿里云容器服务区块链解决方案全新升级 支持Hyperledger Fabric v1.1
  • -- 查询加强-- 使用如何where子句进行筛选,% _ like的使用
  • 从tcpdump抓包看TCP/IP协议
  • 给新手的新浪微博 SDK 集成教程【一】
  • 关于Android中设置闹钟的相对比较完善的解决方案
  • 简析gRPC client 连接管理
  • 紧急通知:《观止-微软》请在经管柜购买!
  • 理解 C# 泛型接口中的协变与逆变(抗变)
  • 如何进阶一名有竞争力的程序员?
  • 如何设计一个比特币钱包服务
  • 验证码识别技术——15分钟带你突破各种复杂不定长验证码
  • 白色的风信子
  • ‌JavaScript 数据类型转换
  • # Python csv、xlsx、json、二进制(MP3) 文件读写基本使用
  • #Linux(权限管理)
  • #宝哥教你#查看jquery绑定的事件函数
  • (LeetCode C++)盛最多水的容器
  • (LLM) 很笨
  • (pojstep1.1.1)poj 1298(直叙式模拟)
  • (STM32笔记)九、RCC时钟树与时钟 第一部分
  • (不用互三)AI绘画:科技赋能艺术的崭新时代
  • (二)JAVA使用POI操作excel
  • (二)构建dubbo分布式平台-平台功能导图
  • (二十六)Java 数据结构