目录
- 前言
- 文字水印配置项
- 文字水印关键点
- 定义滤镜实现
- 项目工程源码
- 使用效果
- 总结
前言
和图片水印一样,很多时候为了声明视频的原创性,我们会给视频添加文字水印进行版权保护。添加文字水印和添加图片水印的流程相似,但又略有不同,这里介绍一下如何通过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-")
同时我们还应该确保使用的字体支持中文。
字体路径问题
指定字体文件路径是强制参数,可以使用绝对路径和相对路径
//使用工程相对路径下的字体
fontfile=.//simsun.ttc
//使用D盘绝对路径下的字体,要对斜杠进行转义
fontfile=D\\\\:simun.ttc
定义滤镜实现
文字水印对应的绘制流程图如下图所示:
文字水印滤镜的实现如下:
int InitFilter(AVCodecContext * codecContext)
{
char args[];
int ret =;
//缓存输入和缓存输出
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)
//绘制的字体颜色为白色
string filters_descr = "drawtext=fontfile=.//simsun.ttc:fontsize=:text=鬼灭之刃:x=100:y=100:fontcolor=0xFFFFFF";
enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_YUVP, 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 <) {
goto end;
}
//输出数据缓存
ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out",
NULL, NULL, filter_graph);
if (ret <)
{
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_YUVP, AV_OPT_SEARCH_CHILDREN);
if (ret <)
{
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 =;
outputs->next = NULL;
inputs->name = av_strdup("out");
inputs->filter_ctx = buffersink_ctx;
inputs->pad_idx =;
inputs->next = NULL;
//初始化滤镜
if ((ret = avfilter_graph_parse_ptr(filter_graph, filters_descr.c_str(),
&inputs, &outputs, NULL)) <)
goto end;
//滤镜生效
if ((ret = avfilter_graph_config(filter_graph, NULL)) <)
goto end;
end:
//释放对应的输入输出
avfilter_inout_free(&inputs);
avfilter_inout_free(&outputs);
return ret;
}
项目工程源码
给视频文件添加文字水印的工程源码如下,欢迎参考,如有问题欢迎反馈。
#pragma execution_character_set("utf-")
#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 = -;
int audio_stream_index = -;
//输出编码器
static AVCodec * output_video_codec;
//滤镜容器和缓存
AVFilterGraph * filter_graph = nullptr;
AVFilterContext *buffersink_ctx = nullptr;;
AVFilterContext *buffersrc_ctx = nullptr;;
AVPacket packet;
//起始时间
static int_t startTime;
int OpenOutput(char *fileName)
{
//创建输出流,输出flv格式视频
int ret =;
ret = avformat_alloc_output_context(&output_format_ctx, NULL, "flv", fileName);
if (ret <)
{
return -;
}
//打开输出流
ret = avio_open(&output_format_ctx->pb, fileName, AVIO_FLAG_READ_WRITE);
if (ret <)
{
return -;
}
//创建输出流
for (int index =; 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 =;
}
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 =;
}
}
//写文件头
ret = avformat_write_header(output_format_ctx, nullptr);
if (ret <)
{
return -;
}
if (ret >=)
cout << "open output stream successfully" << endl;
return ret;
}
//初始化输出视频的编码器
int InitEncoderCodec(int iWidth, int iHeight)
{
output_video_codec = avcodec_find_encoder(AV_CODEC_ID_H);
if (NULL == output_video_codec)
{
return -;
}
//指定编码器的参数
ouput_video_encode_ctx = avcodec_alloc_context(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_S;
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_H_MAIN;
ouput_video_encode_ctx->level =;
ouput_video_encode_ctx->thread_count =;
return;
}
int InitFilter(AVCodecContext * codecContext)
{
char args[];
int ret =;
//缓存输入和缓存输出
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)
//绘制的字体颜色为白色
string filters_descr = "drawtext=fontfile=.//simsun.ttc:fontsize=:text=鬼灭之刃:x=100:y=100:fontcolor=0xFFFFFF";
enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_YUVP, 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 <) {
goto end;
}
//输出数据缓存
ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out",
NULL, NULL, filter_graph);
if (ret <)
{
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_YUVP, AV_OPT_SEARCH_CHILDREN);
if (ret <)
{
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 =;
outputs->next = NULL;
inputs->name = av_strdup("out");
inputs->filter_ctx = buffersink_ctx;
inputs->pad_idx =;
inputs->next = NULL;
//初始化滤镜
if ((ret = avfilter_graph_parse_ptr(filter_graph, filters_descr.c_str(),
&inputs, &outputs, NULL)) <)
goto end;
//滤镜生效
if ((ret = avfilter_graph_config(filter_graph, NULL)) <)
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 = { };
av_init_packet(&packet);
int ret = avcodec_send_frame(ouput_video_encode_ctx, frame);
if (ret <)
{
printf("Error sending a frame for encoding\n");
return -;
}
while (ret >=)
{
ret = avcodec_receive_packet(ouput_video_encode_ctx, &packet);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
{
return (ret == AVERROR(EAGAIN)) ? : 1;
}
else if (ret <) {
printf("Error during encoding\n");
exit();
}
AVRational avTimeBaseQ = {, AV_TIME_BASE };
int_t ptsTime = av_rescale_q(frame->pts, input_format_ctx->streams[video_stream_index]->time_base, avTimeBaseQ);
int_t nowTime = av_gettime() - startTime;
if ((ptsTime > nowTime))
{
int_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 <)
{
av_log(NULL, AV_LOG_ERROR, "[ERROR] Writing Live Stream Interleaved Frame");
}
if (ret <) {
exit();
}
av_packet_unref(&packet);
}
}
int main(int argc, char* argv[])
{
if (argc !=)
{
printf("usage:% input filepath %2 outputfilepath");
return -;
}
//输入文件地址、输出文件地址
string fileInput = std::string(argv[]);
string fileOutput = std::string(argv[]);
//初始化各种配置
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 <)
{
return ret;
}
ret = avformat_find_stream_info(input_format_ctx, NULL);
if (ret <)
{
return ret;
}
//查找音视频流的索引
for (int index =; 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 -;
}
video_decode_ctx = avcodec_alloc_context(codec);
if (!video_decode_ctx)
{
fprintf(stderr, "Could not allocate video codec context\n");
return -;
}
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_open(video_decode_ctx, codec, NULL);
if (ret <)
{
av_free(video_decode_ctx);
return -;
}
//初始化视频编码器
ret = InitEncoderCodec(video_decode_ctx->width, video_decode_ctx->height);
if (ret <)
{
return;
}
//初始化滤镜
ret = InitFilter(ouput_video_encode_ctx);
//打开编码器
ret = avcodec_open(ouput_video_encode_ctx, output_video_codec, NULL);
if (ret <)
{
return ret;
}
//初始化输出
if (OpenOutput((char *)fileOutput.c_str()) <)
{
cout << "Open file Output failed!" << endl;
this_thread::sleep_for(chrono::seconds());
return;
}
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 <)
{
break;
}
//视频帧通过滤镜处理之后编码输出
if (packet.stream_index == video_stream_index)
{
int ret = avcodec_send_packet(video_decode_ctx, &packet);
if (ret <)
{
break;
}
while (ret >=)
{
ret = avcodec_receive_frame(video_decode_ctx, pSrcFrame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
{
break;
}
else if (ret <)
{
goto End;
}
pSrcFrame->pts = pSrcFrame->best_effort_timestamp;
//添加到滤镜中
if (av_buffersrc_add_frame_flags(buffersrc_ctx, pSrcFrame, AV_BUFFERSRC_FLAG_KEEP_REF) <)
{
break;
}
while ()
{
//获取滤镜输出
int ret = av_buffersink_get_frame(buffersink_ctx, filterFrame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
{
break;
}
else if (ret <)
{
goto End;
}
//编码之后输出
output_frame(filterFrame, buffersink_ctx->inputs[]->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;
}
使用效果
没有添加水印之前的视频截图如下:
添加水印之后的效果图如下图所示: