本来准备从六月份开始研究使用ffmpeg的movie filter实现图片水印、drawtext filter实现文字水印的能力,但一直没时间,临近中秋终于有空,于是研究了下ffmpeg命令行(这里不做展示,关注代码实现),从中转化为C++代码实现。
直接上代码,这里新增了视频水印类,以下是主要实现:
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.png[wm];[in][wm]overlay=W-w:H-h[out]”,其中W、H是[in]的大小,w、h是[wm]的大小,将[wm]overlay在[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)》类似,可以跳转参考。