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

用ffmpeg 进行视频的拼接


author: hjjdebug
date: 2025年 07月 22日 星期二 17:06:02 CST
descrip: 用ffmpeg 进行视频的拼接


文章目录

  • 1. 指定协议为concat 方式.
    • 1.1 协议为concat 模式,会调用 concat_open 函数
    • 1.2 当读数据时,会调用concat_read
  • 2. 指定file_format 为 concat 方式
    • 2.1 调用concat_read_header 时,读入文件信息
    • 2.2 调用concat_read_packet 来读取数据包
    • 2.3 怎样打开下一个文件
  • 3. 使用 filter concat

1. 指定协议为concat 方式.

举例:
ffmpeg -i “concat:short1.ts|1.ts” -c copy output.ts

工作原理:
在libavformat/concat.c 文件有该协议的实现

1.1 协议为concat 模式,会调用 concat_open 函数

会引起读写数据时,由concat协议控制从文件中读数据,当第一个文件读到尾时,
接着从第二个文件读
分析字符串:“concat:short1.ts|1.ts”, 找到文件名 “short1.ts”,“1.ts”,
用一个循环把文件都打开.
err = ffurl_open_whitelist(&uc, node_uri, flags,
&h->interrupt_callback, NULL, h->protocol_whitelist, h->protocol_blacklist, h);
并保留uc 到nodes
nodes[i].uc = uc;
nodes[i].size = size;
total_size += size;

1.2 当读数据时,会调用concat_read

static int concat_read(URLContext *h, unsigned char *buf, int size)
{int result, total = 0;struct concat_data  *data  = h->priv_data;  //拿到数据上下文struct concat_nodes *nodes = data->nodes;size_t i                   = data->current;while (size > 0) {result = ffurl_read(nodes[i].uc, buf, size); //从URLContext 中读取数据if (result == AVERROR_EOF) { //如果到了文件尾if (i + 1 == data->length ||   //存在下一个文件ffurl_seek(nodes[++i].uc, 0, SEEK_SET) < 0) //从下一个文件读取数据break;result = 0;}if (result < 0)return total ? total : result;total += result;buf   += result;size  -= result;}data->current = i;return total ? total : result;
}

2. 指定file_format 为 concat 方式

举例:
创建 filelist.txt 文件,内容如下:
file ‘short1.ts’
file ‘1.ts’
ffmpeg -f concat -i filelist.txt -c copy output.mp4 -y

工作原理:
在libavformat/concatdec.c 文件有该demuxer的实现
定义了concat_read_header, concat_read_packet, concat_seek 等函数
当指定format 为concat 会找到 concat_demuxer

2.1 调用concat_read_header 时,读入文件信息

具体代码:

static int concat_read_header(AVFormatContext *avf)
{ConcatContext *cat = avf->priv_data; //拿到上下文int64_t time = 0;unsigned i;int ret = concat_parse_script(avf); //分析输入文件if (ret < 0) return ret;for (i = 0; i < cat->nb_files; i++)  //枚举处理每一个输入文件, 但中途退出了.{if (cat->files[i].start_time == AV_NOPTS_VALUE)cat->files[i].start_time = time;elsetime = cat->files[i].start_time;if (cat->files[i].user_duration == AV_NOPTS_VALUE) {if (cat->files[i].inpoint == AV_NOPTS_VALUE || cat->files[i].outpoint == AV_NOPTS_VALUE ||cat->files[i].outpoint - (uint64_t)cat->files[i].inpoint != av_sat_sub64(cat->files[i].outpoint, cat->files[i].inpoint))break; //但这里中途退出了,相当于i==0, 没有给 duration 赋值cat->files[i].user_duration = cat->files[i].outpoint - cat->files[i].inpoint;}cat->files[i].duration = cat->files[i].user_duration;time += cat->files[i].user_duration;}if (i == cat->nb_files) {avf->duration = time;cat->seekable = 1;}cat->stream_match_mode = avf->nb_streams ? MATCH_EXACT_ID :MATCH_ONE_TO_ONE;if ((ret = open_file(avf, 0)) < 0) //前面代码都没有用, 此处avf已经知道了文件名,用第一个文件打开AVFormatCtxreturn ret;return 0;
}

2.2 调用concat_read_packet 来读取数据包

可以在这里控制从第一个文件中组包, 当第一个文件到达文件尾时,从第2个文件读包.
具体实现:

static int concat_read_packet(AVFormatContext *avf, AVPacket *pkt)
{ConcatContext *cat = avf->priv_data;int ret;int64_t delta;ConcatStream *cs;AVStream *st;FFStream *sti;if (cat->eof)return AVERROR_EOF;if (!cat->avf)return AVERROR(EIO);while (1) {ret = av_read_frame(cat->avf, pkt);  // 靠 读取frame 来处理数据if (ret == AVERROR_EOF) { //文件到达尾部后if ((ret = open_next_file(avf)) < 0)  //切换到下一个文件,继续读包return ret;continue;}if (ret < 0) return ret;//流不匹配返回错误if ((ret = match_streams(avf)) < 0) {return ret;}//包位置判断if (packet_after_outpoint(cat, pkt)) {av_packet_unref(pkt);if ((ret = open_next_file(avf)) < 0)return ret;continue;}//获取ConcatStream 指针供后续使用cs = &cat->cur_file->streams[pkt->stream_index];if (cs->out_stream_index < 0) {av_packet_unref(pkt);continue;}break;}if ((ret = filter_packet(avf, cs, pkt)) < 0) return ret; //检查是否需要bsf处理,一般格式会直接返回st = cat->avf->streams[pkt->stream_index];sti = ffstream(st);//时间戳转换av_log(avf, AV_LOG_DEBUG, "file:%d stream:%d pts:%s pts_time:%s dts:%s dts_time:%s",(unsigned)(cat->cur_file - cat->files), pkt->stream_index,av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, &st->time_base),av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, &st->time_base));delta = av_rescale_q(cat->cur_file->start_time - cat->cur_file->file_inpoint,AV_TIME_BASE_Q,cat->avf->streams[pkt->stream_index]->time_base);if (pkt->pts != AV_NOPTS_VALUE) pkt->pts += delta;if (pkt->dts != AV_NOPTS_VALUE) pkt->dts += delta;av_log(avf, AV_LOG_DEBUG, " -> pts:%s pts_time:%s dts:%s dts_time:%s\n",av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, &st->time_base),av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, &st->time_base));//metadata 处理if (cat->cur_file->metadata) {size_t metadata_len;char* packed_metadata = av_packet_pack_dictionary(cat->cur_file->metadata, &metadata_len);if (!packed_metadata)return AVERROR(ENOMEM);ret = av_packet_add_side_data(pkt, AV_PKT_DATA_STRINGS_METADATA,packed_metadata, metadata_len);if (ret < 0) {av_freep(&packed_metadata);return ret;}}//时间戳处理if (cat->cur_file->duration == AV_NOPTS_VALUE && sti->cur_dts != AV_NOPTS_VALUE) {int64_t next_dts = av_rescale_q(sti->cur_dts, st->time_base, AV_TIME_BASE_Q);if (cat->cur_file->next_dts == AV_NOPTS_VALUE || next_dts > cat->cur_file->next_dts) {cat->cur_file->next_dts = next_dts;}}//pkt流索引号pkt->stream_index = cs->out_stream_index;return 0;
}

2.3 怎样打开下一个文件

static int open_next_file(AVFormatContext *avf)
{ConcatContext *cat = avf->priv_data;          //拿到上下文unsigned fileno = cat->cur_file - cat->files; //取到文件号 0,1,2...cat->cur_file->duration = get_best_effort_duration(cat->cur_file, cat->avf); //获取当前文件时长if (++fileno >= cat->nb_files) { //文件号加1 并判断是否到尾.cat->eof = 1;return AVERROR_EOF;}return open_file(avf, fileno); //用fileno 打开AVFormatContext
}

concat 作为协议,与concat 作为demux 控制的时机是不同的.
前者是读数据的时候.
后者是组包的时候.

组包是先读取数据,再从数据中挑出有用的负载组成包.

3. 使用 filter concat

举例:
ffmpeg -i input1.mp4 -i input2.mp4 -filter_complex “[0:v][0:a][1:v][1:a]concat=n=2:v=1:a=1” output.mp4 -y
v=1 和 a=1:表示只保留 1 个视频流和 1 个音频流。

编码参数中不能够使用-c copy,
Filtering and streamcopy cannot be used together.
过滤器有解码和编码参与,使得执行速度大大降低,只有2-3倍速而已. 详细过程也没分析透,此处忽略.

其它filter 使用举例.
举例:由scale 和 crop:统一分辨率。然后用overlay进行叠加的过滤器链. 这里用了3个过滤器,scale,crop,overlay
ffmpeg -i input1.mp4 -i input2.mp4 -filter_complex “[0:v]scale=1280:720[bg];[1:v]crop=1280:720[fg];[bg][fg]overlay=0:0” output.mp4

http://www.lryc.cn/news/596163.html

相关文章:

  • 从 0 到 1 搞定nvidia 独显推流:硬件视频编码环境安装完整学习笔记
  • Golang避免主协程退出方案
  • 前端葵花宝典
  • 《Uniapp-Vue 3-TS 实战开发》自定义时间选择
  • el-input 动态获焦
  • Vue 脚手架基础特性
  • js 数字逢三切断、整数最大9位、小数最大2位
  • SpringBoot+Mybatis+MySQL+Vue+ElementUI前后端分离版:权限管理(三)
  • ucharts 搭配uniapp 自定义x轴文字 实现截取显示
  • redis秒杀之lua脚本
  • 企业微信快捷回复设定方法(提高效率)
  • 如何永久删除安卓设备中的照片(已验证)
  • 大型语言模型(Large Language Models,LLM)
  • REASONING ELICITATION IN LANGUAGE MODELSVIA COUNTERFACTUAL FEEDBACK
  • AWS OpenSearch 搜索排序常见用法
  • 如何加固Endpoint Central服务器的安全?(上)
  • 【运维】SGLang服务器参数配置详解
  • Python趣味算法:折半查找(二分查找)算法终极指南——原理、实现与优化
  • SQL Server 查询优化
  • 电子电气架构 --- 从软件质量看组织转型路径
  • 【NLP舆情分析】基于python微博舆情分析可视化系统(flask+pandas+echarts) 视频教程 - 访问鉴权功能实现
  • 5G 智慧矿山监控终端
  • UE5 UI 控件切换器
  • 记录解决问题--使用maven help插件一次性上传所有依赖到离线环境,spring-boot-starter-undertow离线环境缺少依赖
  • Jenkins 多架构并发构建实战
  • gitlab私服搭建
  • wed前端简单解析
  • k8s:离线部署tomcatV11.0.9,报Cannot find /opt/bitnami/tomcat/bin/setclasspath.sh
  • 中国在远程医疗智能化方面有哪些特色发展模式?
  • 公交车客流人数统计管理解决方案:智能化技术与高效运营实践