FFmpeg进阶教程之给视频添加文字水印
码农飞飞 人气:0前言
和图片水印一样,很多时候为了声明视频的原创性,我们会给视频添加文字水印进行版权保护。添加文字水印和添加图片水印的流程相似,但又略有不同,这里介绍一下如何通过FFmpeg给视频添加文字水印。添加文字水印的流程图如下图所示:
文字水印配置项
在讲文字水印之前先介绍一下文字水印支持的那些配置,方便大家的使用。
项目 | 介绍 |
---|---|
使用格式 | drawtext=fontfile=font_f:text=text1…(通过冒号分割配置项,通过=给配置项赋值) |
fontfile | 用于绘制文本的字体文件的正确路径,强制参数 |
text | 要绘制的文本字符串,必须是UTF-8编码的字符序列 |
x,y | 绘制的位置的起始坐标值 |
fontcolor | 字体颜色名称或0xRRGGBB[AA]格式的颜色,默认为黑色 |
fontsize | 要绘制的文本字体大小,默认值为16 |
tabsize | 用于呈现选项卡的空间大小,默认值为4 |
line_h,lh | 每个文本行的高度 |
main_h,h,H | 输入的高度 |
main_w,w,W | 输入的宽度 |
常用的配置项主要有这些,如果需要其他的配置可以参考官方文档介绍。
文字水印关键点
中文的支持
和QT一样,FFmpeg绘制文字水印也存在中文乱码的问题。在windows下解决中文乱码主要需要以下几点:
1.将源码文件修改为utf-8编码
2.将编译编码类型修改为utf-8编码对应的配置如下:
#pragma execution_character_set("utf-8")
同时我们还应该确保使用的字体支持中文。
字体路径问题
指定字体文件路径是强制参数,可以使用绝对路径和相对路径
//使用工程相对路径下的字体 fontfile=.//simsun.ttc //使用D盘绝对路径下的字体,要对斜杠进行转义 fontfile=D\\\\:simun.ttc
定义滤镜实现
文字水印对应的绘制流程图如下图所示:
文字水印滤镜的实现如下:
int InitFilter(AVCodecContext * codecContext) { char args[512]; int ret = 0; //缓存输入和缓存输出 const AVFilter *buffersrc = avfilter_get_by_name("buffer"); const AVFilter *buffersink = avfilter_get_by_name("buffersink"); //创建输入输出参数 AVFilterInOut *outputs = avfilter_inout_alloc(); AVFilterInOut *inputs = avfilter_inout_alloc(); //滤镜的描述 //使用simhei字体,绘制的字体大小为100,文本内容为"鬼灭之刃",绘制位置为(100,100) //绘制的字体颜色为白色 string filters_descr = "drawtext=fontfile=.//simsun.ttc:fontsize=100:text=鬼灭之刃:x=100:y=100:fontcolor=0xFFFFFF"; enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV420P }; //创建滤镜容器 filter_graph = avfilter_graph_alloc(); if (!outputs || !inputs || !filter_graph) { ret = AVERROR(ENOMEM); goto end; } //初始化数据帧的格式 sprintf_s(args, sizeof(args), "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d", codecContext->width, codecContext->height, codecContext->pix_fmt, codecContext->time_base.num, codecContext->time_base.den, codecContext->sample_aspect_ratio.num, codecContext->sample_aspect_ratio.den); //输入数据缓存 ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in", args, NULL, filter_graph); if (ret < 0) { goto end; } //输出数据缓存 ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out", NULL, NULL, filter_graph); if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Cannot create buffer sink\n"); goto end; } //设置元素样式 ret = av_opt_set_int_list(buffersink_ctx, "pix_fmts", pix_fmts, AV_PIX_FMT_YUV420P, AV_OPT_SEARCH_CHILDREN); if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Cannot set output pixel format\n"); goto end; } //设置滤镜的端点 outputs->name = av_strdup("in"); outputs->filter_ctx = buffersrc_ctx; outputs->pad_idx = 0; outputs->next = NULL; inputs->name = av_strdup("out"); inputs->filter_ctx = buffersink_ctx; inputs->pad_idx = 0; inputs->next = NULL; //初始化滤镜 if ((ret = avfilter_graph_parse_ptr(filter_graph, filters_descr.c_str(), &inputs, &outputs, NULL)) < 0) goto end; //滤镜生效 if ((ret = avfilter_graph_config(filter_graph, NULL)) < 0) goto end; end: //释放对应的输入输出 avfilter_inout_free(&inputs); avfilter_inout_free(&outputs); return ret; }
项目工程源码
给视频文件添加文字水印的工程源码如下,欢迎参考,如有问题欢迎反馈。
#pragma execution_character_set("utf-8") #include <string> #include <iostream> #include <thread> #include <memory> #include <iostream> #include <fstream> extern "C" { #include <libavcodec/avcodec.h> #include <libavdevice/avdevice.h> #include <libavfilter/avfilter.h> #include <libavformat/avformat.h> #include <libavformat/avio.h> #include <libavutil/avutil.h> #include <libswresample/swresample.h> #include <libswscale/swscale.h> #include <libavutil/frame.h> #include <libavutil/imgutils.h> #include <libavformat/avformat.h> #include <libavutil/time.h> #include <libavfilter/avfilter.h> #include <libavfilter/buffersink.h> #include <libavfilter/buffersrc.h> #include <libswscale/swscale.h> #include <libswresample/swresample.h> #include <libavdevice/avdevice.h> } using namespace std; //输入媒体文件的上下文 AVFormatContext * input_format_ctx = nullptr; //输出媒体文件的上下文 AVFormatContext* output_format_ctx; //输出视频编码器 AVCodecContext* ouput_video_encode_ctx = NULL; //音视频解码器 AVCodecContext *video_decode_ctx = NULL; AVCodecContext *audio_decode_ctx = NULL; //视频索引和音频索引 int video_stream_index = -1; int audio_stream_index = -1; //输出编码器 static AVCodec * output_video_codec; //滤镜容器和缓存 AVFilterGraph * filter_graph = nullptr; AVFilterContext *buffersink_ctx = nullptr;; AVFilterContext *buffersrc_ctx = nullptr;; AVPacket packet; //起始时间 static int64_t startTime; int OpenOutput(char *fileName) { //创建输出流,输出flv格式视频 int ret = 0; ret = avformat_alloc_output_context2(&output_format_ctx, NULL, "flv", fileName); if (ret < 0) { return -1; } //打开输出流 ret = avio_open(&output_format_ctx->pb, fileName, AVIO_FLAG_READ_WRITE); if (ret < 0) { return -2; } //创建输出流 for (int index = 0; index < input_format_ctx->nb_streams; index++) { if (index == video_stream_index) { AVStream * stream = avformat_new_stream(output_format_ctx, output_video_codec); avcodec_parameters_from_context(stream->codecpar, ouput_video_encode_ctx); stream->codecpar->codec_tag = 0; } else if (index == audio_stream_index) { AVStream * stream = avformat_new_stream(output_format_ctx, NULL); stream->codecpar = input_format_ctx->streams[audio_stream_index]->codecpar; stream->codecpar->codec_tag = 0; } } //写文件头 ret = avformat_write_header(output_format_ctx, nullptr); if (ret < 0) { return -3; } if (ret >= 0) cout << "open output stream successfully" << endl; return ret; } //初始化输出视频的编码器 int InitEncoderCodec(int iWidth, int iHeight) { output_video_codec = avcodec_find_encoder(AV_CODEC_ID_H264); if (NULL == output_video_codec) { return -1; } //指定编码器的参数 ouput_video_encode_ctx = avcodec_alloc_context3(output_video_codec); ouput_video_encode_ctx->time_base = input_format_ctx->streams[video_stream_index]->time_base; ouput_video_encode_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; ouput_video_encode_ctx->sample_fmt = AV_SAMPLE_FMT_S16; ouput_video_encode_ctx->width = iWidth; ouput_video_encode_ctx->height = iHeight; ouput_video_encode_ctx->bit_rate = input_format_ctx->streams[video_stream_index]->codecpar->bit_rate; ouput_video_encode_ctx->pix_fmt = (AVPixelFormat)*output_video_codec->pix_fmts; ouput_video_encode_ctx->profile = FF_PROFILE_H264_MAIN; ouput_video_encode_ctx->level = 41; ouput_video_encode_ctx->thread_count = 8; return 0; } int InitFilter(AVCodecContext * codecContext) { char args[512]; int ret = 0; //缓存输入和缓存输出 const AVFilter *buffersrc = avfilter_get_by_name("buffer"); const AVFilter *buffersink = avfilter_get_by_name("buffersink"); //创建输入输出参数 AVFilterInOut *outputs = avfilter_inout_alloc(); AVFilterInOut *inputs = avfilter_inout_alloc(); //滤镜的描述 //使用simhei字体,绘制的字体大小为100,文本内容为"鬼灭之刃",绘制位置为(100,100) //绘制的字体颜色为白色 string filters_descr = "drawtext=fontfile=.//simsun.ttc:fontsize=100:text=鬼灭之刃:x=100:y=100:fontcolor=0xFFFFFF"; enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV420P }; //创建滤镜容器 filter_graph = avfilter_graph_alloc(); if (!outputs || !inputs || !filter_graph) { ret = AVERROR(ENOMEM); goto end; } //初始化数据帧的格式 sprintf_s(args, sizeof(args), "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d", codecContext->width, codecContext->height, codecContext->pix_fmt, codecContext->time_base.num, codecContext->time_base.den, codecContext->sample_aspect_ratio.num, codecContext->sample_aspect_ratio.den); //输入数据缓存 ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in", args, NULL, filter_graph); if (ret < 0) { goto end; } //输出数据缓存 ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out", NULL, NULL, filter_graph); if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Cannot create buffer sink\n"); goto end; } //设置元素样式 ret = av_opt_set_int_list(buffersink_ctx, "pix_fmts", pix_fmts, AV_PIX_FMT_YUV420P, AV_OPT_SEARCH_CHILDREN); if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Cannot set output pixel format\n"); goto end; } //设置滤镜的端点 outputs->name = av_strdup("in"); outputs->filter_ctx = buffersrc_ctx; outputs->pad_idx = 0; outputs->next = NULL; inputs->name = av_strdup("out"); inputs->filter_ctx = buffersink_ctx; inputs->pad_idx = 0; inputs->next = NULL; //初始化滤镜 if ((ret = avfilter_graph_parse_ptr(filter_graph, filters_descr.c_str(), &inputs, &outputs, NULL)) < 0) goto end; //滤镜生效 if ((ret = avfilter_graph_config(filter_graph, NULL)) < 0) goto end; end: //释放对应的输入输出 avfilter_inout_free(&inputs); avfilter_inout_free(&outputs); return ret; } //将加水印之后的图像帧输出到文件中 static int output_frame(AVFrame *frame, AVRational time_base) { int code; AVPacket packet = { 0 }; av_init_packet(&packet); int ret = avcodec_send_frame(ouput_video_encode_ctx, frame); if (ret < 0) { printf("Error sending a frame for encoding\n"); return -1; } while (ret >= 0) { ret = avcodec_receive_packet(ouput_video_encode_ctx, &packet); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { return (ret == AVERROR(EAGAIN)) ? 0 : 1; } else if (ret < 0) { printf("Error during encoding\n"); exit(1); } AVRational avTimeBaseQ = { 1, AV_TIME_BASE }; int64_t ptsTime = av_rescale_q(frame->pts, input_format_ctx->streams[video_stream_index]->time_base, avTimeBaseQ); int64_t nowTime = av_gettime() - startTime; if ((ptsTime > nowTime)) { int64_t sleepTime = ptsTime - nowTime; av_usleep((sleepTime)); } else { printf("not sleeping\n"); } packet.pts = av_rescale_q_rnd(packet.pts, time_base, output_format_ctx->streams[video_stream_index]->time_base, (AVRounding)(AV_ROUND_INF | AV_ROUND_PASS_MINMAX)); packet.dts = av_rescale_q_rnd(packet.dts, time_base, output_format_ctx->streams[video_stream_index]->time_base, (AVRounding)(AV_ROUND_INF | AV_ROUND_PASS_MINMAX)); packet.stream_index = video_stream_index; code = av_interleaved_write_frame(output_format_ctx, &packet); av_packet_unref(&packet); if (code < 0) { av_log(NULL, AV_LOG_ERROR, "[ERROR] Writing Live Stream Interleaved Frame"); } if (ret < 0) { exit(1); } av_packet_unref(&packet); } } int main(int argc, char* argv[]) { if (argc != 3) { printf("usage:%1 input filepath %2 outputfilepath"); return -1; } //输入文件地址、输出文件地址 string fileInput = std::string(argv[1]); string fileOutput = std::string(argv[2]); //初始化各种配置 avformat_network_init(); av_log_set_level(AV_LOG_ERROR); //打开输入文件 int ret = avformat_open_input(&input_format_ctx, fileInput.c_str(), NULL, NULL); if (ret < 0) { return ret; } ret = avformat_find_stream_info(input_format_ctx, NULL); if (ret < 0) { return ret; } //查找音视频流的索引 for (int index = 0; index < input_format_ctx->nb_streams; ++index) { if (index == AVMEDIA_TYPE_AUDIO) { audio_stream_index = index; } else if (index == AVMEDIA_TYPE_VIDEO) { video_stream_index = index; } } //打开视频解码器 const AVCodec* codec = avcodec_find_decoder(input_format_ctx->streams[video_stream_index]->codecpar->codec_id); if (!codec) { return -1; } video_decode_ctx = avcodec_alloc_context3(codec); if (!video_decode_ctx) { fprintf(stderr, "Could not allocate video codec context\n"); return -2; } avcodec_parameters_to_context(video_decode_ctx, input_format_ctx->streams[video_stream_index]->codecpar); if (codec->capabilities & AV_CODEC_CAP_TRUNCATED) video_decode_ctx->flags |= AV_CODEC_FLAG_TRUNCATED; ret = avcodec_open2(video_decode_ctx, codec, NULL); if (ret < 0) { av_free(video_decode_ctx); return -3; } //初始化视频编码器 ret = InitEncoderCodec(video_decode_ctx->width, video_decode_ctx->height); if (ret < 0) { return 0; } //初始化滤镜 ret = InitFilter(ouput_video_encode_ctx); //打开编码器 ret = avcodec_open2(ouput_video_encode_ctx, output_video_codec, NULL); if (ret < 0) { return ret; } //初始化输出 if (OpenOutput((char *)fileOutput.c_str()) < 0) { cout << "Open file Output failed!" << endl; this_thread::sleep_for(chrono::seconds(10)); return 0; } AVFrame* pSrcFrame = av_frame_alloc(); AVFrame* filterFrame = av_frame_alloc(); av_init_packet(&packet); startTime = av_gettime(); while (true) { int ret = av_read_frame(input_format_ctx, &packet); if (ret < 0) { break; } //视频帧通过滤镜处理之后编码输出 if (packet.stream_index == video_stream_index) { int ret = avcodec_send_packet(video_decode_ctx, &packet); if (ret < 0) { break; } while (ret >= 0) { ret = avcodec_receive_frame(video_decode_ctx, pSrcFrame); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { break; } else if (ret < 0) { goto End; } pSrcFrame->pts = pSrcFrame->best_effort_timestamp; //添加到滤镜中 if (av_buffersrc_add_frame_flags(buffersrc_ctx, pSrcFrame, AV_BUFFERSRC_FLAG_KEEP_REF) < 0) { break; } while (1) { //获取滤镜输出 int ret = av_buffersink_get_frame(buffersink_ctx, filterFrame); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { break; } else if (ret < 0) { goto End; } //编码之后输出 output_frame(filterFrame, buffersink_ctx->inputs[0]->time_base); av_frame_unref(filterFrame); } av_frame_unref(pSrcFrame); } } else if (packet.stream_index == audio_stream_index) { packet.pts = av_rescale_q_rnd(packet.pts, input_format_ctx->streams[audio_stream_index]->time_base, output_format_ctx->streams[audio_stream_index]->time_base, (AVRounding)(AV_ROUND_INF | AV_ROUND_PASS_MINMAX)); packet.dts = av_rescale_q_rnd(packet.dts, input_format_ctx->streams[audio_stream_index]->time_base, output_format_ctx->streams[audio_stream_index]->time_base, (AVRounding)(AV_ROUND_INF | AV_ROUND_PASS_MINMAX)); packet.stream_index = audio_stream_index; av_interleaved_write_frame(output_format_ctx, &packet); } av_packet_unref(&packet); } av_write_trailer(output_format_ctx); End: //结束的时候清理资源 avfilter_graph_free(&filter_graph); if (input_format_ctx != NULL) { avformat_close_input(&input_format_ctx); } avcodec_free_context(&video_decode_ctx); avcodec_free_context(&ouput_video_encode_ctx); return 0; }
使用效果
没有添加水印之前的视频截图如下:
添加水印之后的效果图如下图所示:
总结
加载全部内容