searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

基于ffmpeg的视频文件拼接技术

2025-09-26 10:18:02
0
0

一、技术背景与需求分析

 
在视频处理场景中,为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并设置采样率。
0条评论
0 / 1000
g****n
5文章数
0粉丝数
g****n
5 文章 | 0 粉丝
原创

基于ffmpeg的视频文件拼接技术

2025-09-26 10:18:02
0
0

一、技术背景与需求分析

 
在视频处理场景中,为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并设置采样率。
文章来自个人专栏
文章 | 订阅
0条评论
0 / 1000
请输入你的评论
0
0