【音视频】使用movie、drawtext过滤器实现图片、文字水印(6-3)

本来准备从六月份开始研究使用ffmpeg的movie filter实现图片水印、drawtext filter实现文字水印的能力,但一直没时间,临近中秋终于有空,于是研究了下ffmpeg命令行(这里不做展示,关注代码实现),从中转化为C++代码实现。

直接上代码,这里新增了视频水印类,以下是主要实现:

cpp 复制代码
int VideoWaterMarker::init(const VIDEO_FILTER_CTX& outCtx, const VIDEO_FILTER_CTX* inCtx0, const VIDEO_FILTER_CTX* inCtx1)
{
	int err = ERROR_CODE_OK;

	if (m_inited) {
		return err;
	}

	if (inCtx0 == nullptr) {
		err = ERROR_CODE_PARAMS_ERROR;
		return err;
	}

	do {
		m_filterGraph = avfilter_graph_alloc();
		if (m_filterGraph == nullptr) {
			err = ERROR_CODE_FILTER_ALLOC_GRAPH_FAILED;
			break;
		}

		m_filterInCtxs = new VIDEO_FILTER_CTX;
		memcpy_s(&m_filterInCtxs[0], sizeof(VIDEO_FILTER_CTX), inCtx0, sizeof(VIDEO_FILTER_CTX));
		m_filterOutCtx = outCtx;

		m_filterInCtxs[0].filterInout = avfilter_inout_alloc();
		m_filterOutCtx.filterInout = avfilter_inout_alloc();

		char filterInArg[512] = { 0 };
		sprintf_s(filterInArg, sizeof(filterInArg), "video_size=%dx%d:pix_fmt=%d:frame_rate=%d:time_base=%d/%d:pixel_aspect=%d/%d",
			m_filterInCtxs[0].width, m_filterInCtxs[0].height, m_filterInCtxs[0].pixelFmt, m_filterInCtxs[0].framerate,
			m_filterInCtxs[0].timebase.num, m_filterInCtxs[0].timebase.den, m_filterInCtxs[0].pixelAspect.num, m_filterInCtxs[0].pixelAspect.den);

		int ret = avfilter_graph_create_filter(&m_filterInCtxs[0].filterCtx, avfilter_get_by_name("buffer"), "in", filterInArg, nullptr, m_filterGraph);
		if (ret < 0) {
			err = ERROR_CODE_FILTER_CREATE_FILTER_FAILED;
			break;
		}

		ret = avfilter_graph_create_filter(&m_filterOutCtx.filterCtx, avfilter_get_by_name("buffersink"), "out", nullptr, nullptr, m_filterGraph);
		if (ret < 0) {
			err = ERROR_CODE_FILTER_CREATE_FILTER_FAILED;
			break;
		}

		av_opt_set_bin(m_filterOutCtx.filterCtx, "pix_fmts", (uint8_t*)&m_filterOutCtx.pixelFmt, sizeof(m_filterOutCtx.pixelFmt), AV_OPT_SEARCH_CHILDREN);

		m_filterInCtxs[0].filterInout->name = av_strdup("in");
		m_filterInCtxs[0].filterInout->filter_ctx = m_filterInCtxs[0].filterCtx;
		m_filterInCtxs[0].filterInout->pad_idx = 0;
		m_filterInCtxs[0].filterInout->next = nullptr;

		m_filterOutCtx.filterInout->name = av_strdup("out");
		m_filterOutCtx.filterInout->filter_ctx = m_filterOutCtx.filterCtx;
		m_filterOutCtx.filterInout->pad_idx = 0;
		m_filterOutCtx.filterInout->next = nullptr;

		std::string filterDesc;
		/* 1、图片水印 */
		filterDesc = "movie=11.png[wm];[in][wm]overlay=W-w:H-h[out]";
		/* 2、文字水印 */
		std::wstring tempText = L"Hello, 世界!";
		std::string strText = HELPER::StringConverter::convertUnicodeToUtf8(tempText);
		filterDesc = "drawtext=fontfile=\'C\\:/Windows/Fonts/simsun.ttc\':fontsize=72:fontcolor=white:box=1:boxcolor=blue@0.5:x=w-text_w:y=h-text_h:text=\'" + strText + "\'";
		/* 3、时间水印 */
		filterDesc = "drawtext=fontfile=\'C\\:/Windows/Fonts/Arial.ttf\':text=\'%{localtime\\:%Y-%m-%d %H.%M.%S}\':fontsize=36:fontcolor=white:box=1:boxcolor=blue:x=100:y=50";
		ret = avfilter_graph_parse_ptr(m_filterGraph, filterDesc.c_str(), &m_filterOutCtx.filterInout, &m_filterInCtxs[0].filterInout, nullptr);
		if (ret < 0) {
			err = ERROR_CODE_FILTER_PARSE_PTR_FAILED;
			break;
		}

		ret = avfilter_graph_config(m_filterGraph, nullptr);
		if (ret < 0) {
			err = ERROR_CODE_FILTER_CONFIG_FAILED;
			break;
		}

		m_inited = true;
	} while (0);

	if (err != ERROR_CODE_OK) {
		LOGGER::Logger::log(LOGGER::LOG_TYPE_ERROR, "[%s] init mixer, error: %s", __FUNCTION__, HCMDR_GET_ERROR_DESC(err));
		cleanup();
	}

	return err;
}

