ffmpeg音视频裁剪

news2024/10/6 16:25:49

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

 基础概念:

编码帧的分类:

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);	// 释放packet
			continue;
		}
		
		// 相对时间
		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 >= dts
			pkt.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.coloradmin.cn/o/1634145.html

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

相关文章

linux(ubuntu18.04.2) Qt编译 MySQL(8.0以上版本)链接库 Qt版本 5.12.12及以上 包含Mysql动态库缺失问题

整理这篇文档的意义在于&#xff1a;自己走了很多弯路&#xff0c;淋过雨所以想为别人撑伞&#xff0c;也方便回顾&#xff0c;仅供参考 一、搭建开发环境&#xff1a; 虚拟机&#xff08;ubuntu-20.04.6-desktop-amd64&#xff09;&#xff1a;Mysql数据库 8.0.36Workbench …

pytorch库 01 安装Anaconda、Jupyter,Anaconda虚拟环境连接pycharm

文章目录 一、安装Anaconda1、卸载Anaconda&#xff08;可选&#xff09;2、下载并安装Anaconda3、配置环境变量4、桌面快捷方式 二、安装 PyTorch&#xff08;GPU 版&#xff09;库1、创建虚拟环境&#xff0c;并安装一些常用包2、GPU 基础3、检查驱动4、安装CUDA&#xff08;…

数字化转型新篇章:企业通往智能化的新范式

早在十多年前&#xff0c;一些具有前瞻视野的企业以实现“数字化”为目标启动转型实践。但时至今日&#xff0c;可以说尚无几家企业能够在真正意义上实现“数字化”。 在实现“数字化”的征途上&#xff0c;人们发现&#xff0c;努力愈进&#xff0c;仿佛终点愈远。究其原因&a…

Springboot+Vue项目-基于Java+MySQL的校园外卖服务系统(附源码+演示视频+LW)

大家好&#xff01;我是程序猿老A&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;Java毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计 &…

企业营销战略新思考:营销5.0与开源AI智能名片S2B2C商城小程序引领私域流量经营新纪元

随着互联网的深入发展&#xff0c;企业营销战略已经不再是单一、静态的规划&#xff0c;而是一个持续进化、与市场紧密相连的动态过程。在这个过程中&#xff0c;营销5.0和开源AI智能名片S2B2C商城小程序的结合&#xff0c;为企业营销战略注入了新的活力&#xff0c;也为私域流…

EasyRecovery数据恢复软件2025永久免费电脑版下载

EasyRecovery数据恢复软件是一款业界知名的数据恢复工具&#xff0c;它凭借强大的恢复能力和广泛的数据兼容性&#xff0c;帮助用户从各种存储设备中恢复丢失或删除的数据。以下是关于EasyRecovery数据恢复软件的详细介绍。 EasyRecovery绿色破解下载网盘链接: https://pan.ba…

《QT实用小工具·四十七》可交互的创意动态按钮

1、概述 源码放在文章末尾 该项目实现了可交互的创意动态按钮&#xff0c;包含如下功能&#xff1a; 所有颜色自定义 鼠标悬浮渐变 两种点击效果&#xff1a;鼠标点击渐变 / 水波纹动画&#xff08;可多层波纹叠加&#xff09; 额外鼠标移入/移出/按下/弹起的实时/延迟共8种事…

2024最新版JavaScript逆向爬虫教程-------基础篇之常用的编码与加密介绍(python和js实现)

目录 一、编码与加密原理1.1 ASCII 编码1.2 详解 Base641.2.1 Base64 的编码过程和计算方法1.2.2 基于编码的反爬虫设计1.2.3 Python自带base64模块实现base64编码解码类封装 1.3 MD5消息摘要算法1.3.1 MD5 介绍1.3.2 Python实现md5以及其他常用消息摘要算法封装 1.4 对称加密与…

在kuboard中添加k8s集群

1.登录kuboard后&#xff0c;点击添加集群面板 系统会跳转到k8s集群添加页面&#xff0c;按照页面提示输入自身的集群信息即可&#xff0c;此处没有什么难点。 添加成功后&#xff0c;点击集群面板&#xff0c;然后点击集群概要信息&#xff0c;就可以查看集群节点信息。 集群节…

