当前位置: 首页 > news >正文

FFmpeg过滤器实战:水印处理

过滤器实战:水印处理

这个 Demo 展示了如何使用 FFmpeg 的滤镜系统对视频进行处理,实现将视频上半部分倒置并与下半部分叠加的效果。下面我将详细解析这个代码。

程序整体结构

整个程序完成以下几个步骤:

  1. 初始化滤镜系统
  2. 构建滤镜图(Filter Graph)
  3. 处理输入视频帧
  4. 输出处理后的视频帧
  5. 清理资源

滤镜图设计分析

buffer_src → split → overlay (主输出)\→ crop → vflip → overlay (副输入)

代码分析

文件输入初始化

int ret = 0;// 出入文件和输出文件
FILE* in_file = NULL;
FILE* out_file = NULL;const char* in_file_name = NULL;
const char* out_file_name = NULL;if (argc <= 3) {printf("Usage ./ffmpeg_watermark [in_file] [out_file]\n");return -1;
}in_file_name = argv[1];
out_file_name = argv[2];in_file = fopen(in_file_name, "rb+");
if (!in_file) {printf("Failed to open in_file");return -1;
}out_file = fopen(out_file_name, "wb");
if (!out_file) {printf("Failed to open out_file");return -1;
}int width = 768;
int height = 320;
  • 我们定义一些出入文件和输出文件的参数,比如文件指针和文件路径。
  • 最后的 widthheight是我们输入视频流 frame 的宽和高。

过滤器图初始化(管理所有滤镜)

// 注册所有内置滤镜
avfilter_register_all();
// 初始化并分配一个空的滤镜图结构
AVFilterGraph* filter_graph = avfilter_graph_alloc();if (!filter_graph) {printf("avfilter_graph_alloc failed\n");return -1;
}char args[512];
sprintf(args, "video_sizeo=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
width, height, AV_PIX_FMT_YUV420P, 1, 25, 1, 1);
  • 使用 avfilter_graph_alloc函数来初始化一个空的滤镜图结构,滤镜图结构会统一管理所有的滤镜,所以后续的所有滤镜都需要注册到滤镜图上

滤镜初始化(具体的滤镜)

// 获取名为buffer的滤镜,用于像滤镜图输入原始数据
// 返回值AVFilter指向FFmpeg内置的buffer滤镜
AVFilter* buffer_src = avfilter_get_by_name("buffer");
AVFilterContext* buffer_src_ctx;
ret = avfilter_graph_create_filter(&buffer_src_ctx, buffer_src, "in", args, NULL, filter_graph);
if (ret < 0) {printf("avfilter_graph_create_filter buffer failed\n");return -1;
}// buffersink滤镜 作为滤镜的输出节点,用于从滤镜中提取处理后的帧(如缩放/裁剪后的视频帧)
AVBufferSinkParams* buffer_sink_params; // 配置输出帧的格式(如像素格式、音频采样格式等)
AVFilterContext* buffer_sink_ctx;
// 获取buffersink滤镜
AVFilter* buffer_sink_filter = avfilter_get_by_name("buffersink");
enum AVPixelFormat pix_fmts[] = {AV_PIX_FMT_YUV420P, AV_PIX_FMT_NONE}; // 指定允许的输出像素格式列表
buffer_sink_params->pixel_fmts = pix_fmts;
ret = avfilter_graph_create_filter(&buffer_sink_ctx, buffer_sink_filter, "out", NULL, buffer_sink_params, filter_graph);if (ret < 0) {printf("avfilter_graph_create_filter buffer_sink_params failed\n");return -1;
}AVFilter* split_filter = avfilter_get_by_name("split");
AVFilterContext* split_filter_ctx;
ret = avfilter_graph_create_filter(&split_filter_ctx, split_filter, "split", "outputs=2", NULL, filter_graph);if (ret < 0) {printf("avfilter_graph_create_filter split_filter failed\n");return -1;
}/*
out_w=iw:输出宽度=输入宽度
out_h=ih/2 输出高度=输入高度的一半
x=0 从x=0位置开始裁剪
y=0 从y=0位置开始裁剪
*/
AVFilter* crop_filter = avfilter_get_by_name("crop");
AVFilterContext* crop_filter_ctx;
ret = avfilter_graph_create_filter(&crop_filter_ctx, crop_filter, "crop", "out_w=iw:out_h=ih/2:x=0:y=0", NULL, filter_graph);if (ret < 0) {printf("avfilter_graph_create_filter crop_filter failed\n");return -1;
}AVFilter* vfilp_filter = avfilter_get_by_name("vflip");
AVFilterContext* vflip_filter_ctx;
ret = avfilter_graph_create_filter(&vflip_filter_ctx, vfilp_filter, "vflip", NULL, NULL, filter_graph);if (ret < 0) {printf("avfilter_graph_create_filter vflip_filter failed\n");return -1;
}AVFilter* overlay_filter = avfilter_get_by_name("overlay");
AVFilterContext* overlay_filter_ctx;
ret = avfilter_graph_create_filter(&overlay_filter_ctx, overlay_filter, "overlay", "y=0:H/2", NULL, filter_graph);if (ret < 0) {printf("avfilter_graph_create_filter overlay_filter failed\n");return -1;
}
  • split滤镜的作用如下
    • 将单个输入流复制为多个完全相同的输出流
    • 每个输出流包含与输入流完全相同的内容
    • 常用于需要将同一个视频源用于多个不同处理流程的场景
  • crop是裁剪滤镜,作用如下:
    • 从输入视频中裁剪出指定的矩形区域
    • 可以精确控制裁剪的位置和尺寸
    • 支持动态表达式计算裁剪参数
  • vflip是垂直翻转滤镜,作用如下:
    • 将输入视频帧沿水平轴(X 轴)进行垂直翻转
    • 实现“上下颠倒”的视觉效果
    • 不改变视频的分辨率和像素合适
    • 处理效率高,适合实时应用

