前面两篇文章详细讲解了怎么叠加字幕和Logo,但是这两篇的例子主要是针对Windows平台的,用到大量Windows API,一些非Windows程序员想要移植到其他平台(如Linux、Android)可能还要费一番功夫。要在其他平台进行叠加字幕和Logo有什么比较通用的方案呢?其实FFmpeg已经集成了一个加水印滤镜功能,用跨平台的FFmpeg能够帮助我们轻松实现该功能。

废话少说,先看看加水印滤镜怎么用。

首先要调用avfilter_register_all() 注册所有AVFilter。

接着,定义几个跟加水印滤镜相关的变量:

AVFilterContext * buffersink_ctx = NULL;
AVFilterContext * buffersrc_ctx = NULL;
AVFilterGraph * filter_graph = NULL;
BOOL      g_bInitFilterOK = FALSE;CCritSec     g_FilterLock;

FFmpeg初始化加水印滤镜的例子代码如下:


static int init_filters(const char *filters_descr, AVCodecContext *pCodecCtx)
{CAutoLock lock(&g_FilterLock);if(filter_graph != NULL)return 1;if(pCodecCtx->pix_fmt != PIX_FMT_YUV420P) //检查输入图像像素格式return 2;char args[512];int ret;AVFilter *buffersrc  = avfilter_get_by_name("buffer");AVFilter *buffersink = avfilter_get_by_name("ffbuffersink");AVFilterInOut *outputs = avfilter_inout_alloc();AVFilterInOut *inputs  = avfilter_inout_alloc();enum PixelFormat pix_fmts[] = { PIX_FMT_YUV420P, PIX_FMT_NONE };AVBufferSinkParams *buffersink_params;filter_graph = avfilter_graph_alloc();/* buffer video source: the decoded frames from the decoder will be inserted here. */_snprintf(args, sizeof(args),"video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,pCodecCtx->time_base.num, pCodecCtx->time_base.den,pCodecCtx->sample_aspect_ratio.num, pCodecCtx->sample_aspect_ratio.den);ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in",args, NULL, filter_graph);if (ret < 0) {TRACE("Cannot create buffer source\n");return ret;}/* buffer video sink: to terminate the filter chain. */buffersink_params = av_buffersink_params_alloc();buffersink_params->pixel_fmts = pix_fmts;ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out",NULL, buffersink_params, filter_graph);av_free(buffersink_params);if (ret < 0) {TRACE("Cannot create buffer sink\n");return ret;}/* Endpoints for the filter graph. */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,&inputs, &outputs, NULL)) < 0)return ret;if ((ret = avfilter_graph_config(filter_graph, NULL)) < 0)return ret;g_bInitFilterOK = TRUE;return 0;
}

上面代码主要用到的API如下:

avfilter_graph_alloc():为FilterGraph分配内存。
avfilter_graph_create_filter():创建并向FilterGraph中添加一个Filter。
avfilter_graph_parse_ptr():将一串通过字符串描述的Graph添加到FilterGraph中。
avfilter_graph_config():检查FilterGraph的配置。
av_buffersrc_add_frame():向FilterGraph中加入一个AVFrame。
av_buffersink_get_frame():从FilterGraph中取出一个AVFrame。

init_filters函数需要传入一个字符串,这个字符串是描述要加水印的属性的,包括:水印图片的路径,水印显示坐标等。水印支持PNG图片文件作为输入,即支持背景透明。这个字符串的格式如下:

movie=logo.png,scale=60:30[watermask];[in] [watermask] overlay=30:10 [out]

参数说明:

logo.png: 添加的水印图片;

scale:水印大小,水印长度*水印的高度;

overlay:水印的位置,距离原视频左侧的距离:距离原视频上侧的距离;mainW主视频宽度, mainH主视频高度,overlayW水印宽度,overlayH水印高度

  左上角overlay参数为 overlay=0:0

  右上角为 overlay= main_w-overlay_w:0

  右下角为 overlay= main_w-overlay_w:main_h-overlay_h

  左下角为 overlay=0: main_h-overlay_h

FFmpeg水印滤镜支持的参数见下面表格:

参数

参数

 

overlay

main_w

视频单帧图像宽度

main_h

视频单帧图像高度

overlay_w

水印图片的宽度

overlay_h

水印图片的高度

-vf

