ffmpeg音视频裁剪

音视频裁剪,通常会依据时间轴为基准,从某个起始点到终止点的音视频截取出来,当然音视频文件中存在多路流,所对每一组流进行裁剪 

 基础概念:

编码帧的分类:

I帧(Intra coded frames):  关键帧,采用帧内压缩技术,所占数据的信息量比较大,I帧不需要参考其他画面而生成,解码时仅靠自己就重构完整图像;

P 帧(forward Predicted frames):  向前参考帧,根据本帧与相邻的前一帧(l帧或P帧)的不同点来压缩本帧数据,同时利用了空间和时间上的相关性。压缩时,只参考前面已经处理的帧(I帧或P帧),采用帧间压缩技术。它占I帧的一半大小

B 帧(Bidirectional predicted frames):  双向参考帧,B 帧图像采用双向时间预测,可以大大提高压缩倍数。压缩时,既参考前面已经处理的帧,也参考后面的帧,帧间压缩技术。它占I帧四分之一大小。

        I帧图像是周期性出现在图像序列中的,出现频率可由编码器选择;I帧是P帧和B帧的参考帧(其质量直接影响到同组中以后各帧的质量);I帧是帧组GOP(Group of Pictures)的基础帧(第一帧),且每组只有一个I帧。

        对于一个视频文件,帧的显示顺序:IBBP,但是帧的存储方式可能是:IPBB。现在我们需要在显示B帧之前知道P帧中的信息,这时就需要一个解码时间戳(dts(Decoding Time Stamp))和一个显示时间戳(pts(Presentation Time Stamp))。解码时间戳告诉我们什么时候需要解码,显示时间戳告诉我们什么时间需要显示。通常pts和dts只有在流中有B帧的时候才不同。

        FFmpeg中用AVPacket结构体来描述解码前、后的压缩包,用AVFrame结构体来描述解码后、前的信号帧。 对于视频来说,AVFrame就是视频的一帧图像。这帧图像什么时候显示给用户,就取决于它的PTS。DTS是AVPacket里的一个成员,表示这个压缩包应该什么时候被解码。 如果视频里各帧的编码是按输入顺序(也就是显示顺序)依次进行的,那么解码和显示时间应该是一致的。可事实上,在大多数编解码标准(如H.264或HEVC)中,编码顺序和输入顺序并不一致,于是才会需要PTS和DTS这两种不同的时间戳。所以视频流中的时间总是pts(显示时间) >= dts(解码时间)。

ffmpeg中时间相关时间单位:

        ffmepg中的内部计时单位(时间基),ffmepg中的所有时间都是于它为一个单位,比如AVStream中的duration即以为着这个流的长度为duration个AV_TIME_BASE。AV_TIME_BASE定义为:

#define         AV_TIME_BASE   1000000

ffmpeg提供了一个把AVRatioal结构转换成double的函数:

static inline double av_q2d(AVRational a){
/**
* Convert rational to double.
* @param a rational to convert
**/return a.num / (double) a.den;
}

可以根据pts来计算一桢在整个视频中的时间位置:

timestamp(秒) = pts * av_q2d(st->time_base);    //这里的st是一个AVStream对象指针。

计算视频长度的方法:

time(秒) = st->duration * av_q2d(st->time_base);    // 这里的st是一个AVStream对象指针。

时间基转换公式

  • timestamp(ffmpeg内部时间戳) = AV_TIME_BASE * time(秒)
  • time(秒) = AV_TIME_BASE_Q * timestamp(ffmpeg内部时间戳)

所以当需要把视频跳转到N秒的时候可以使用下面的方法:

int64_t timestamp = N * AV_TIME_BASE; // N秒转换为内部时间戳av_seek_frame(fmtctx, index_of_video, timestamp, AVSEEK_FLAG_BACKWARD);    //  // AVSEEK_FLAG_BACKWARD 向后找到I帧

