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

本文共 6659 字,大约阅读时间需要 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/

    你可能感兴趣的文章
    mysqldump数据库备份无法进行操作只能查询 --single-transaction
    查看>>
    mysqldump的一些用法
    查看>>
    mysqli
    查看>>
    MySQLIntegrityConstraintViolationException异常处理
    查看>>
    mysqlreport分析工具详解
    查看>>
    MySQLSyntaxErrorException: Unknown error 1146和SQLSyntaxErrorException: Unknown error 1146
    查看>>
    Mysql_Postgresql中_geometry数据操作_st_astext_GeomFromEWKT函数_在java中转换geometry的16进制数据---PostgreSQL工作笔记007
    查看>>
    mysql_real_connect 参数注意
    查看>>
    mysql_secure_installation初始化数据库报Access denied
    查看>>
    MySQL_西安11月销售昨日未上架的产品_20161212
    查看>>
    Mysql——深入浅出InnoDB底层原理
    查看>>
    MySQL“被动”性能优化汇总
    查看>>
    MySQL、HBase 和 Elasticsearch:特点与区别详解
    查看>>
    MySQL、Redis高频面试题汇总
    查看>>
    MYSQL、SQL Server、Oracle数据库排序空值null问题及其解决办法
    查看>>
    mysql一个字段为空时使用另一个字段排序
    查看>>
    MySQL一个表A中多个字段关联了表B的ID,如何关联查询?
    查看>>
    MYSQL一直显示正在启动
    查看>>
    MySQL一站到底!华为首发MySQL进阶宝典,基础+优化+源码+架构+实战五飞
    查看>>
    MySQL万字总结!超详细!
    查看>>