设置video过滤器,视频旋转,缩放,水印等处理

af

设置audio过滤器

关于更多的参数可以参考ffmpeg官网filter的描述:https://ffmpeg.org/ffmpeg-filters.html

下面是在程序中调用init_filters的代码:

 CString strFilterLogoDesc;if(m_bIsOSDText)strFilterLogoDesc = "movie=logo_text.png[watermark];[in][watermark]overlay=10:10[out]"; //movie=后面的参数是Logo图标的文件名,overlay=后面的是坐标,OSD坐标位置暂时固定为(10, 10)elsestrFilterLogoDesc = "movie=logo.png[watermark];[in][watermark]overlay=10:10[out]";int nRet = init_filters(strFilterLogoDesc, input_st->codec);

这里要说明一下,Logo文件的路径默认是跟执行文件同一个目录的,如果要用绝对路径指定Logo的路径,则可能会失败。我在Windows平台上测试过,Logo文件用绝对路径的话,init_filters将会返回-2(不知在Linux上是否也有这个问题)。后来找到一个解决办法,Logo.png的路径不用绝对路径,但要设置当前目录的路径:调用系统API设置SetCurrentDirectory(path),将目标目录路径指定为Logo文件所在的目录。

从上面调用代码可以看到,对于叠加字幕和叠加图标都需要传入一个水印PNG文件,对于文字怎么生成一个图片文件呢?在Windows平台,我们可以用Windows GDI 函数在内存中生成一个位图,然后打印上字符,并保存为PNG图片(额!还是得用Windows API,不是说跨平台吗?这个只是作者本人的技术倾向,对Windows API比较熟悉,其实不直接用系统API也可以,一些跨平台库如SDL、QT也有类似在的内存中打印文字和输出图形的功能)。下面是从一段文字转为一个带Alpha通道的位图的代码:

//创建一个显示OSD文字的位图,位图带透明背景
static BOOL CreateOsdTextBitmap(const char * szText, LOGFONT * lplf, CImage & image)
{BOOL pass = FALSE;CSize csTextSize;HDC memDC = CreateCompatibleDC(NULL);HFONT hFont = ::CreateFontIndirect(lplf);ASSERT(hFont != NULL);::SelectObject(memDC, hFont);GetTextExtentPoint32(memDC, szText, lstrlen(szText), &csTextSize);int cx = csTextSize.cx + 12;int cy = csTextSize.cy + 8;HBITMAP membmp = CreateCompatibleBitmap(memDC, cx, cy);HBITMAP oldbmp = (HBITMAP) SelectObject(memDC, membmp);SetBkMode(memDC, TRANSPARENT);::SetBkColor(memDC, RGB(0, 0, 0)); //背景色SetTextColor(memDC, RGB(0xFF, 0, 0));CRect OsdRect(6, 4, cx-6, cy-4);DrawText(memDC, szText, lstrlen(szText), &OsdRect, DT_CENTER);#if 1if(!image.Create(cx, cy, 32, 0x01)) //创建带Alpha通道的32位位图
#elseif(!image.Create(cx, cy, 32))
#endif{pass = FALSE;goto end_osd_bitmap_func;}pass = TRUE;HDC hImgDC = image.GetDC();BitBlt(hImgDC, 0, 0, cx, cy, memDC, 0, 0, SRCCOPY);image.ReleaseDC();#if 1if(image.GetBPP() == 32){//将OSD背景的部分设置为透明int image_cx = image.GetWidth();int image_cy = image.GetHeight();long lPitch = image.GetPitch();BYTE * image_data;if(lPitch < 0){image_data = (BYTE *)image.GetBits()+(image.GetPitch()*(image.GetHeight()-1));}else{image_data = (BYTE *)image.GetBits();}BYTE * pImage = NULL;for(int y = 0; y < image_cy; y++){pImage = image_data + abs(lPitch) * y;for(int x = 0; x < image_cx; x++){if(pImage[0] == 0 && pImage[1] == 0 && pImage[2] == 0) //RGB等于背景色{pImage[3] = 0; //透明}else{pImage[3] = 0xff;}pImage += 4;}}}
#endifend_osd_bitmap_func:::DeleteObject(hFont);SelectObject(memDC, oldbmp);DeleteObject(membmp);DeleteDC(memDC);return pass;
}