不同时间基之间的转换函数(作用是计算a * bq / cq,来把时间戳从一个时基调整到另外一个时基。在进行时基转换的时候,我们应该首选这个函数,因为它可以避免溢出的情况发生。)

int64_t av_rescale_q(int64_t a, AVRational bq, AVRational cq)

裁剪音视频代码实例:

//裁剪多媒体文件(因为视频存在I帧B帧P帧,所以裁剪结果和输入时长有误差)//编译链接:gcc -o cut cut.c `pkg-config --libs --cflags libavutil libavformat libavcodec`//执行 ./cut test.mp4 cut.mp4  (starttime)  (endtime)(单位秒)#include<stdio.h>
#include<stdlib.h>
#include<libavutil/log.h>
#include <libavformat/avformat.h>int main(int argc, char* argv[])
{int ret = -1;int idx = -1;int i = 0;int stream_idx = 0;// 处理输入参数char* src, * dst;double starttime, endtime;int64_t* dts_start_time, * pts_start_time;int* stream_map = NULL;AVFormatContext* pFmtCtx = NULL;	// 多媒体上下文AVFormatContext* oFmtCtx = NULL;	// 目标文件上下文信息const AVOutputFormat* outFmt = NULL;		// 输出文件格式信息AVPacket pkt;		// 包av_log_set_level(AV_LOG_DEBUG);if (argc < 5) {	//该可执行程序  源文件   目标文件 起始时间 结束时间av_log(NULL, AV_LOG_INFO, "Arguments must be more than 5.");exit(-1);}src = argv[1];dst = argv[2];starttime = atof(argv[3]);endtime = atof(argv[4]);if (endtime < starttime) {av_log(NULL, AV_LOG_INFO, "Cut time error!.");exit(-1);}// 打开多媒体文件(包含文件头和文件体)if ((ret = avformat_open_input(&pFmtCtx, src, NULL, NULL))){av_log(NULL, AV_LOG_ERROR, "%s\n", av_err2str(ret));exit(-1);}// 打开目的文件的上下文avformat_alloc_output_context2(&oFmtCtx, NULL, NULL, dst);if (!oFmtCtx) {av_log(NULL, AV_LOG_ERROR, "NO Memory!\n");goto _ERROR;}stream_map = av_calloc(pFmtCtx->nb_streams, sizeof(int));if (!stream_map) {av_log(NULL, AV_LOG_ERROR, "NO Memory!\n");goto _ERROR;}// 遍历源文件每一条流for (i = 0; i < pFmtCtx->nb_streams; i++) {AVStream* outStream = NULL;AVStream* inStream = pFmtCtx->streams[i];AVCodecParameters* inCodecPar = inStream->codecpar;// 只处理音、视频、字幕数据if (inCodecPar->codec_type != AVMEDIA_TYPE_AUDIO &&inCodecPar->codec_type != AVMEDIA_TYPE_VIDEO &&inCodecPar->codec_type != AVMEDIA_TYPE_SUBTITLE) {stream_map[i] = -1;continue;}stream_map[i] = stream_idx++;// 为目的文件创建一个新的视频流outStream = avformat_new_stream(oFmtCtx, NULL);if (!outStream) {av_log(oFmtCtx, AV_LOG_ERROR, "NO Memory!\n");goto _ERROR;}avcodec_parameters_copy(outStream->codecpar, inStream->codecpar);	//将源文件的内容复制到目的文件 outStream->codecpar->codec_tag = 0;	// 根据多媒体文件自动识别编解码器}//上下文信息与输出文件绑定ret = avio_open2(&oFmtCtx->pb, dst, AVIO_FLAG_WRITE, NULL, NULL);if (ret < 0) {av_log(NULL, AV_LOG_ERROR, "%s", av_err2str(ret));goto _ERROR;}// 写多媒体文件头(包含多媒体的类型、版本等信息)到目标文件ret = avformat_write_header(oFmtCtx, NULL);if (ret < 0) {av_log(oFmtCtx, AV_LOG_ERROR, "%s", av_err2str(ret));goto _ERROR;}// 跳转到时间点ret = av_seek_frame(pFmtCtx, -1, starttime * AV_TIME_BASE, AVSEEK_FLAG_BACKWARD); // AVSEEK_FLAG_BACKWARD 向后找到I帧if (ret < 0) {av_log(oFmtCtx, AV_LOG_ERROR, "%s", av_err2str(ret));goto _ERROR;}// 记录第一个包的时间戳dts_start_time = av_calloc(pFmtCtx->nb_streams, sizeof(int64_t));pts_start_time = av_calloc(pFmtCtx->nb_streams, sizeof(int64_t));for (int t = 0; t < pFmtCtx->nb_streams; t++) {dts_start_time[t] = -1;pts_start_time[t] = -1;}// 从源多媒体文件中读到音、视频、字幕数据while (av_read_frame(pFmtCtx, &pkt) >= 0) {  // 从多媒体文件读取到帧数据,读取码流中的音频若干帧或者视频一帧AVStream* inStream, * outStream;// 记录每组流截取开始的时间戳if (dts_start_time[pkt.stream_index] == -1 && pkt.dts > 0) {dts_start_time[pkt.stream_index] = pkt.dts;}if (pts_start_time[pkt.stream_index] == -1 && pkt.pts > 0) {pts_start_time[pkt.stream_index] = pkt.pts;}inStream = pFmtCtx->streams[pkt.stream_index];if (av_q2d(inStream->time_base) * pkt.pts > endtime) {	// 结束时间av_log(oFmtCtx, AV_LOG_INFO, "cut success!\n");break;}if (stream_map[pkt.stream_index] < 0) {		// 流编号为-1, 不是音、视频、字幕流数据av_packet_unref(&pkt);	// 释放packetcontinue;}// 相对时间pkt.pts = pkt.pts - pts_start_time[pkt.stream_index];pkt.dts = pkt.dts - dts_start_time[pkt.stream_index];if (pkt.dts > pkt.pts) {	// 音频dts、pts 相等,视频的pts >= dtspkt.pts = pkt.dts;}pkt.stream_index = stream_map[pkt.stream_index];outStream = oFmtCtx->streams[pkt.stream_index];av_packet_rescale_ts(&pkt, inStream->time_base, outStream->time_base);	// 修改时间戳pkt.pos = -1;			// 偏移位置av_interleaved_write_frame(oFmtCtx, &pkt);		// 将视频帧写入目标文件中av_packet_unref(&pkt);}// 写多媒体文件尾到文件中av_write_trailer(oFmtCtx);// 将申请的资源释放掉
_ERROR:if (pFmtCtx) {avformat_close_input(&pFmtCtx);pFmtCtx = NULL;}if (oFmtCtx->pb) {avio_close(oFmtCtx->pb);}if (oFmtCtx) {avformat_free_context(oFmtCtx);oFmtCtx = NULL;}if (stream_map) {av_free(stream_map);}if (dts_start_time) {av_free(dts_start_time);}if (pts_start_time) {av_free(pts_start_time);}return 0;
}

