一、技术背景与需求分析
在视频处理场景中,为MP4文件添加前后贴内容(如开场动画、片尾字幕)是常见需求。FFmpeg作为开源多媒体框架,其API支持精确控制视频流的时间轴操作。本文将以添加1秒前贴(黑屏静音)和3秒后贴为例,演示如何通过FFmpeg API实现无损拼接。
二、技术实现步骤
1. 环境准备
FFmpeg库配置:需链接libavformat(封装)、libavcodec(编解码)及libavutil(工具库)。
输入输出定义:假设输入文件为input.mp4,输出为output.mp4,前贴为1秒黑屏(header.mp4),后贴为3秒黑屏(tailer.mp4)。
2. 核心代码流程
int main() {
const char* inputs[] = {"header.mp4", "content.mp4", "tailer.mp4"};
const char* output = "merged.mp4";
AVOutputFormat* ofmt = NULL;
AVFormatContext* ifmt_ctx[3] = {NULL};
AVFormatContext* ofmt_ctx = NULL;
AVPacket pkt;
int ret, i;
// 初始化输出上下文
avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, output);
if (!ofmt_ctx) return -1;
ofmt = ofmt_ctx->oformat;
// 遍历输入文件
for (i = 0; i < 3; i++) {
if ((ret = avformat_open_input(&ifmt_ctx[i], inputs[i], NULL, NULL)) < 0) {
fprintf(stderr, "Could not open input file %s\n", inputs[i]);
goto end;
}
if ((ret = avformat_find_stream_info(ifmt_ctx[i], NULL)) < 0) {
fprintf(stderr, "Failed to retrieve stream info\n");
goto end;
}
}
// 由于三分文件编码参数一致,复制任何一个流信息到输出文件即可,这里选择第一个文件
for (unsigned int j = 0; j < ifmt_ctx[0]->nb_streams; j++) {
AVStream* in_stream = ifmt_ctx[0]->streams[j];
AVStream* out_stream = avformat_new_stream(ofmt_ctx, NULL);
if (!out_stream) {
ret = AVERROR(ENOMEM);
goto end;
}
ret = avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar);
if (ret < 0) {
fprintf(stderr, "Failed to copy codec parameters\n");
goto end;
}
out_stream->codecpar->codec_tag = 0;
}
// 打开输出文件
if (!(ofmt->flags & AVFMT_NOFILE)) {
ret = avio_open(&ofmt_ctx->pb, output, AVIO_FLAG_WRITE);
if (ret < 0) {
fprintf(stderr, "Could not open output file %s\n", output);
goto end;
}
}
// 写入文件头
ret = avformat_write_header(ofmt_ctx, NULL);
if (ret < 0) {
fprintf(stderr, "Error writing header\n");
goto end;
}
// 合并数据包
int video_dts_offset = 0;
int audio_dts_offset = 0;
int last_packet_stream_index = 0;
for (i = 0; i < 3; i++) {
while (1) {
ret = av_read_frame(ifmt_ctx[i], &pkt);
if (ret < 0) break;
AVStream* in_stream = ifmt_ctx[i]->streams[pkt.stream_index];
AVStream* out_stream = ofmt_ctx->streams[pkt.stream_index];
pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF);
pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF);
pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
last_packet_stream_index = pkt.stream_index;
if (is_video_packet){
// 视频
pkt.pts += video_dts_offset;
pkt.dts += video_dts_offset;
} else {
// 音频
pkt.pts += video_dts_offset;
pkt.dts += video_dts_offset;
}
pkt.pos = -1;
ret = av_interleaved_write_frame(ofmt_ctx, &pkt);
if (ret < 0) {
fprintf(stderr, "Error writing packet\n");
break;
}
av_packet_unref(&pkt);
}
// 伪代码,根据最后一帧的类型,分别计算音频帧和视频帧的偏移。
if (last_packet_stream_index == video_index){
audio_dts_offset =
last_packet_dts * av_q2d(v_stream->time_base) / av_q2d(a_stream->time_base);
video_dts_offset += last_packet_dts;
else {
audio_dts_offset += last_packet_dts;
video_dts_offset =
last_packet_dts * av_q2d(a_stream->time_base) / av_q2d(v_stream->time_base);
}
}
// 写入文件尾
av_write_trailer(ofmt_ctx);
end:
// 清理资源
for (i = 0; i < 3; i++) {
if (ifmt_ctx[i]) avformat_close_input(&ifmt_ctx[i]);
}
if (ofmt_ctx && !(ofmt->flags & AVFMT_NOFILE)) avio_closep(&ofmt_ctx->pb);
avformat_free_context(ofmt_ctx);
return ret < 0 ? 1 : 0;
}
3. 关键参数说明
时间戳控制:通过计算上一个文件的时间戳偏移, 应用到下一个视频的时间戳上,保证时间戳的连续性。
编码兼容性:输出流需与原视频编码格式(如H.264)一致,避免转码损失。
三、性能优化与注意事项
内存管理:使用av_packet_unref释放未使用的AVPacket,防止内存泄漏。
错误处理:检查avformat_write_header返回值,确保文件写入成功。
跨平台适配:Windows需注意路径分隔符,Linux需处理文件权限。
四、扩展应用
动态贴片:替换黑屏文件为自定义视频(如Logo动画),需调整分辨率与帧率匹配。
音频同步:若需添加音频贴片,需创建独立的AVStream并设置采样率。