文章目录

  • 文字水印配置项
  • 文字水印关键点
  • 定义滤镜实现
  • 项目工程源码
  • 使用效果

和图片水印一样,很多时候为了声明视频的原创性,我们会给视频添加文字水印进行版权保护。添加文字水印和添加图片水印的流程相似,但又略有不同,这里介绍一下如何通过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;
}

使用效果

没有添加水印之前的视频截图如下:

添加水印之后的效果图如下图所示:

FFmpeg进阶:给视频添加文字水印相关推荐

  1. 【Qt+FFmpeg】给视频添加时间水印

    ffmpeg编解码中,给本地视频加上时间水印,并保存到本地,使用到的技术是ffmpeg中的avfilter库: 具体效果如下 yuv: mp4 本方法不适合摄像头解码,解码出来糊得不行,本地视频的话会 ...

  2. FFmpeg进阶:给视频添加模糊滤镜

    文章目录 全部画面模糊处理 部分画面模糊处理 在视频的录制过程中很多时候为了保护视频中某些敏感信息不泄露,我们会给对应的视频部位添加模糊处理.这时候就需要用到FFmpeg的模糊滤镜了,模糊滤镜有很多种 ...

  3. ffmpeg 添加视频加文字水印--drawtext 滤镜详解

    使用ffmpeg 命令行给视频添加文字水印,利用drawtext滤镜,如果是代码上api调用,也是一样的用法,创建好滤镜图, 在滤镜图描述字符串中,把这里命令行的参数拷贝过去替换就行,改动非常方便. ...

  4. php怎么给视频加水印,视频怎么加水印 添加文字水印和图片水印 给视频加水印的软件...

    昨天由于台风"妮妲"来了深圳,所以在家休息了一天,而且势汹汹的台风"妮妲"于2日03时35分在深圳市大鹏半岛登陆,登陆时风力达到14级,成为近年来正面登陆珠江三 ...

  5. android 水印视频教程,如何给视频添加一个摇摆的文字水印?安卓手机视频编辑助手app给视频加文字水印...

    注意此教程方案是『安卓手机端教程方案』 如果在手机端操作不方便或对眼睛不好 也可以用另外电脑端的教程方案操作:视频加旋转水印[找更多方案] 今天要介绍安卓手机上视频编辑助手是可以给视频添加摇摆的文字水 ...

  6. ffmpeg给视频添加时间水印

    ffmpeg给视频添加时间水印 通过 drawtext 滤镜模块给视频添加时间水印 给视频添加时间水印 用来做片源调试,非常方便的查找和定位处理的哪一帧视频片源: 1. 添加本地时间水印 ffmpeg ...

  7. ffmpeg 给 视频 加 文字 水印 标识 阴影 设置 颜色 字体 大小 懒人系列 28 centos7 linux

    centos7 linux ffmpeg shell 懒 0.给 视频 加 文字 水印 标识 阴影 设置 颜色 字体 大小 1.建立个文本,改个扩展名shell.sh,复制下面代码-保存. 打开终端( ...

  8. 用Java为视频添加图片水印(类似直播)

    文章目录 1.首先[下载nginx](http://nginx-win.ecsds.eu/download/) 2.[下载ffmpeg](https://pan.baidu.com/s/1LUWeVn ...

  9. PHP实现给视频加图片水印,怎么在视频画面上加图片?如何给视频添加自己的图片作为水印?视频添加图片水印的方法...

    今天就是周一啦,昨天周末大家有没有跟好友去玩呢~反正小编是跟同学聚会去了,聚会主题:胖.哈哈哈,不过小编可不跟他们一样只会长胖,小编可是瘦瘦瘦的呢,偷偷的告诉你们,小编减肥瘦了三十斤哦,嘻嘻.好啦,废 ...

  10. 如何批量给视频加文字水印?

    如何批量给视频加文字水印?我们在对外发布视频前,最好提前给视频添加自己的专属水印,这样能有效的防止视频被别人恶意盗用,还能帮助自己提高品牌宣传的效果.视频水印的方式主要有两种,分别是文字水印和图片水印 ...

最新文章

  1. DNS MX记录一定要放在A记录之前
  2. JAVA中大小写转化函数_Java-切换大小写,多个大小写调用同一函数
  3. windows下自制动画层引擎 - 放两个demo
  4. apache weblogic ssl linux,apache基于ssl配置weblogic(完结篇)
  5. 关联规则挖掘算法_基于Apriori关联规则的协同过滤算法
  6. Coursera自动驾驶课程第18讲:The Planning Problem
  7. 变分法理解2——基本方法
  8. python与java前景-java和python学习哪个未来发展前景更好?
  9. Social Engineering Data
  10. IO之Socket网络编程
  11. 三班倒有害健康,建议六班倒
  12. 基于多源传感器融合的导航定位综述方法分析
  13. Linux的开源操作系统
  14. MySQL字段根据逗号隔开查询
  15. excel单元格的合并与计算
  16. 可编辑div在光标位置插入指定内容
  17. 威斯康星麦迪逊计算机专业排名,威斯康星大学麦迪逊分校计算机工程排名
  18. vpa函数python_python 调用百度接口 做人脸识别
  19. layui表格显示后台的多表的级联查询(多对多,多对一)带mybatis级联查询源码,已解决
  20. P5937 [CEOI1999]Parity Game-扩展域并查集与离散化处理

热门文章

  1. Android View事件传递机制
  2. 大数据方面核心技术有哪些
  3. How To Clone Scrypt Based Altcoins for Fun and Profit
  4. iOS开发面试和底层学习视频整理合集
  5. biostarhandboo(三)|本体论和功能分析
  6. 什么是消防产品3C认证?
  7. 2022大淘宝技术工程师推荐书单
  8. UI设计公司面试时会提哪些问题?
  9. Ps的一些小知识,还有快捷键
  10. html展示微信昵称特殊字符,微信昵称加标签一直弹特殊符号