参考:

ffmpeg中的时间单位_pkt.duration的值-CSDN博客

https://blog.51cto.com/moonfdd/6266754?articleABtest=0

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

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

相关文章

xLua热更新解决方案

图中灰色的无法实现热更新&#xff0c;而Lua代码可以打包成AB包&#xff0c;并上传到资源服务器&#xff0c; 当进入游戏检测是否有资源需要更新&#xff0c;需要则会从资源服务器下载。 学习目标 1.导入xLua框架 2.C#调用Lua 3.Lua调用C# 4.xLua热补丁 xLua框架导入和AB…

如何消除浏览器SmartScreen对网站“不安全”提示?

面对互联网时代用户对网站安全性和可信度的严苛要求&#xff0c;网站运营者时常遭遇Microsoft Defender SmartScreen&#xff08;SmartScreen&#xff09;提示网站不安全的困扰。本文将剖析SmartScreen判定网站不安全的原因&#xff0c;并为运营者提供应对策略&#xff0c;以恢…

机器学习:基于Sklearn、XGBoost框架,使用逻辑回归、支持向量机和XGBClassifier来诊断并预测一个人是否患有自闭症

前言 系列专栏&#xff1a;机器学习&#xff1a;高级应用与实践【项目实战100】【2024】✨︎ 在本专栏中不仅包含一些适合初学者的最新机器学习项目&#xff0c;每个项目都处理一组不同的问题&#xff0c;包括监督和无监督学习、分类、回归和聚类&#xff0c;而且涉及创建深度学…

二、VLAN原理和配置

vlan不是协议&#xff0c;是一个技术&#xff0c;虚拟局域网技术&#xff0c;基于802.1q协议。 vlan&#xff08;虚拟局域网&#xff09;&#xff0c;将一个物理的局域网在逻辑上划分成多个广播域的技术。 目录 1.冲突域和广播域 概念 范围 2.以太网帧格式 3.以太网帧封装…

Facebook的声音:听见社交媒体的心跳

社交媒体如今已经成为人们日常生活中不可或缺的一部分&#xff0c;而Facebook作为其中的佼佼者&#xff0c;承载着数以亿计的用户的交流、分享和连接。在这个信息爆炸的时代&#xff0c;Facebook的声音就像是社交媒体的心跳&#xff0c;传递着无数个体的情感、思想和生活。本文…

Python-VBA函数之旅-object基类(非函数)

目录 一、object基类的常见应用场景 二、object基类使用注意事项 三、如何用好object基类&#xff1f; 1、object基类&#xff1a; 1-1、Python&#xff1a; 1-2、VBA&#xff1a; 2、推荐阅读&#xff1a; 个人主页&#xff1a;神奇夜光杯-CSDN博客 一、object基类的…

基于H.264的RTP打包中的组合封包以及分片封包结构图简介及抓包分析

H.264视频流的RTP封装类型分析&#xff1a; 前言&#xff1a; NULL Hearder简介(结构如下)&#xff1a; ---------------|0|1|2|3|4|5|6|7|--------|F|NRI| Type |--------------- F&#xff1a;forbidden_zero_bit&#xff0c; 占1位&#xff0c;在 H.264 规范中规定了这…

CI/CD:基于kubernetes的Gitlab搭建

1. 项目目标 &#xff08;1&#xff09;熟悉使用k8s环境搭建Gitlab &#xff08;2&#xff09;熟练应用Gitlab基本配置 2. 项目准备 2.1. 规划节点 主机名 主机IP 节点规划 k8s-master 10.0.1.1 kube_master k8s-node1 10.0.1.2 kube_node k8s-node2 10.0.1.3 k…

2024年武汉东湖高新水测成绩出来了

本次水测通过人员有1016名&#xff0c;通过的人数还是蛮多的&#xff0c;水测其实没有大家想象的那么难&#xff0c;现在职称评审都是水测线下评审的模式进行的。 水平测试分机考&#xff0c;笔试和面试答辩&#xff0c;各区随机安排选其一&#xff0c;机考就相当于考驾照刷题&…

自动化测试web库(元素定位、元素操作、浏览器操作)

按照谷歌浏览器 Chrome &#xff1a;https://googlechromelabs.github.io/chrome-for-testing/ Chrome使用技巧&#xff1a; 1、找到自己想要的标签 打开检查&#xff0c;点击箭头&#xff0c;再点击你想要点击的地方 2、直接在浏览器上查询&#xff0c;看看是否查询成功 可…

【算法刷题 | 贪心算法08】4.29(划分字母区间、合并区间)

文章目录 14.划分字母区间14.1题目14.2解法&#xff1a;贪心14.2.1贪心思路14.2.2代码实现 15.合并区间15.1题目15.2解法&#xff1a;贪心15.2.1贪心思路15.2.2代码实现 14.划分字母区间 14.1题目 给你一个字符串 s 。我们要把这个字符串划分为尽可能多的片段&#xff0c;同一…

