FFmpeg添加水印
项目简介
本项目是通过ffmpeg代码实现给264/265裸码流添加水印.添加的水印有图片水印和文本水印两种;水印可以在视频画面中按一定路径移动,也可以在指定位置显示.
水印效果
图片水印
文本水印
ffmpeg添加水印流程
a.初始化输入文件 ,得到输入上下文 (输入格式上下文 ,解码上下文).
b.初始化输出文件,得到输出上下文(输出格式上下文,编码上下文).
c.初始化滤镜,得到滤镜图对象和滤镜上下文(输入滤镜上下文 ,输出滤镜上下文).
d.处理帧信息,将输入文件解码.
e.将解码后的帧传给滤镜处理接口,生成添加滤镜后的帧信息.
f.将添加滤镜后的帧信息编码,生成编码后的帧数据 .
g.将编码后的帧数据写入文件 .
ffmpeg添加水印实现代码
图片水印实现代码
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 0snprintf(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);
#elsesnprintf(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);
#endiffprintf(stderr,"args:%s\n",args);#if 0if (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);
#endiffprintf(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