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.mzph.cn/web/7738.shtml

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

相关文章

【智能算法】人类进化优化算法(HEOA)原理及实现

目录 1.背景2.算法原理2.1算法思想2.2算法过程 3.结果展示4.参考文献5.代码获取 1.背景 2024年&#xff0c;J Lian受到人类进化启发&#xff0c;提出了人类进化优化算法&#xff08;Human Evolutionary Optimization Algorithm, HEOA&#xff09;。 2.算法原理 2.1算法思想 …

【Linux 命令操作】如何在 Linux 中使用多行注释呢?

文章目录 1. 给代码进行多行注释2. 给代码取消多行注释 1. 给代码进行多行注释 &#x1f427;① 首先用 vim 打开代码&#xff0c;按 Esc进入命令模式(Normal mode)&#xff1b; &#x1f427;② 然后按住 ctrl v 进入列模式&#xff1b; &#x1f427;③ 再通过按 h(左)、j(…

19.删除链表的倒数第n个结点

刷算法题&#xff1a; 第一遍&#xff1a;1.看5分钟&#xff0c;没思路看题解 2.通过题解改进自己的解法&#xff0c;并且要写每行的注释以及自己的思路。 3.思考自己做到了题解的哪一步&#xff0c;下次怎么才能做对(总结方法) 4.整理到自己的自媒体平台。 5.再刷重复的类…

Docker容器:Docker-Consul 的容器服务更新与发现

目录 前言 一、什么是服务注册与发现 二、 Docker-Consul 概述 1、Consul 概念 2、Consul 提供的一些关键特性 3、Consul 的优缺点 4、传统模式与自动发现注册模式的区别 4.1 传统模式 4.2 自动发现注册模式 5、Consul 核心组件 5.1 Consul-Template组件 5.2 Consu…

深度学习之基于Vgg16卷积神经网络乳腺癌诊断系统

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 基于VGG16卷积神经网络的乳腺癌诊断系统项目是一个结合深度学习技术和医学图像处理的创新项目&#xff0c;旨在提高…

代码随想录Day 40|Leetcode|Python|139.单词拆分 ● 关于多重背包,你该了解这些! ● 背包问题总结篇!

139.单词拆分 给你一个字符串 s 和一个字符串列表 wordDict 作为字典。如果可以利用字典中出现的一个或多个单词拼接出 s 则返回 true。 注意&#xff1a;不要求字典中出现的单词全部都使用&#xff0c;并且字典中的单词可以重复使用。 解题思路&#xff1a; 确定dp数组含义…

路飞吃桃递归问题

在写代码之前&#xff0c;补充两个知识点 1.C语言递归的模版 2.递归是怎么工作的 好!话不多说让我们开始吧&#xff1a; 我们知道路飞吃了n天&#xff0c;每次都是吃一半&#xff0b;1&#xff0c;知道最后一天&#xff0c;只有一个桃子了&#xff0c;所以就可以列出式子&…

列转行(spark 与presto语法)

一、Presto 语法 原始数据&#xff1a; 期望数据&#xff1a; 代码&#xff1a; SELECT info, value FROM ( select 张三 as name,18 as age,男 as gender,清华 as schoolunion allselect 李四 as name,18 as age,男 as gender,清华 as school ) as a CROSS JOIN UNNEST(…

Linux实现Flappy bird项目

目录 1、项目介绍 2、功能总结 3、前期准备 3.1 Ncurses库 3.2 信号机制 3.2.1 设置信号响应方式 3.2.2 设置定时器 4、代码实现 4.1 头文件引用及变量、函数定义 4.2 主函数 4.3 curses初始化 4.4 设置定时器 4.5 定时器响应函数 4.6 小鸟控制相关函数 4…

C语言 自定义类型——联合体

目录: 一、联合体是&#xff1f;声明计算内存大小 二、联合体的特点例如 三、联合体大小的计算规则&#xff1a; 四、应用习1习2 一、联合体是&#xff1f; 联合体和结构体差不多&#xff0c;但是其最大的区别在于联合体所有的成员共用一块内存空间。所以联合体也叫共用体。联…

