ffmpeg 实现多视频轨录制到同一个文件
引言
在视频录制中,有时会碰到这样一个需求,将不同摄像头的画面写入到一个视频文件,这个叫法很多,有的厂家叫合流模式,有的叫多画面多流模式。无论如何,它们的实质都是在一个视频文件上实现多路不同分辨率视频的保存。
经过调查,支持这种需求封装格式的有MP4、MOV、MKV 等,这里因为MP4 格式应用最广泛。
原理
ffmpeg 有一个map命令,可以将多路视频轨封装在一个视频容器,掰ffmpeg源码发现其实新建一个新的AVStream,修改stream->index,就可以实现多流录制的目的。
ffmpeg -i input.mp4 -i test.mp4 -map 0:v:0 -map 1:v -map 0:a -map 1:a -c copy -y mix.mp4
#include <iostream>
#include <string>extern "C"
{#include <libavutil/timestamp.h>#include <libavformat/avformat.h>
}typedef struct
{char* file_name;AVFormatContext* fmt_ctx;int video_index;int audio_index;int source_index;double last_pts[2];double last_dts[2];AVRational video_time_base;AVRational audio_time_base;bool is_end;
}InputStream;int create_stream(InputStream *input_stream, AVFormatContext* out_fmt_ctx, int &stream_num)
{int i = 0;int ret = 0;if ((ret = avformat_open_input(&input_stream->fmt_ctx, input_stream->file_name, 0, 0)) < 0) //打开输出文件{fprintf(stderr, "Could not open input file '%s'", input_stream->file_name);goto end;}if ((ret = avformat_find_stream_info(input_stream->fmt_ctx, 0)) < 0) //打开输入{fprintf(stderr, "Failed to retrieve input stream information");goto end;}//av_dump_format(input_stream->fmt_ctx, 0, input_stream->file_name, 0);//打印信息for (size_t i = 0; i < input_stream->fmt_ctx->nb_streams; i++){AVStream* in_stream = input_stream->fmt_ctx->streams[i];AVStream* out_stream = avformat_new_stream(out_fmt_ctx, in_stream->codec->codec);if (in_stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO){input_stream->audio_index = i;input_stream->audio_time_base = in_stream->time_base;printf("流 %d 是音频 \n", input_stream->source_index + i);}if (in_stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO){input_stream->video_index = i;input_stream->video_time_base = in_stream->time_base;printf("流 %d 是视频 \n", input_stream->source_index + i);}if (!out_stream){fprintf(stderr, "Failed allocating output stream\n");goto end;}ret = avcodec_copy_context(out_stream->codec, in_stream->codec);if (ret < 0){fprintf(stderr, "Failed to copy context from input to output stream codec context\n");goto end;}out_stream->codec->codec_tag = 0;if (out_fmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)out_stream->codec->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;stream_num++;}
end:return ret;
}int write_a_channel(InputStream *stream, AVFormatContext *out_fmt_ctx)
{int ret = -1;AVPacket packet;ret = av_read_frame(stream->fmt_ctx, &packet);if (ret < 0){if (ret == AVERROR_EOF){//printf("readFrame报错 %d\n", ret);av_free_packet(&packet);return ret;}}AVRational time_base = {0};AVStream* in_stream = stream->fmt_ctx->streams[packet.stream_index];AVStream* out_stream = out_fmt_ctx->streams[packet.stream_index];packet.stream_index = stream->source_index + packet.stream_index;if (in_stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO){time_base = stream->video_time_base;}if (in_stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO){time_base = stream->audio_time_base;}packet.pts = av_rescale_q_rnd(packet.pts, time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));packet.dts = av_rescale_q_rnd(packet.dts, time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));if (packet.pts < 0){packet.pts = 0;}if (packet.dts < 0){packet.dts = 0;}//printf("%d, pts %d dts %d \n", packet.stream_index, packet.pts, packet.dts);ret = av_interleaved_write_frame(out_fmt_ctx, &packet);if (ret < 0){printf("写入错误\n");}av_free_packet(&packet);return ret;
}int main(int argc, char** argv)
{int ret = -1;char file_path[100][100] = {"D:\\素材\\test1.mp4", "D:\\素材\\test2.mp4", "D:\\素材\\test3.mp4", "D:\\素材\\test4.mp4"};int count = sizeof(file_path) / sizeof(char);const char *out_fileName = "my_muxing.mp4";av_register_all();AVOutputFormat* ofmt = NULL;AVFormatContext* out_fmt_ctx = NULL;const int channel_num = 4;avformat_alloc_output_context2(&out_fmt_ctx, NULL, NULL, out_fileName);//创建streams InputStream stream_arry[channel_num] = {0};int stream_index = 0;for (int i = 0; i < channel_num; i++){ int stream_num = 0;stream_arry[i].file_name = file_path[i];stream_arry[i].source_index = stream_index;ret = create_stream(&stream_arry[i], out_fmt_ctx, stream_index);stream_index += stream_num;}if (!out_fmt_ctx){return -1;}ofmt = out_fmt_ctx->oformat;if (!(ofmt->flags & AVFMT_NOFILE)){ret = avio_open(&out_fmt_ctx->pb, out_fileName, AVIO_FLAG_WRITE);if (ret < 0){fprintf(stderr, "Could not open output file '%s'", out_fileName);return -1;}}AVDictionary* opts = NULL;av_dict_set(&opts, "movflags", "frag_keyframe+empty_moov", 0);//fmp4输出ret = avformat_write_header(out_fmt_ctx, &opts);if (ret < 0){fprintf(stderr, "Error occurred when opening output file\n");return -1;}while (true){int finish_count = 0;for (int i = 0; i < channel_num; i++){if (!stream_arry[i].is_end){ret = write_a_channel(&stream_arry[i], out_fmt_ctx);if (ret == AVERROR_EOF){stream_arry[i].is_end = true;finish_count++;}else{continue;}}}if (finish_count >= channel_num - 1){break;}}//清理for (size_t i = 0; i < channel_num; i++){avformat_close_input(&stream_arry[i].fmt_ctx);}ret = av_write_trailer(out_fmt_ctx);printf("==============合并完毕,写文件尾部 %d=============\n", ret);return 0;
}
设置flags 避免播放器拖动的时候出现花屏。
packet->flags = isKeyFrame ? packet->flags | AV_PKT_FLAG_KEY : packet->flags;
相关文章:
ffmpeg 实现多视频轨录制到同一个文件
引言 在视频录制中,有时会碰到这样一个需求,将不同摄像头的画面写入到一个视频文件,这个叫法很多,有的厂家叫合流模式,有的叫多画面多流模式。无论如何,它们的实质都是在一个视频文件上实现多路不同分辨率视…...
vue3中子组件调用父组件的方法
<script lang"ts" setup>前提 父组件: 子组件: const emit defineEmits([closeson]) 在子组件的方法中使用: emit(closeson)...
使用OkHttp上传本地图片及参数
下面以一个例子来讲解在项目中如何使用OKHttp来对本地图片做个最简单的上传功能,基本上无封装,只需要简单调用便可(对于OKHttp的引入不再单独做介绍)。 1:构建上传图片附带的参数(params) Map…...
无公网IP环境如何SSH远程连接Deepin操作系统
文章目录 前言1. 开启SSH服务2. Deppin安装Cpolar3. 配置ssh公网地址4. 公网远程SSH连接5. 固定连接SSH公网地址6. SSH固定地址连接测试 前言 Deepin操作系统是一个基于Debian的Linux操作系统,专注于使用者对日常办公、学习、生活和娱乐的操作体验的极致࿰…...
不会代码(零基础)学语音开发(语音控制板载双继电器)
继电器的用途可广了,这个语音控制用处也特别广。继电器,它实际上是一种“自动开关”,用小电流去控制大电流运作,在电路中起着自动调节、安全保护、转换电路等作用。 在日常生活中,你插入汽车钥匙,车辆可以…...
在imx6ull中加入ov5640模块
本来觉得是一件很简单的事情但是走了很多的弯路,记录一下调试过程。 先使用正点原子提供的出厂内核把摄像头影像调试出来,然后cat /dev/video1,看一下video1牵扯到哪些模块,可以看到需要ov5640_camera.ko和 mx6s_capture.ko这两个…...
Kafka中的auto-offset-reset配置
Kafka这个服务在启动时会依赖于Zookeeper,Kafka相关的部分数据也会存储在Zookeeper中。如果kafka或者Zookeeper中存在脏数据的话(即错误数据),这个时候虽然生产者可以正常生产消息,但是消费者会出现无法正常消费消息的…...
TCP/IP_整理起因
先分享一个初级的问题;有个客户现场,终端设备使用客户网络更新很慢,使用手机热点更新速度符合预期;网络部署情况如下: 前期花费了很大的精力进行问题排查对比,怀疑是客户网络问题(其他的客户现…...
CG-0A 电子水尺水导电测量原理应用于道路积水监测
CG-0A 电子水尺水导电测量原理应用于道路积水监测产品概述 本产品是一种采用微处理器芯片为控制器,内置通讯电路的数字式水位传感器,具备高的可靠性及抗干扰性能。适用于江、河、湖、水库及蓄水池、水渠等处的水位测量使用。 本产品采用了生产工艺技术…...
openEuler JDK21 部署 Zookeeper 集群
zookeeper-jdk21 操作系统:openEuler JDK:21 主机名IP地址spark01192.168.171.101spark02192.168.171.102spark03192.168.171.103 安装 1. 升级内核和软件 yum -y update2. 安装常用软件 yum -y install gcc gcc-c autoconf automake cmake make \zl…...
前端——html拖拽原理
文章目录 ⭐前言⭐draggable属性💖 api💖 单向拖动示例💖 双向拖动示例 ⭐总结⭐结束 ⭐前言 大家好,我是yma16,本文分享关于 前端——html拖拽原理。 vue3系列相关文章: vue3 fastapi 实现选择目录所有文…...
JVM 执行引擎篇
机器码、指令、汇编语言 机器码 各种用二进制编码方式表示的指令,叫做机器指令码。开始,人们就用它采编写程序,这就是机器语言。机器语言虽然能够被计算机理解和接受,但和人们的语言差别太大,不易被人们理解和记忆&a…...
js中数组对象去重的方法
前端面试题库 (面试必备) 推荐:★★★★★ 地址:前端面试题库 最近工作中需要用到数组对象去重的方法,我是怎么想也没想出来,今天稍微研究了一下,总算找到了2种方法。分享一下&…...
【送书活动四期】被GitHub 要求强制开启 2FA 双重身份验证,我该怎么办?
记得是因为fork了OpenZeppelin/openzeppelin-contracts的项目,之后就被GitHub 要求强制开启 2FA 双重身份验证了,一拖再拖,再过几天帐户操作将受到限制了,只能去搞一下了 目录 2FA是什么为什么要开启 2FA 验证GitHub 欲在整个平台…...
GO设计模式——13、享元模式(结构型)
目录 享元模式(Flyweight Pattern) 享元模式的核心角色: 优缺点 使用场景 注意事项 代码实现 享元模式(Flyweight Pattern) 享元模式(Flyweight Pattern)它通过共享对象来减少内存使用和提…...
Linux 网络协议
1 网络基础 1.1 网络概念 网络是一组计算机或者网络设备通过有形的线缆或者无形的媒介如无线,连接起来,按照一定的规则,进行通讯的集合( 缺一不可 )。 5G的来临以及IPv6的不断普及,能够进行联网的设备将会是越来越多(…...
【C语言】7-32 刮刮彩票 分数 20
7-32 刮刮彩票 分数 20 全屏浏览题目 切换布局 作者 DAI, Longao 单位 杭州百腾教育科技有限公司 “刮刮彩票”是一款网络游戏里面的一个小游戏。如图所示: 每次游戏玩家会拿到一张彩票,上面会有 9 个数字,分别为数字 1 到数字 9…...
交叉验证以及scikit-learn实现
交叉验证 交叉验证既可以解决数据集的数据量不够大问题,也可以解决参数调优的问题。 主要有三种方式: 简单交叉验证(HoldOut检验)、k折交叉验证(k-fold交叉验证)、自助法。 本文仅针对k折交叉验证做详细解…...
css实现头部占一定高度,内容区占剩余高度可滚动
上下布局: <div class"container"><header class"header">头部内容</header><div class"content">内容区域</div> </div>.container {display: flex;flex-direction: column;height: 100vh; /*…...
redis主从复制模式和哨兵机制
目录 第一章、主从复制模式1.1)Redis 主从复制模式介绍1.2)Redis 主从复制实现、 第二章、哨兵机制2.1)容灾处理之哨兵2.2)Sentinel 配置 第一章、主从复制模式 1.1)Redis 主从复制模式介绍 ①单点故障:数…...
Vim 调用外部命令学习笔记
Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...
Linux应用开发之网络套接字编程(实例篇)
服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …...
为什么需要建设工程项目管理?工程项目管理有哪些亮点功能?
在建筑行业,项目管理的重要性不言而喻。随着工程规模的扩大、技术复杂度的提升,传统的管理模式已经难以满足现代工程的需求。过去,许多企业依赖手工记录、口头沟通和分散的信息管理,导致效率低下、成本失控、风险频发。例如&#…...
04-初识css
一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…...
听写流程自动化实践,轻量级教育辅助
随着智能教育工具的发展,越来越多的传统学习方式正在被数字化、自动化所优化。听写作为语文、英语等学科中重要的基础训练形式,也迎来了更高效的解决方案。 这是一款轻量但功能强大的听写辅助工具。它是基于本地词库与可选在线语音引擎构建,…...
c# 局部函数 定义、功能与示例
C# 局部函数:定义、功能与示例 1. 定义与功能 局部函数(Local Function)是嵌套在另一个方法内部的私有方法,仅在包含它的方法内可见。 • 作用:封装仅用于当前方法的逻辑,避免污染类作用域,提升…...
DeepSeek源码深度解析 × 华为仓颉语言编程精粹——从MoE架构到全场景开发生态
前言 在人工智能技术飞速发展的今天,深度学习与大模型技术已成为推动行业变革的核心驱动力,而高效、灵活的开发工具与编程语言则为技术创新提供了重要支撑。本书以两大前沿技术领域为核心,系统性地呈现了两部深度技术著作的精华:…...
Modbus RTU与Modbus TCP详解指南
目录 1. Modbus协议基础 1.1 什么是Modbus? 1.2 Modbus协议历史 1.3 Modbus协议族 1.4 Modbus通信模型 🎭 主从架构 🔄 请求响应模式 2. Modbus RTU详解 2.1 RTU是什么? 2.2 RTU物理层 🔌 连接方式 ⚡ 通信参数 2.3 RTU数据帧格式 📦 帧结构详解 🔍…...
从物理机到云原生:全面解析计算虚拟化技术的演进与应用
前言:我的虚拟化技术探索之旅 我最早接触"虚拟机"的概念是从Java开始的——JVM(Java Virtual Machine)让"一次编写,到处运行"成为可能。这个软件层面的虚拟化让我着迷,但直到后来接触VMware和Doc…...
基于江科大stm32屏幕驱动,实现OLED多级菜单(动画效果),结构体链表实现(独创源码)
引言 在嵌入式系统中,用户界面的设计往往直接影响到用户体验。本文将以STM32微控制器和OLED显示屏为例,介绍如何实现一个多级菜单系统。该系统支持用户通过按键导航菜单,执行相应操作,并提供平滑的滚动动画效果。 本文设计了一个…...
