FFmpeg过滤器实战:水印处理

过滤器实战:水印处理

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

程序整体结构

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

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

滤镜图设计分析

plain 复制代码
buffer_src → split → overlay (主输出)
               \
                → crop → vflip → overlay (副输入)

代码分析

文件输入初始化

c 复制代码
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 的宽和高。

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

c 复制代码
// 注册所有内置滤镜
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函数来初始化一个空的滤镜图结构,滤镜图结构会统一管理所有的滤镜,所以后续的所有滤镜都需要注册到滤镜图上

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

c 复制代码
// 获取名为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 轴)进行垂直翻转
    • 实现"上下颠倒"的视觉效果
    • 不改变视频的分辨率和像素合适
    • 处理效率高,适合实时应用

滤镜组合

c 复制代码
// 输入->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 到文件

c 复制代码
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);

输出结果如下:

plain 复制代码
+----------+
|    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)    |
                                             +---------------+

帧处理

c 复制代码
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

相关推荐
小狮子安度因6 小时前
FFmpeg过滤器实战:混音
ffmpeg
小钱c76 小时前
Python利用ffmpeg实现rtmp视频拉流和推流
python·ffmpeg·音视频
小狮子安度因14 小时前
FFmpeg-vflip滤镜使用
vue.js·ffmpeg·myeclipse
铍镁钙锶钡镭14 小时前
FFmpeg 解封装简单流程
开发语言·ffmpeg·php
Aevget1 天前
「Java EE开发指南」用MyEclipse开发的EJB开发工具(一)
java·ide·java-ee·myeclipse
Rysxt_3 天前
FFmpeg 教程:从入门到精通,探索多媒体处理的瑞士军刀
ffmpeg·音频处理
Yang.O3 天前
MyEclipse在高分辨率显示屏上图标显示太小的解决方案
java·ide·myeclipse
小小ken4 天前
whisper-large-v3部署详细步骤,包括cpu和gpu方式,跟着做一次成功
ffmpeg·whisper·语音识别
少年白char4 天前
Music Tag Web 怎么安装 ffmpeg?
ffmpeg