用ffmpeg 进行视频的拼接


author: hjjdebug

date: 2025年 07月 22日 星期二 17:06:02 CST

descrip: 用ffmpeg 进行视频的拼接


文章目录

  • [1. 指定协议为concat 方式.](#1. 指定协议为concat 方式.)
    • [1.1 协议为concat 模式,会调用 concat_open 函数](#1.1 协议为concat 模式,会调用 concat_open 函数)
    • [1.2 当读数据时,会调用concat_read](#1.2 当读数据时,会调用concat_read)
  • [2. 指定file_format 为 concat 方式](#2. 指定file_format 为 concat 方式)
    • [2.1 调用concat_read_header 时,读入文件信息](#2.1 调用concat_read_header 时,读入文件信息)
    • [2.2 调用concat_read_packet 来读取数据包](#2.2 调用concat_read_packet 来读取数据包)
    • [2.3 怎样打开下一个文件](#2.3 怎样打开下一个文件)
  • [3. 使用 filter concat](#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

coo 复制代码
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 时,读入文件信息

具体代码:

cpp 复制代码
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;
        else
            time = 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已经知道了文件名,用第一个文件打开AVFormatCtx
        return ret;

    return 0;
}

2.2 调用concat_read_packet 来读取数据包

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

具体实现:

cpp 复制代码
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 怎样打开下一个文件

cpp 复制代码
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

相关推荐
却道天凉_好个秋2 小时前
音视频学习(四十三):H264无损压缩
音视频·无损压缩·熵编码
zhangfeng11332 小时前
VTM 是“H.266/VVC 标准的官方参考软件”视频分析,入门教程,它存在的唯一目的就是“让学术界和工业界在同一把尺子上做实验
音视频·h.266
lxmyzzs2 小时前
【bug】 jetson上opencv无法录制h264本地视频
opencv·bug·音视频
胖虎113 小时前
(十九)深入了解 AVFoundation-编辑:使用 AVMutableVideoComposition 实现视频加水印与图层合成(上)——理论篇
音视频·视频编辑·视频水印·视频动画
后端小张13 小时前
智谱AI图生视频:从批处理到多线程优化
开发语言·人工智能·ai·langchain·音视频
Tracy97313 小时前
A316-Mini-V1:超小尺寸USB高清音频解码器模组技术探析
嵌入式硬件·音视频·智能硬件·xmos 模组
mixboot15 小时前
FFmpeg 图片处理
ffmpeg
lxmyzzs20 小时前
从 0 到 1 搞定nvidia 独显推流:硬件视频编码环境安装完整学习笔记
笔记·学习·音视频
南通SEO20 小时前
CentOS 7安装 FFmpeg问题可以按照以下步骤进行安装
linux·ffmpeg·centos