Java_方法引用

方法引用就是把已经有的方法拿过来用&#xff0c;当作函数式接口中抽象方法的方法体。 条件&#xff1a; 1.引用处需要是函数式接口 2.被引用的方法需要已经存在 3.被引用的方法的形参和返回值需要跟抽象方法的形参和返回值保持一致 4.被引用方法的功能需要满足当前的要求 简…

搭建父模块和工具子模块

第一章 项目父模块搭建 1.1 nancal-idsa 作为所有工程的父工程&#xff0c;用于管理项目的所有依赖版本。 1.2 指定 pom 类型模块&#xff0c;删除 src 目录&#xff0c;点击Reload project 1.3 添加依赖 pom.xml <parent> <groupId>org.springframework.…

Python爬虫教程:入门爬取网页数据

1.遵守法律法规 爬虫在获取网页数据时&#xff0c;需要遵守以下几点&#xff0c;以确保不违反法律法规&#xff1a; 不得侵犯网站的知识产权&#xff1a;爬虫不得未经授权&#xff0c;获取和复制网站的内容&#xff0c;这包括文本、图片、音频、视频等。 不得违反网站的使用条…

如何设置ddns动态域名服务实现外网访问

在本地搭建好服务器&#xff0c;部署好web网站或其他应用后&#xff0c;需要在外网访问内网时&#xff0c;如何设置动态域名服务ddns&#xff0c;将主机的内网IP端口映射到外网访问&#xff0c;是我们需要面对的一个重要步骤。 内网发布外网&#xff0c;常见的有两种方案&…

【智能优化算法】金枪鱼群优化(Tuna Swarm Optimization,TSO)

金枪鱼群优化&#xff08;Tuna Swarm Optimization,TSO&#xff09;是期刊“Computational Intelligence and Neuroscience”&#xff08;IF&#xff1a;1.8&#xff09;的2021年智能优化算法 01.引言 金枪鱼群优化&#xff08;Tuna Swarm Optimization,TSO&#xff09;的主要…

【Diffusion实战】训练一个类别引导diffusion模型(Pytorch代码详解)

又学习了一种方法&#xff0c;类别引导diffusion模型&#xff0c;使用mnist数据集&#xff0c;记录一下它的用法吧。 Diffusion实战篇&#xff1a;   【Diffusion实战】训练一个diffusion模型生成S曲线&#xff08;Pytorch代码详解&#xff09;   【Diffusion实战】训练一个…

Rust Course学习(编写测试)

如果友友你的计算机上没有安装Rust&#xff0c;可以直接安装&#xff1a;Rust 程序设计语言 (rust-lang.org)https://www.rust-lang.org/zh-CN/ Introduce 介绍 Testing in Rust involves writing code specifically designed to verify that other code works as expected. It…

2024.5.7

槽函数声明 private slots:void on_ed_textChanged();void on_pushButton_clicked(); }; 槽函数定义 void Widget::on_ed_textChanged()//文本框 {if(ui->ed1->text().length()>5&&ui->ed2->text().length()>5){ui->pushButton->setStyleSh…

Xinstall广告效果监测,助力广告主优化投放策略

在移动互联网时代&#xff0c;APP推广已成为企业营销的重要手段。然而&#xff0c;如何衡量推广效果&#xff0c;了解用户来源&#xff0c;优化投放策略&#xff0c;一直是广告主和开发者面临的难题。这时&#xff0c;Xinstall作为国内专业的App全渠道统计服务商&#xff0c;以…

Docker 安装部署 postgres

Docker 安装部署 postgres 1、拉取 postgres 镜像文件 [rootiZbp19a67kznq0h0rgosuxZ ~]# docker pull postgres:latest latest: Pulling from library/postgres b0a0cf830b12: Pull complete dda3d8fbd5ed: Pull complete 283a477db7bb: Pull complete 91d2729fa4d5: Pul…