如何使用FFmpeg(libav/libavfilter等)C/C++ API为视频帧添加图片或文字水印?
用FFmpeg libavfilter实现视频帧添加水印(图片/文字)
核心逻辑
基于FFmpeg的libavfilter构建滤镜图,将解码后的AVFrame送入滤镜链处理,完成水印添加后,输出处理后的帧用于编码。图片水印和文字水印的底层滤镜框架通用,仅需调整滤镜命令参数。
具体实现步骤
1. 初始化滤镜环境
首先注册所有FFmpeg滤镜,确保后续能调用所需滤镜:
avfilter_register_all();
2. 构建滤镜命令
根据水印类型选择对应滤镜:
- 图片水印:使用
overlay滤镜,示例命令(将logo置于右上角,保留10像素边距):
其中[in] overlay=W-w-10:H-h-10 [out]W/H为视频帧宽高,w/h为logo图宽高。 - 文字水印:使用
drawtext滤镜,示例命令(左上角白色24号文字):
若需显示中文,需追加字体路径参数:[in] drawtext=text='Hello World':fontsize=24:fontcolor=white:x=10:y=10 [out]fontfile=/path/to/your/font.ttf。
3. 初始化滤镜图与上下文
创建并配置滤镜图、输入输出上下文,解析滤镜命令并完成初始化:
// 初始化滤镜图 AVFilterGraph *filter_graph = avfilter_graph_alloc(); AVFilterInOut *inputs = avfilter_inout_alloc(); AVFilterInOut *outputs = avfilter_inout_alloc(); // 创建输入buffer滤镜(需匹配视频帧的像素格式、宽高、时间基) const AVFilter *buffer_src = avfilter_get_by_name("buffer"); AVFilterContext *buffer_src_ctx; AVDictionary *opts = NULL; av_dict_set(&opts, "video_size", "1920x1080", 0); // 替换为实际视频尺寸 av_dict_set(&opts, "pix_fmt", av_get_pix_fmt_name(AV_PIX_FMT_YUV420P), 0); // 替换为实际像素格式 av_dict_set(&opts, "time_base", "1/25", 0); // 替换为实际视频的time_base avfilter_graph_create_filter(&buffer_src_ctx, buffer_src, "in", NULL, &opts, filter_graph); // 创建输出buffersink滤镜 const AVFilter *buffer_sink = avfilter_get_by_name("buffersink"); AVFilterContext *buffer_sink_ctx; avfilter_graph_create_filter(&buffer_sink_ctx, buffer_sink, "out", NULL, NULL, filter_graph); // 绑定输入输出的InOut节点 inputs->name = av_strdup("out"); inputs->filter_ctx = buffer_sink_ctx; inputs->pad_idx = 0; inputs->next = NULL; outputs->name = av_strdup("in"); outputs->filter_ctx = buffer_src_ctx; outputs->pad_idx = 0; outputs->next = NULL; // 解析滤镜命令(以图片水印为例) char filter_desc[1024]; snprintf(filter_desc, sizeof(filter_desc), "[in] overlay=W-w-10:H-h-10 [out]"); int ret = avfilter_graph_parse_ptr(filter_graph, filter_desc, &inputs, &outputs, NULL); if (ret < 0) { // 错误处理逻辑 } // 完成滤镜图配置 ret = avfilter_graph_config(filter_graph, NULL); if (ret < 0) { // 错误处理逻辑 }
4. 处理解码帧并获取带水印的帧
将解码得到的AVFrame送入滤镜处理,再取出处理后的帧:
// 调整帧的时间基,匹配滤镜输入要求 frame->pts = av_rescale_q(frame->pts, codec_ctx->time_base, buffer_src_ctx->inputs[0]->time_base); // 将帧送入滤镜输入 int ret = av_buffersrc_add_frame_flags(buffer_src_ctx, frame, AV_BUFFERSRC_FLAG_KEEP_REF); if (ret < 0) { // 错误处理逻辑 } // 从滤镜输出获取处理后的帧 AVFrame *filtered_frame = av_frame_alloc(); ret = av_buffersink_get_frame(buffer_sink_ctx, filtered_frame); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { // 无帧可获取,结束处理或继续等待 } else if (ret < 0) { // 错误处理逻辑 } // filtered_frame即为带水印的帧,可用于后续编码
5. 资源释放
处理完成后释放所有滤镜相关资源:
av_frame_free(&filtered_frame); avfilter_graph_free(&filter_graph); avfilter_inout_free(&inputs); avfilter_inout_free(&outputs);
注意事项
- 图片水印需提前将logo解码为
AVFrame,并通过overlay的第二个输入构建多输入滤镜链;静态logo可解码一次后重复使用。 drawtext滤镜要求FFmpeg编译时开启--enable-libfreetype选项,否则无法使用。- 输入帧的像素格式必须与滤镜buffer源配置的格式一致,否则会触发参数不匹配错误。
内容的提问来源于stack exchange,提问作者Alex
相关产品推荐
相关产品推荐

