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

用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.xdnf.cn/news/15950.html

相关文章:

  • ni-app 对鸿蒙的支持现状
  • Redis的五大基本数据类型
  • 有关Spring的总结
  • 【每日算法】专题十七_多源 BFS
  • React基础(1)
  • 【HarmonyOS】ArkUI - 声明式开发范式
  • 空间曲线正交投影及其距离计算的理论与实践
  • Anaconda 路径精简后暴露 python 及工具到环境变量的配置记录 [二]
  • 苍穹外卖Day5
  • JAVA+AI教程-第三天
  • 使用Python绘制专业柱状图:Matplotlib完全指南
  • 原型与原型链
  • 三大工厂设计模式
  • 2025杭电多校赛(2)1006 半
  • I2S音频的时钟
  • Zabbix 企业级分布式监控系统深度解析
  • Leetcode力扣解题记录--第238题(前/后缀积)
  • Windows防火墙配置详解
  • 暑期算法训练.5
  • Xilinx FPGA XCKU115‑2FLVA1517I AMD KintexUltraScale
  • day058-docker常见面试题与初识zabbix
  • 果园里的温柔之手:Deepoc具身智能如何重塑采摘机器人的“生命感知”
  • CS课程项目设计4:支持AI人机对战的五子棋游戏
  • 计算机网络中:传输层和网络层之间是如何配合的
  • buntu 22.04 上离线安装Docker 25.0.5(二)
  • 动静态库原理与实战详解
  • Pycaita二次开发基础代码解析:边线提取、路径追踪与曲线固定
  • WebAPIs事件流与事件委托与其他事件
  • 力扣15:三数之和
  • 识别PDF中的二维码