使用ffmpeg 的 filter 给图片添加水印。
main.c
#include <stdio.h>
#include <libavfilter/avfilter.h>
#include <libavfilter/buffersrc.h>
#include <libavfilter/buffersink.h>
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
AVFilterContext* mainsrc_ctx = NULL;
AVFilterContext* logosrc_ctx = NULL;
AVFilterContext* resultsink_ctx = NULL;
AVFilterGraph* filter_graph = NULL;
//初始化过滤器处理
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];" // Parsed_buffer_0
"buffer=video_size=%dx%d:pix_fmt=%d:time_base=1/25:pixel_aspect=%d/%d[logo];" // Parsed_bufer_1
"[main][logo]overlay=%d:%d[result];" // Parsed_overlay_2
"[result]buffersink", // Parsed_buffer_sink_3
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);
//添加过滤器处理到AVFilterGraph
ret = avfilter_graph_parse2(filter_graph, filter_args, &inputs, &outputs);
if(ret < 0)
{
printf("%d : avfilter_graph_parse2() failed!\n", __LINE__);
return ret;
}
//配置AVFilterGraph的过滤器处理
ret = avfilter_graph_config(filter_graph, NULL);
if(ret < 0)
{
printf("%d : avfilter_graph_config() failed!\n", __LINE__);
return ret;
}
//获取AVFilterGraph内的过滤器
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;
}
//添加水印
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;
}
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");
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);
//关联解码器上下文
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);
cleanup:
if(format_ctx)
avformat_close_input(&format_ctx);
if(codec_ctx)
avcodec_free_context(&codec_ctx);
return frame;
}
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;
//encoder_opts 为空就会分配内存的
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");
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");
return -4;
}
ret = 0;
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");
ret = -1;
break;
}
//写入文件
if(fwrite((char*)pkt.data, 1, pkt.size, outfile) == pkt.size)
{
ret = 0;
}
else
{
printf("%d : fwrite failed!\n");
ret = -1;
}
fclose(outfile);
ret = 0;
break;
}
avcodec_free_context(&jpeg_codec_ctx);
return ret;
}
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;
}