对于字幕,我们保存的图片名是logo_text.png;而对于Logo,我们加载Logo图片(logo.png)。

初始化完滤镜后,我们在视频图像解码出来之后就可以往上叠上水印了。解码出来的图像帧通过回调函数传递到应用层,这个回调函数的实现如下:

//视频图像回调
LRESULT CALLBACK VideoCaptureCallback(AVStream * input_st, enum PixelFormat pix_fmt, AVFrame *pframe, INT64 lTimeStamp)
{if(gpMainFrame->IsOverlayOSD()){CAutoLock lock(&g_FilterLock);if(filter_graph == NULL){CString strFilterLogoDesc;//注意:Logo图片需要跟执行文件同一个目录,并且要调用SetCurrentDirectory设置当前目录,否则初始化Filter将会失败,返回-2if(gpMainFrame->m_bIsOSDText)strFilterLogoDesc = "movie=logo_text.png[watermark];[in][watermark]overlay=10:10[out]"; //movie=后面的参数是Logo图标的文件名,overlay=后面的是坐标,OSD坐标位置暂时固定为(10, 10)elsestrFilterLogoDesc = "movie=logo.png[watermark];[in][watermark]overlay=10:10[out]";::SetCurrentDirectory(GetAppDir());int nRet = init_filters(strFilterLogoDesc, input_st->codec);if(nRet != 0){TRACE("Error: init_filters failed!! nRet = %d\n", nRet);goto end_filter;}}if(!g_bInitFilterOK)goto end_filter;AVFilterBufferRef *picref;pframe->pts = av_frame_get_best_effort_timestamp(pframe);if (av_buffersrc_add_frame(buffersrc_ctx, pframe) < 0) {TRACE( "Error while feeding the filtergraph\n");goto end_filter;}int ret;while (1) {ret = av_buffersink_get_buffer_ref(buffersink_ctx, &picref, 0);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)break;if (ret < 0)break;if (picref) {int y_size=picref->video->w*picref->video->h;AVFrame outframe;outframe.data[0] = picref->data[0];outframe.data[1] = picref->data[1];outframe.data[2] = picref->data[2];outframe.data[3] = 0;outframe.linesize[0] = picref->linesize[0];outframe.linesize[1] = picref->linesize[1];outframe.linesize[2] = picref->linesize[2];outframe.linesize[3] = 0;gpMainFrame->m_Painter.PlayAVFrame(input_st, &outframe);avfilter_unref_bufferp(&picref);}}// whilereturn 0;}else{
#if 0if(filter_graph != NULL){avfilter_graph_free(&filter_graph);filter_graph = NULL;}
#endif}end_filter://if(gpMainFrame->IsPreview()){gpMainFrame->m_Painter.PlayAVFrame(input_st, pframe);}return 0;
}

上述函数中,   成员变量  m_bOverlayOSD是表示当前是否正在叠加字幕或图标;变量   m_bIsOSDText 表示叠加的是字幕;根据这两个变量在界面上更新叠加对象类型,显示不同的叠加效果(目前只支持叠加一个OSD,不同类型的OSD需要切换,读者可在基础上进行扩展)。

最后说说这个水印滤镜的缺点:就是它不支持从内存冲传入水印图片,而需要读取一个磁盘上的图片文件,如果想实现动态更新的OSD效果,比如显示一个不停更新时间的OSD,则需要频繁的构建图片-》保存图片-》加载,效率肯定不好,不知道FFmpeg对这种情况有没有更好的实现方式?欢迎大家留言评论。

例子下载地址:https://download.csdn.net/download/zhoubotong2012/11855623

利用FFmpeg API进行字符叠加和加水印相关推荐

  1. 利用FFmpeg玩转Android视频录制与压缩(二)

    请尊重原创,转载请注明出处:http://blog.csdn.net/mabeijianxi/article/details/72983362 预热 时光荏苒,光阴如梭,离上一次吹牛逼已经过去了两三个 ...

  2. 利用FFmpeg玩转Android视频录制与压缩

    本文为剑西独家授权发布,剑西也是做Android多媒体开发,算是同道中人,不过他主要集中在视频压缩,利用FFmpeg,能做很多事,但是做到效果好,却不多.今天看下他的分享,剑西的blog是:http: ...

  3. 【技能教学】如何通过FFMPEG编码推RTSP视频直播流到EasyDarwin开源平台时叠加时间水印?

    继之前一篇科普文<如何使用RTSP推流组件EasyPusher将MP4文件推到EasyDarwin开源平台>发布后,有开发者提出疑问:假如需要显示视频直播时间,如何在推送的时候可以自定义在 ...

  4. 如何通过FFMPEG编码推RTSP视频直播流到EasyDarwin开源平台时叠加时间水印?

    继之前一篇科普文<如何使用RTSP推流组件EasyPusher将MP4文件推到EasyDarwin开源平台>发布后,有开发者提出疑问:假如需要显示视频直播时间,如何在推送的时候可以自定义在 ...

  5. 如何利用ffmpeg提供的API函数进行多媒体文件的解封装

    多媒体已经无处不在,程序员必须知道的一些多媒体封装知识 如何利用ffmpeg提供的API函数进行多媒体文件的解封装. 上一篇文章我们搭好了环境并编译出所需的ffmpeg库,本篇我们讨论如何利用ffmp ...

  6. 利用ffmpeg提供的库(API)进行音频与视频的编码并生成文件

    Output example.c 目录 [隐藏] 1 概述 2 音频输出 2.1 add_audio_stream 2.2 open_audio 2.3 get_audio_frame 2.4 wri ...

  7. 利用ffmpeg实现添加图片水印和文字水印,添加多个水印。代码和命令实现及中文水印乱码

    ffmpeg中文水印乱码两种原因 1.字符编码格式原因,中文必须是utf8编码格式的(我遇到的问题,在vs2013上写的中文,已做编码格式转码,放到centos7.2上编译运行也会出现中文乱码的问题, ...

  8. 史林枫:C#.NET利用ffmpeg操作视频实战(格式转换,加水印 一步到位)

    ffmpeg.exe是大名鼎鼎的视频处理软件,以命令行参数形式运行.网上也有很多关于ffmpeg的资料介绍.但是在用C#做实际开发时,却遇到了几个问题及注意事项,比如如何无损处理视频?如何在转换格式的 ...

  9. ffmpeg实现摄像头拉流_利用ffmpeg一步一步编程实现摄像头采集编码推流直播系统...

    了解过ffmpeg的人都知道,利用ffmpeg命令即可实现将电脑中摄像头的画面发布出去,例如发布为UDP,RTP,RTMP等,甚至可以发布为HLS,将m3u8文件和视频ts片段保存至Web服务器,普通 ...

最新文章

  1. phpmyadmin教程
  2. html如何与py_Web项目如何做单元测试?
  3. SQL Server 2008存储过程的加密
  4. leetcode 909. 蛇梯棋
  5. [翻译] python Tutorial 之一
  6. win7上修改MySQL数据库密码
  7. linux搭建redis
  8. 程序员的算法课(15)-分治法获取文件中出现频次最高100词
  9. CSS彻底研究(1)
  10. prim算法_历时两月,终拿字节跳动offer,算法面试题分享「带答案」
  11. 在服务中创建用户进程的方法(C#版)
  12. Java反射机制的简单应用
  13. LeetCode 6罗马数字转整数
  14. matlab的基本使用
  15. VC-MFC程序设计精讲
  16. 自动写诗APP项目、基于python+Android实现(技术:LSTM+Fasttext分类+word2vec+Flask+mysql)第一节
  17. 利用netstat查看http为短连接还是长连接?
  18. matlab电机系统建模与仿真软件下载,基于MATLAB直流无刷电动机系统建模与仿真
  19. 协众技术教你玩转电商海报设计
  20. 机械臂控制软件,上位机软件 此机器人上位软件。 运动采用通用G代码指令编程,具有G5三维的空间圆弧插补,空间直线插补功能

热门文章

  1. Java-分布式锁的实现方式
  2. apmserv 5.2.6 升级php,Windows + APMServ5.2.6/PHP5以上
  3. 在 unity中可以使用的直接设置音量大小的方法
  4. 送给1985年的朋友 ZT
  5. 基于3D关节点的人体动作识别综述(转)
  6. 北邮php,周琳娜-北京邮电大学网络空间安全学院
  7. 搞懂质数,质因子,互质,最大公约数,最小公倍数.
  8. 超直线能否用于真实物理空间?
  9. 在职研究生(多重继承)Python
  10. 从我做起 - 抵制1024程序员节-不要再自黑了