1、对于图片的水印,使用movie filter将图片作为一个输入源wm跟另外一个视频输入源in(摄像头或桌面采集)输出新的out视频源。"movie=11.pngwm;inwmoverlay=W-w:H-hout",其中W、H是in的大小,w、h是wm的大小,将wmoverlay在in上面,且显示在右下角。

2、对于文字水印,使用drawtext filter,首先要指定绝对路径的文字文件,例如fontfile='C\:/Windows/Fonts/simsun.ttc',在windows下一定要这样写,要不然找不到指定的文字文件而使用默认的文字文件,其次文字输入如果包含中文则需要将其转化为UTF8格式作为text的值传入。一般文字大小从8磅到72磅,这里指定文字大小为72磅,颜色为白色,box=1表示开启文字背景色,指定背景色为蓝色且透明度为50%(文字也可以像这样单独指定透明度;如果要同时指定文字和背景色透明度,可以使用命令alpha=0.5),这里x、y将文字位置至于右下角。

3、对于时间水印,同样需要指定绝对路径fontfile,其次text需要指定例如text='%{localtime\:%Y-%m-%d %H.%M.%S}',Y表示年,m表示月份,d表示天,H表示小时,M表示分钟,S表示秒,Arial字体的时间样式:

PS:因为视频水印也是线程驱动的,剩下的一些实现如开始线程start()、停止线程stop()、喂帧addFrame()、清空cleanup()和线程函数mixProcess()跟《【音视频】视频混流-avfilter(2-2)》类似,可以跳转参考。

相关推荐
Vex2une1 小时前
Windows 性能监视器发展史:从 PerfMon 计数器到 XPerf 深度诊断
windows·wpa·etw·性能监视器·perfmon·性能计数器·xperf
在角落发呆1 小时前
工具配置备份步骤:从手动备份到自动化方案
运维·windows·自动化
fpcc2 小时前
c++编程实践—堆和栈越界调试
c++
Vd7H20A73 小时前
Windows 下用 llama.cpp 运行 Qwen3.8-27B 并开启 512K 上下文 完全指南
windows·llama
weiabc3 小时前
MSYS2 UCRT64 + g++ C++ 中文输出乱码 完整全过程
c++
灯澜忆梦5 小时前
【面向对象编程C++】| 基础语法
java·c++·算法
揽秀亭长5 小时前
视频转脚本有哪些方法?5种方案技术拆解
人工智能·音视频
aqiu1111116 小时前
【C++算法打怪专栏】LeetCode 165. 比较版本号(双指针与字符串流法)
c++·算法·leetcode
Android系统攻城狮6 小时前
Linux Gstreamer深度解析之gst_audio_sink_get_type调用流程与实战(六十五)
linux·运维·服务器·音视频·gstreamer音视频·音视频进阶·gstreamer音视频进阶
chenbingjie_c7 小时前
C++ STL 适配器:stack & queue,从 vector/list 到 deque 深度解析
数据结构·c++