滤镜组合

// 输入->split
ret = avfilter_link(buffer_src_ctx, 0, split_filter_ctx, 0);
if (ret != 0) {printf("avfilter_link buffer_src_ctx and split_filter_ctx failed\n");return -1;
}
// split主输出->overlay主输入
ret = avfilter_link(split_filter_ctx, 0, overlay_filter_ctx, 0);
if (ret != 0) {printf("avfilter_link split_filter_ctx and overlay_filter_ctx\n");return -1;
}
// split副输出->crop
ret = avfilter_link(split_filter_ctx, 1, crop_filter_ctx, 0);
if (ret != 0) {printf("avfilter_link split_filter_ctx and crop_filter_ctx\n");return -1;
}
// crop->vflip
ret = avfilter_link(crop_filter_ctx, 0, vflip_filter_ctx, 0);
if (ret != 0) {printf("avfilter_link crop_filter_ctx and vflip_filter_ctx\n");return -1;
}
// vflip->overlay副输入
ret = avfilter_link(vflip_filter_ctx, 0, overlay_filter_ctx, 1);
if (ret != 0) {printf("avfilter_link vflip_filter_ctx and overlay_filter_ctx\n");return -1;
}
// overlay->输出
ret = avfilter_link(overlay_filter_ctx, 0, buffer_sink_ctx, 0);
if (ret != 0) {printf("avfilter_link overlay_filter_ctx and buffer_sink_ctx\n");return -1;
}

输出 filter_graph 到文件

char* graph_str = avfilter_graph_dump(filter_graph, NULL);
FILE* graph_file = NULL;
graph_file = fopen("graph_file.txt", "w");
fprintf(graph_file, "%s", graph_str);
av_free(graph_str);

输出结果如下:

+----------+
|    in    |default--[768x320 1:1 yuv420p]--split:default
| (buffer) |
+----------++--------------+
overlay:default--[768x320 1:1 yuv420p]--default|     out      || (buffersink) |+--------------++---------+
in:default--[768x320 1:1 yuv420p]--default|  split  |output0--[768x320 1:1 yuv420p]--overlay:main| (split) |output1--[768x320 1:1 yuv420p]--crop:default+---------++--------+
split:output1--[768x320 1:1 yuv420p]--default|  crop  |default--[768x160 1:1 yuv420p]--vflip:default| (crop) |+--------++---------+
crop:default--[768x160 1:1 yuv420p]--default|  vflip  |default--[768x160 1:1 yuv420p]--auto_scaler_0:default| (vflip) |+---------++-----------+
split:output0----------[768x320 1:1 yuv420p]------main|  overlay  |default--[768x320 1:1 yuv420p]--out:default
auto_scaler_0:default--[768x160 1:1 yuva420p]--overlay| (overlay) |+-----------++---------------+
vflip:default--[768x160 1:1 yuv420p]--default| auto_scaler_0 |default--[768x160 1:1 yuva420p]--overlay:overlay|    (scale)    |+---------------+