msf练习

一、什么是msfvenom&#xff1f; msfvenom是msf中的一个独立的负载生成器&#xff0c;它可以利用msf中的payloads和encoders来生成各种格式的木马文件&#xff0c;并在目标机上执行&#xff0c;配合meterpreter在本地监听上线。msfvenom是msfpayload和msfencode的结合体&#…

【C++】深入了解C++内存管理

个人主页&#xff1a;救赎小恶魔 欢迎大家来到小恶魔频道 好久不见&#xff0c;甚是想念 今天我们要深入讲述类与对象的初始化列表以及隐式类型转换 目录 1.C的内存分布 2.C/C言中动态内存管理方式 1.C语言的管理方式 2.C的管理方式 new delete 3.operator new与ope…

IntelliJ IDEA - Auto filling Java call arguments 插件教程

首先&#xff0c;安装该插件&#xff0c;下载完毕后重启 IDEA 当 userService 中方法需要参数的时候&#xff0c;我们一般都是自己手动写这些参数&#xff0c;是很费劲的。因此就出现了一个插件解决这类问题 Auto filling Java call arguments 光标点击需要填写参数的位置 Alt …

【酱浦菌-爬虫项目】python爬取彼岸桌面壁纸

首先&#xff0c;代码导入了两个库&#xff1a;requests和parsel。这些库用于处理HTTP请求和解析HTML内容。 然后&#xff0c;它定义了一个变量url&#xff0c;指向网站’樱花2024年4月日历风景桌面壁纸_高清2024年4月日历壁纸_彼岸桌面’。 接下来&#xff0c;设置了一个HTT…

变革 Perplexica:AI驱动的问答搜索引擎

Perplexica是一个开源的人工智能搜索工具&#xff0c;也可以说是一款人工智能搜索引擎&#xff0c;它深入互联网以找到答案。受Perplexity AI启发&#xff0c;它是一个开源选择&#xff0c;不仅可以搜索网络&#xff0c;还能理解您的问题。它使用先进的机器学习算法&#xff0c…

帕累托森林李朝政博士受聘「天工开物开源基金会」专家顾问

导语&#xff1a; 开源铸造了当前最前沿的科技引擎。开源驱动了软件生态&#xff0c;也以指数级速度驱动硬件生态。 3月中旬&#xff0c;天工开物开源基金会授予李朝政博士专家顾问&#xff0c;表彰他积极推动参与中国智能软件生态的建设&#xff0c;期待一起共筑未来新生态。…

Axure实现tab页面切换功能

1. 实现效果 2. 实现原理 创建两个标签&#xff0c;并实现点击时选中状态点击时&#xff0c;设置面板状态 3. 实现步骤 3.1 实现可切换的标签 在页面上拖拽两个矩形作为两个tab标签&#xff0c;并命名 tab1 和 tab2 设置每个矩形的边框显示&#xff0c;只显示下边框即可 …

pyQt5 和 Qt Designer 实现登录注册案例

Qt Designer 设计页面: 通过 PyQt5 手写 1. 先引入用到的库 from PyQt5.QtWidgets import * import sys 2. 创建应用,窗口, 设置窗口 # 创建应用 app QApplication(sys.argv) # 创建窗口 w QWidget()# 设置窗口标题 w.setWindowTitle("注册登录")# 展示 w.sho…

使用STM32CubeMX对STM32F4进行串口配置

目录 1. 配置1.1 Pin脚1.2 RCC开启外部晶振1.3 时钟1.4 串口配置 2. 代码2.1 默认生成代码2.1 开启串口中断函数2.3 接收中断2.4 接收回调函数2.5 增加Printf 的使用 1. 配置 1.1 Pin脚 1.2 RCC开启外部晶振 1.3 时钟 外部使用8MHz晶振 开启内部16MHz晶振 使用锁相环 开启最高…