mac配置maven

在 macOS 上配置 Maven 也相对简单。以下是一种常用的方法&#xff1a; 1. 安装maven **下载 Maven&#xff1a;**首先&#xff0c;你需要从 Maven 官网&#xff08;https://maven.apache.org/download.cgi&#xff09;下载最新版本的 Maven。你可以选择二进制压缩包&#xf…

ctfshow web78 获取flag(用老版的火狐浏览器)

题&#xff1a; 第一种&#xff1a;利用input伪协议 ,获取到flag ?filephp://input POST data <?php system(tac ls) ?> 第二种&#xff1a;利用flter协议,获取到flag https://21d9e58a-c0fd-47ea-a9c4-d875100f2fdb.challenge.ctf.show/?filephp://filter/readcon…

Java网址url工具类

功能描述 无需引入三方依赖文本匹配网址&#xff08;支持多个&#xff09;网址解析&#xff08;包括协议、主机、路径、参数等&#xff09; package com.qiangesoft.image.utils;import org.springframework.util.Assert; import org.springframework.util.CollectionUtils;i…

使用 Wireshark 实现 ARP 嗅探监听网络

前言 Wireshark是一个开源的网络协议分析工具&#xff0c;用于捕获和分析网络数据包。它可以在多个操作系统上运行&#xff0c;并提供了强大的功能和用户友好的界面。 通过Wireshark&#xff0c;用户可以捕获网络流量&#xff0c;并对其进行深入的分析。它支持多种协议的解析…

Unity 合并子物体获得简化Mesh

合并子物体获得简化Mesh &#x1f959;环境&#x1f96a;Demo &#x1f959;环境 PackageManager安装Editor Coroutines 导入插件&#x1f448; &#x1f96a;Demo 生成参数微调&#xff1a;Assets/EasyColliderEditor/Scripts/VHACDSettings/VHACDSettings.asset

大数据开发工作中的数仓设计(Hadoop,hive ,mysql )

1.HUE工具介绍使用 HUE是CDH提供一个hive和hdfs的操作工具&#xff0c;在hue中编写了hiveSQl也可以操作hdfs的文件 http://主机名字:端口号 hdfs的web访问端口 http://主机名字:端口号 hdfs的程序访问端口 进入后确保hdfs hive yarn 开启 在点击hue开启 在这里面也可以进行h…

android studio拍照功能问题解决

1.点击拍照功能直接闪退 2.拍照后不能选择确认键&#xff0c;无法保存 上述是在android studio做项目中经常会使用到模拟器或真机的拍照功能时主要遇到的两个问题。 解决方法&#xff1a; 1.直接闪退问题&#xff1a; if(Build.VERSION.SDK_INT>Build.VERSION_CODES.N)…

【HTML植物大战僵尸源码】

HTML植物大战僵尸源码 效果图部分源码领取源码下期更新预报 效果图 部分源码 index.html <html> <head> <meta http-equiv"Content-Type" content"text/html; charsetUTF-8"> <meta name"KeyWords" content"lonelys…

Vue3 + TS 项目实战 - 后台管理系统 - 按钮权限

前期回顾 网站的打赏 —— 新一代的思路-CSDN博客https://blog.csdn.net/m0_57904695/article/details/136704914?spm1001.2014.3001.5501 目录 &#x1f6a9; XX银行_系统管理_按钮权限控制_前端_提测单 项目信息 提测版本信息 功能列表 测试范围 测试环境 ✅ 步…

05-MessageConverter和ControllerAdvice

准备对象 Data static class User {private String name;private int age;JsonCreator // 默认jackson会使用无参构造器反序列化 这里强制使用当前带参构造器public User(JsonProperty("name") String name, JsonProperty("age") int age) {this.name …

《21天学通C++》(第十一章)多态

为什么需要多态&#xff1f; 为了最大限度地减少代码&#xff0c;提高可读性 1.虚函数 虚函数是C中的一种特殊成员函数&#xff0c;它允许在派生类&#xff08;也称为子类&#xff09;中重写&#xff08;覆盖&#xff09;基类的实现&#xff0c;使用virtual进行声明 在C中&am…