帧处理

VFrame* in_frame = av_frame_alloc();
unsigned char* in_frame_buffer = (unsigned char*)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P, width, height, 1));
av_image_fill_arrays(in_frame->data, in_frame->linesize, in_frame_buffer, AV_PIX_FMT_YUV410P, width, height, 1);AVFrame* out_frame = av_frame_alloc();
unsigned char* out_frame_buffer = (unsigned char*)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P, width, height, 1));
av_image_fill_arrays(out_frame->data, out_frame->linesize, out_frame_buffer, AV_PIX_FMT_YUV420P, width, height, 1);in_frame->width = width;
in_frame->height = height;
in_frame->format = AV_PIX_FMT_YUV420P;
uint32_t frame_count = 0;while (1) {// 读取yuv帧数据if (fread(in_frame_buffer, 1, width*height*3/2, in_file) != width*height*3/2) {break;}// 设置帧数据指针in_frame->data[0] = in_frame_buffer;in_frame->data[1] = in_frame_buffer + width*height;in_frame->data[2] = in_frame_buffer + width*height*5/4;// 将帧送入滤镜系统if (av_buffersrc_add_frame(buffer_src_ctx, in_frame) < 0) {printf("av_buffersrc_add_frame failed\n");break;}// 从滤镜系统获取处理后的帧ret = av_buffersink_get_frame(buffer_sink_ctx, out_frame);if (ret < 0) {break;}// 写入处理后的帧if (out_frame->format == AV_PIX_FMT_YUV420P) {// 写入Y分量for (int i = 0; i < out_frame->height; i++) {fwrite(out_frame->data[0] + out_frame->linesize[0] * i, 1, out_frame->width, out_file);}// 写入U分量for (int i = 0; i < out_frame->height / 2; i++) {fwrite(out_frame->data[1] + out_frame->linesize[1] * i, 1, out_frame->width / 2, out_file);}// 写入V分量for (int i = 0; i < out_frame->height / 2; i++) {fwrite(out_frame->data[2] + out_frame->linesize[2] * i, 1, out_frame->width / 2, out_file);}}++frame_count;if (frame_count % 25 == 0)printf("process %d frame\n", frame_count);// 释放引用,不释放frame本身av_frame_unref(out_frame);
}

参考资料:https://github.com/0voice

http://www.dtcms.com/a/419017.html

相关文章:

  • 网站推广好难免费建网站代理
  • 东莞网站建设主要学什么北京有哪些著名网站
  • 英文版科技网站网站推广套餐
  • 网站建设与开发课程内容wordpress 启动wordpress mu
  • 10.4 线性规划
  • 【Svelte】比较 onMount 和 browser,以及客户端获取数据时,应该使用谁?
  • 欢迎学习《现代控制理论》——自动化专业的核心课程
  • 强化学习的数学原理-04章 策略评估与策略优化
  • 广州网站建设 .超凡科技新网网站登录不上
  • week 3
  • 建设网站 课程设计怎样用手机做网站
  • 图文讲解k8s中Service、Selector、EndpointSlice的运行原理
  • 菊风智能双录+质检+可视化回溯,组合拳助力金融合规数字化升级
  • k8s中的kubelet
  • 精读C++20设计模式——结构型设计模式:适配器模式
  • 如何用visual做网站十大国际贸易公司排名
  • 网站建设 仿站什么是电商?电商怎么做
  • 2025数据治理平台品牌TOP16榜单:技术突破与选型指南
  • 网站快速收录平台dede做的网站打不开
  • LeetCode 230. 二叉搜索树中第 K 小的元素
  • 优秀的平面设计网站国内做的比较好的旅游网站
  • 设计模式(C++)详解——中介者模式(2)
  • MySQL 8.0 “复杂类型”实战
  • 将0~3V电压分区间放大,减法器的使用
  • 2025年11月PgMP认证报名、考试安排!
  • 百度C++实习生面试题深度解析(下篇)
  • Memblock-2
  • 从芯片发布看未来AI发展趋势与前景
  • 【案例教程】生态碳汇涡度通量数据质量控制、缺失插补、可视化分析、光敏感性分析、温度敏感性分析、数据风浪区分析
  • 牛商网 做的p2p网站公司名称变更通知函