项目简介
本项目是通过ffmpeg代码实现给264/265裸码流添加水印.添加的水印有图片水印和文本水印两种;水印可以在视频画面中按一定路径移动,也可以在指定位置显示.
水印效果
图片水印

文本水印

ffmpeg添加水印流程
a.初始化输入文件 ,得到输入上下文 (输入格式上下文 ,解码上下文).
b.初始化输出文件,得到输出上下文(输出格式上下文,编码上下文).
c.初始化滤镜,得到滤镜图对象和滤镜上下文(输入滤镜上下文 ,输出滤镜上下文).
d.处理帧信息,将输入文件解码.
e.将解码后的帧传给滤镜处理接口,生成添加滤镜后的帧信息.
f.将添加滤镜后的帧信息编码,生成编码后的帧数据 .
g.将编码后的帧数据写入文件 .
ffmpeg添加水印实现代码
图片水印实现代码
cpp
int WaterMark::init_pic_filters(AVFilterGraph **graph, AVFilterContext **src, AVFilterContext **sink, AVCodecContext *dec_ctx, const char *watermark_path, const char *position)
{
char args[512];
const AVFilter *buffersrc ;
buffersrc = avfilter_get_by_name("buffer");
const AVFilter *buffersink ;
buffersink = avfilter_get_by_name("buffersink");
AVFilterInOut *outputs = avfilter_inout_alloc();
AVFilterInOut *inputs = avfilter_inout_alloc();
*graph = avfilter_graph_alloc();
if (!outputs || !inputs || !*graph) {
return AVERROR(ENOMEM);
}
// 配置输入源
#if 0
snprintf(args, sizeof(args),
"video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
dec_ctx->width, dec_ctx->height, dec_ctx->pix_fmt,
dec_ctx->time_base.num, dec_ctx->time_base.den,
dec_ctx->sample_aspect_ratio.num, dec_ctx->sample_aspect_ratio.den);
#else
snprintf(args, sizeof(args),
"video_size=%dx%d:pix_fmt=%s:time_base=%d/%d:pixel_aspect=%d/%d",
dec_ctx->width, dec_ctx->height, "yuv420p",
1, 25,
1, 1);
#endif
fprintf(stderr,"args:%s\n",args);
#if 0
if (avfilter_graph_create_filter(src, buffersrc, "in", args, NULL, *graph) < 0) {
fprintf(stderr, "Cannot create buffer source\n");
return -1;
}
// 配置输出接收器
if (avfilter_graph_create_filter(sink, buffersink, "out", NULL, NULL, *graph) < 0) {
fprintf(stderr, "Cannot create buffer sink\n");
return -1;
}
// 构建水印滤镜链
AVFilterContext *watermark_src;
AVFilterContext *overlay;
// 添加水印源滤镜
snprintf(args, sizeof(args), "filename=%s", watermark_path);
if (avfilter_graph_create_filter(&watermark_src, avfilter_get_by_name("movie"), "wm", args, NULL, *graph) < 0) {
fprintf(stderr, "Cannot create watermark source\n");
return -1;
}
char overlay_args[256];
//螺旋渐进式运动
snprintf(overlay_args, sizeof(overlay_args),
"x='main_w/2 + (main_w/2-overlay_w/2)*sin(t/2)*exp(-t/20)':"
"y='main_h/2 + (main_h/2-overlay_h/2)*cos(t/2)*exp(-t/20)':"
"enable='1'");
//对角线往返扫描
snprintf(overlay_args, sizeof(overlay_args),
"x='mod(t*30, main_w-overlay_w)':"
"y='mod(t*20, main_h-overlay_h)':"
"enable='between(t,0,30)'");
//八字形无限循环路径
snprintf(overlay_args, sizeof(overlay_args),
"x='main_w/2 + (main_w/3)*sin(t)':"
"y='main_h/2 + (main_h/4)*sin(2*t)':" // Lissajous曲线
"enable='1'");
//圆周运动+大小变化
// 动态水印速度控制参数
float speed_factor = 0.5; // 速度系数(1.0为原速)
snprintf(overlay_args, sizeof(overlay_args),
"x='main_w/2 + (main_w/3)*sin(%.1f*t) - overlay_w/2':" // 添加速度系数
"y='main_h/2 + (main_h/3)*cos(%.1f*t) - overlay_h/2':" // 同步修改Y轴
"enable='1'",
speed_factor, speed_factor); // 参数注入
// snprintf(overlay_args, sizeof(overlay_args),
// "x='main_w/2 + (main_w/3)*sin(t) - overlay_w/2':"
// "y='main_h/2 + (main_h/3)*cos(t) - overlay_h/2':"
// "enable='1'");
#if 0
//随机弹跳效果
snprintf(overlay_args, sizeof(overlay_args),
"x='if(lte(mod(t,3),1), random(1)*main_w, if(lte(mod(t,3),2), main_w-overlay_w, NAN))':"
"y='if(lte(mod(t,4),1), random(1)*main_h, if(lte(mod(t,4),3), main_h-overlay_h, NAN))':"
"enable='1'");
#endif
// 添加overlay滤镜
if (avfilter_graph_create_filter(&overlay, avfilter_get_by_name("overlay"), "overlay", overlay_args, NULL, *graph) < 0) {
fprintf(stderr, "Cannot create overlay filter\n");
return -1;
}
// 连接滤镜
if (avfilter_link(*src, 0, overlay, 0) < 0 ||
avfilter_link(watermark_src, 0, overlay, 1) < 0 ||
avfilter_link(overlay, 0, *sink, 0) < 0) {
fprintf(stderr, "Failed to link filters\n");
return -1;
}
#else
/*
//命令方式
ffmpeg -i input.265 -i title.png -filter_complex
"[1:v]format=rgba,rotate=PI/4:ow=rotw(PI/4):oh=roth(PI/4):c=0x00000000[wm];
[0:v][wm]overlay=10:10,format=yuv420p" -c:v libx265 output.mp4
*/
char watermark_args[512],overlay_args[512];
snprintf(watermark_args, sizeof(watermark_args), "filename=%s", watermark_path);
//八字形无限循环路径
snprintf(overlay_args, sizeof(overlay_args),
"x='main_w/2 + (main_w/3)*sin(t)':"
"y='main_h/2 + (main_h/4)*sin(2*t)':" // Lissajous曲线
"enable='1'");
fprintf(stderr,"watermark_args:%s\n",watermark_args);
//创建输入滤镜
if(avfilter_graph_create_filter(src, buffersrc, "in", args, NULL, *graph)< 0){
fprintf(stderr, "Cannot create in filter\n");
return -1;
}
if(avfilter_graph_create_filter(&m_watermark_src_ctx, avfilter_get_by_name("movie"), "watermark_in", watermark_args, NULL, *graph)<0){
fprintf(stderr, "Cannot create watermark_in filter\n");
return -1;
}
// 创建输出滤镜
if(avfilter_graph_create_filter(sink, buffersink, "out", NULL, NULL, *graph)<0){
fprintf(stderr, "Cannot create out filter\n");
return -1;
}
// 主视频源作为第一个输入
outputs->name = av_strdup("in");
outputs->filter_ctx = *src;
// 水印源作为第二个输入
AVFilterInOut *wm_inputs = avfilter_inout_alloc();
wm_inputs->name = av_strdup("watermark_in");
wm_inputs->filter_ctx = m_watermark_src_ctx;
//输入形成链表
outputs->next = wm_inputs;
inputs->name = av_strdup("out");
inputs->filter_ctx = *sink;
inputs->next = nullptr;
// 构建滤镜链(水印旋转+叠加)
char filter_desc[2048];
#if 1
//水印设置旋转
snprintf(filter_desc, sizeof(filter_desc),
"[watermark_in]format=rgba,rotate='%s':ow=rotw('%s'):oh=roth('%s'):c=0x00000000[wm];"
"[in][wm]overlay=x='main_w/2 + (main_w/3)*sin(0.5*t)':y='main_h/2 + (main_h/4)*cos(0.5*t)':enable='1'[out]",
ROTATION_ANGLE, ROTATION_ANGLE, ROTATION_ANGLE);
#endif
fprintf(stderr,"filter_desc:%s\n",filter_desc);
if ((avfilter_graph_parse_ptr(*graph, filter_desc,
&inputs, &outputs, NULL)) < 0) {
fprintf(stderr, "滤镜图解析失败\n");
return -1;
}
#endif
// 配置滤镜图
if (avfilter_graph_config(*graph, NULL) < 0) {
f