设计公司企业想法,奢侈品网站怎么做tuig优化,西安做网页的公司,佛山seo网站推广author: hjjdebug date: 2025年 07月 22日 星期二 17:06:02 CST descrip: 用ffmpeg 进行视频的拼接 文章目录1. 指定协议为concat 方式.1.1 协议为concat 模式,会调用 concat_open 函数1.2 当读数据时,会调用concat_read2. 指定file_format 为 concat 方式2.1 调用concat_read_…
author: hjjdebug date: 2025年 07月 22日 星期二 17:06:02 CST descrip: 用ffmpeg 进行视频的拼接 文章目录1. 指定协议为concat 方式.1.1 协议为concat 模式,会调用 concat_open 函数1.2 当读数据时,会调用concat_read2. 指定file_format 为 concat 方式2.1 调用concat_read_header 时,读入文件信息2.2 调用concat_read_packet 来读取数据包2.3 怎样打开下一个文件3. 使用 filter concat1. 指定协议为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; //但这里中途退出了,相当于i0, 没有给 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]concatn2:v1:a1” output.mp4 -y v1 和 a1表示只保留 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]scale1280:720[bg];[1:v]crop1280:720[fg];[bg][fg]overlay0:0” output.mp4