博客
关于我
使用ffmpeg 的 filter 给图片添加水印
阅读量:188 次
发布时间:2019-02-28

本文共 6838 字,大约阅读时间需要 22 分钟。

使用ffmpeg添加图片水印的实现方法

在视频处理领域,ffmpeg是一个强大的工具,能够实现丰富的功能之一就是图片水印的添加。以下将详细介绍如何利用ffmpeg的filter功能,为图片添加水印。

首先,我们需要包含必要的库文件,确保编译环境的支持。代码中我们引入了标准输入输出流文件头,ffmpeg的核心编解码器头文件,以及filter处理相关的头文件。代码结构如下:

#include <stdio.h> #include <libavfilter/avfilter.h> #include <libavfilter/buffersrc.h> #include <libavfilter/buffersink.h> #include <libavformat/avformat.h> #include <libavcodec/avcodec.h>

接下来,我们定义了几个上下文变量,用于处理主图、Logo图以及最终的合成图。这些变量的作用是维护ffmpeg处理过程中的各个阶段。

AVFilterContext* mainsrc_ctx = NULL; AVFilterContext* logosrc_ctx = NULL; AVFilterContext* resultsink_ctx = NULL; AVFilterGraph* filter_graph = NULL;

初始化过滤器处理函数init_filters的实现步骤如下:

  • 分配并初始化过滤器图表filter_graph。
  • 使用snprintf函数构建过滤器参数字符串,指定各个输入源和处理流程。
  • 调用avfilter_graph_parse2函数解析并配置过滤器图表。
  • 调用avfilter_graph_config函数获取并配置过滤器上下文。
  • 代码实现如下:

    static int init_filters(const AVFrame* main_frame, const AVFrame* logo_frame, int x, int y) { int ret = 0; AVFilterInOut* inputs = NULL; AVFilterInOut* outputs = NULL; char filter_args[1024] = { 0 };

    // 初始化用于整个过滤处理的封装
    filter_graph = avfilter_graph_alloc();
    if (!filter_graph) {
    printf("%d : avfilter_graph_alloc() failed!\n", __LINE__);
    return -1;
    }
    // 构建过滤器命令字符串
    snprintf(filter_args, sizeof(filter_args), "buffer=video_size=%dx%d:pix_fmt=%d:time_base=1/25:pixel_aspect=%d/%d[main];"
    "buffer=video_size=%dx%d:pix_fmt=%d:time_base=1/25:pixel_aspect=%d/%d[logo];"
    "[main][logo]overlay=%d:%d[result];"
    "[result]buffersink",
    main_frame->width, main_frame->height, main_frame->format,
    main_frame->sample_aspect_ratio.num, main_frame->sample_aspect_ratio.den,
    logo_frame->width, logo_frame->height, logo_frame->format,
    logo_frame->sample_aspect_ratio.num, logo_frame->sample_aspect_ratio.den,
    x, y);
    // 解析并配置过滤器图表
    ret = avfilter_graph_parse2(filter_graph, filter_args, &inputs, &outputs);
    if (ret < 0) {
    printf("%d : avfilter_graph_parse2() failed!\n", __LINE__);
    return ret;
    }
    // 配置过滤器图表
    ret = avfilter_graph_config(filter_graph, NULL);
    if (ret < 0) {
    printf("%d : avfilter_graph_config() failed!\n", __LINE__);
    return ret;
    }
    // 获取各个上下文
    mainsrc_ctx = avfilter_graph_get_filter(filter_graph, "Parsed_buffer_0");
    logosrc_ctx = avfilter_graph_get_filter(filter_graph, "Parsed_buffer_1");
    resultsink_ctx = avfilter_graph_get_filter(filter_graph, "Parsed_buffersink_3");
    avfilter_inout_free(&inputs);
    avfilter_inout_free(&outputs);
    return 0;

    }

    添加水印的实现主要包含两个步骤:添加主图和Logo图,并对最终结果进行合成。具体实现如下:

    static int main_mix_logo(AVFrame* main_frame, AVFrame* logo_frame, AVFrame *result_frame) { int ret = 0;

    // 添加主图
    ret = av_buffersrc_add_frame(mainsrc_ctx, main_frame);
    if (ret < 0) return ret;
    // 添加Logo图
    ret = av_buffersrc_add_frame(logosrc_ctx, logo_frame);
    if (ret < 0) return ret;
    // 获取合成图
    ret = av_buffersink_get_frame(resultsink_ctx, result_frame);
    return ret;

    }

    实现图片读取的函数get_jpeg的具体实现如下:

    static AVFrame* get_jpeg(const char* filename) { int ret = 0; AVFormatContext* format_ctx = NULL;

    // 打开媒体文件
    if ((ret = avformat_open_input(&format_ctx, filename, NULL, NULL)) != 0) {
    printf("%d : avformat_open_input() failed!\n", __LINE__);
    return NULL;
    }
    // 获取媒体文件信息
    avformat_find_stream_info(format_ctx, NULL);
    AVCodec* codec = NULL;
    AVCodecContext* codec_ctx = NULL;
    int video_stream_index = -1;
    // 获取流
    video_stream_index = av_find_best_stream(format_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &codec, 0);
    if (video_stream_index < 0) goto cleanup;
    codec_ctx = avcodec_alloc_context3(codec);
    if (!codec_ctx) goto cleanup;
    // 初始化解码器上下文
    ret = avcodec_open2(codec_ctx, codec, NULL);
    if (ret < 0) goto cleanup;
    AVPacket pkt;
    av_init_packet(&pkt);
    pkt.data = NULL;
    pkt.size = 0;
    // 读取一帧
    ret = av_read_frame(format_ctx, &pkt);
    if (ret < 0) goto cleanup;
    // 解码帧
    ret = avcodec_send_packet(codec_ctx, &pkt);
    if (ret < 0) goto cleanup;
    AVFrame* frame = av_frame_alloc();
    ret = avcodec_receive_frame(codec_ctx, frame);
    if (ret < 0) {
    av_frame_free(&frame);
    goto cleanup;
    }

    cleanup: if (format_ctx) avformat_close_input(&format_ctx); if (codec_ctx) avcodec_free_context(&codec_ctx); return frame; }

    将处理后的图像保存为jpeg格式的函数savejpeg的实现如下:

    static int savejpeg(const char* filename, const AVFrame* frame) { // 查找相应编码器 AVCodec* jpeg_codec = avcodec_find_encoder(AV_CODEC_ID_MJPEG); if (!jpeg_codec) return -1;

    AVCodecContext* jpeg_codec_ctx = avcodec_alloc_context3(jpeg_codec);
    if (!jpeg_codec_ctx) return -2;
    // 设置jpeg相关参数
    jpeg_codec_ctx->pix_fmt = AV_PIX_FMT_YUVJ420P;
    jpeg_codec_ctx->width = frame->width;
    jpeg_codec_ctx->height = frame->height;
    jpeg_codec_ctx->time_base.num = 1;
    jpeg_codec_ctx->time_base.den = 25;
    jpeg_codec_ctx->framerate.num = 25;
    jpeg_codec_ctx->framerate.den = 1;
    AVDictionary* encoder_opts = NULL;
    // 添加编码器选项
    av_dict_set(&encoder_opts, "flags", "+qscale", 0);
    av_dict_set(&encoder_opts, "qmax", "2", 0);
    av_dict_set(&encoder_opts, "qmin", "2", 0);
    // 初始化编码器上下文
    int ret = avcodec_open2(jpeg_codec_ctx, jpeg_codec, &encoder_opts);
    if (ret < 0) {
    avcodec_free_context(&jpeg_codec_ctx);
    printf("%d : avcodec_open2() failed!\n", __LINE__);
    return -3;
    }
    av_dict_free(&encoder_opts);
    AVPacket pkt;
    av_init_packet(&pkt);
    pkt.data = NULL;
    pkt.size = 0;
    // 编码帧
    ret = avcodec_send_frame(jpeg_codec_ctx, frame);
    if (ret < 0) {
    avcodec_free_context(&jpeg_codec_ctx);
    printf("%d : avcodec_send_frame() failed!\n", __LINE__);
    return -4;
    }
    // 接收编码数据
    while (ret >= 0) {
    ret = avcodec_receive_packet(jpeg_codec_ctx, &pkt);
    if (ret == AVERROR(EAGAIN)) continue;
    if (ret == AVERROR_EOF) {
    ret = 0;
    break;
    }
    // 写入文件
    FILE* outfile = fopen(filename, "wb");
    if (!outfile) {
    printf("%d : fopen() failed!\n", __LINE__);
    ret = -1;
    break;
    }
    if (fwrite((char*)pkt.data, 1, pkt.size, outfile) == pkt.size) {
    ret = 0;
    } else {
    printf("%d : fwrite failed!\n", __LINE__);
    ret = -1;
    break;
    }
    fclose(outfile);
    ret = 0;
    break;
    }
    avcodec_free_context(&jpeg_codec_ctx);
    return ret;

    }

    最后,主函数main的实现步骤如下:

  • 打印欢迎信息。
  • 调用get_jpeg函数读取主图和Logo图。
  • 调用init_filters函数初始化过滤器处理。
  • 调用main_mix_logo函数实现图像合成。
  • 调用savejpeg函数将结果保存为jpeg格式文件。
  • 代码实现如下:

    int main() { printf("Hello watermarkmix!\n"); AVFrame *main_frame = get_jpeg("main.jpg"); AVFrame logo_frame = get_jpeg("logo.jpg"); AVFrame result_frame = av_frame_alloc(); int ret = 0;

    if (ret = init_filters(main_frame, logo_frame, 100, 200) < 0) {
    printf("%d : init_filters failed\n", __LINE__);
    goto end;
    }
    if (main_mix_logo(main_frame, logo_frame, result_frame) < 0) {
    printf("%d : main_picture_mix_logo failed\n", __LINE__);
    goto end;
    }
    savejpeg("output.jpg", result_frame);

    end: if (main_frame) av_frame_free(&main_frame); if (logo_frame) av_frame_free(&logo_frame); if (result_frame) av_frame_free(&result_frame); if (filter_graph) avfilter_graph_free(&filter_graph); printf("finish\n"); printf("End watermarkmix!\n"); return 0; }

    转载地址:http://rnqn.baihongyu.com/

    你可能感兴趣的文章
    Nginx的使用总结(三)
    查看>>
    Nginx的使用总结(二)
    查看>>
    Nginx的可视化神器nginx-gui的下载配置和使用
    查看>>
    Nginx的是什么?干什么用的?
    查看>>
    Nginx访问控制_登陆权限的控制(http_auth_basic_module)
    查看>>
    nginx负载均衡和反相代理的配置
    查看>>
    nginx负载均衡器处理session共享的几种方法(转)
    查看>>
    nginx负载均衡的5种策略(转载)
    查看>>
    nginx负载均衡的五种算法
    查看>>
    nginx转发端口时与导致websocket不生效
    查看>>
    Nginx运维与实战(二)-Https配置
    查看>>
    Nginx配置Https证书
    查看>>
    Nginx配置ssl实现https
    查看>>
    Nginx配置TCP代理指南
    查看>>
    Nginx配置——不记录指定文件类型日志
    查看>>
    nginx配置一、二级域名、多域名对应(api接口、前端网站、后台管理网站)
    查看>>
    Nginx配置代理解决本地html进行ajax请求接口跨域问题
    查看>>
    nginx配置全解
    查看>>
    Nginx配置参数中文说明
    查看>>
    nginx配置域名和ip同时访问、开放多端口
    查看>>