这篇文章我给大家讲解的这种字幕叠加和Logo叠加方法是在渲染视频的时候“画“上去的,其实是通过某种API将OSD和Logo绘制到显卡缓存,然后提交缓存到屏幕。我们知道渲染视频有几种常用的API:GDI,DirectDraw,D3D,OpenGL,SDL,其中SDL库是对前面几种API在不同平台上的封装,是一个大集合。我给大家演示的例子是针对Windows平台的,一般在Windows平台上我们会用DirectDraw或D3D绘制图像(不建议用GDI,因为效率低),而我写的这个叠加字幕和Logo的例子是基于DirectDraw绘图的,DirectDraw API用起来比较简单,并且效率也不错。
用本文介绍的DirectDraw API来绘制字幕和Logo(位图),实现起来并不复杂。因为DirectDraw创建的表面能通过GetDC方法获得一个DC,那么你可以往这个DC上画任何东西,就像用GDI在Windows窗口上绘图一样简单。基于DirectDraw实现,叠加字幕和叠加Logo没有太大区别,因为对显卡来说,两者都是一个图层,你可以往显卡缓存的某个坐标上输出一段字符,也可以画一个位图,只是输出字符和绘制位图调用的API有点不同。因为DirectDraw绘图对字幕叠加和位图叠加都是同样原理,所以没有必要对这两种对象的实现方式作一一介绍,下面提到的字幕叠加的实现方式跟位图叠加方式基本是一样的。
为了更清晰的给大家讲解这种方法的实现思路,我写了一个例子来演示叠加字幕和位图的效果。先亮一下这个Demo的界面:
这个例子具有的功能:播放视频,显示OSD文字,显示OSD图标,关闭OSD。打开视频从“文件”菜单选择一个视频文件的路径,然后按“开始播放”,视频就开始渲染到界面的窗口中。点击“编辑菜单”可以对OSD的打开关闭进行控制。
这个例子对视频的分离和解码用到FFmpeg库,而视频的显示和字幕和图标(Logo)的显示则用的是DirectDraw。
下面讲一下代码实现的流程。
因为用到FFmpeg,需要在预编译头文件引用FFmpeg的头文件和静态库:
#ifdef __cplusplus
extern "C" {
#endif
#ifdef HAVE_AV_CONFIG_H
#undef HAVE_AV_CONFIG_H
#endif
#include "./include/libavcodec/avcodec.h"
#include "./include/libavutil/mathematics.h"
#include "./include/libavutil/avutil.h"
#include "./include/libswscale/swscale.h"
#include "./include/libavutil/fifo.h"
#include "./include/libavformat/avformat.h"
#include "./include/libavutil/opt.h"
#include "./include/libavutil/error.h"
#include "./include/libswresample/swresample.h"
#include "./include/libavutil/audio_fifo.h"
#include "./include/libavutil/time.h"
#ifdef __cplusplus
}
#endif
#pragma comment( lib, "avcodec.lib")
#pragma comment( lib, "avutil.lib")
#pragma comment( lib, "avformat.lib")
#pragma comment(lib, "swresample.lib")
#pragma comment(lib, "swscale.lib" )
还有要初始化FFmpeg库:
avcodec_register_all();
av_register_all();
程序中用到几个重要的类:
FileStreamReadTask: 这个类封装了FFmpeg对媒体文件的音视频分离,和视频解码的解码操作。并提供回调函数将解码出来的帧传到外部调用者。
CDXDrawPainter(基类是CVideoPlayer): 这个类负责渲染视频图像,以及在视频上叠加字幕和图标,支持叠加多个OSD区域,可以动态打开或关闭某个OSD图层显示。
CMainFrame: 这个主窗口界面内,负责管理各种对象,处理界面上某些Windows消息事件,菜单和按钮的响应都放在这个类里去处理。
CVideoDisplayWnd: 视频显示窗口,目前只是在OnPaint函数里用黑色画刷填充窗口背景,没有其他操作。
其中,在CMainFrame类里面定义了如下几个对象:
FileStreamReadTask m_FileStreamTask; //解码视频
CDXDrawPainter m_Painter; //用DirectDraw绘制
CVideoDisplayWnd m_wndView; //视频窗口类
在界面菜单上选择文件后,然后点击“开始播放”,会调用到CMainFrame的一个方法OnStartStream:
LRESULT CMainFrame:: OnStartStream(WPARAM wParam, LPARAM lParam)
{
if(strlen(m_szFilePath) == 0)
{
return 1;
}
int nRet = m_FileStreamTask.OpenMediaFile(m_szFilePath);
if(nRet != 0)
{
MessageBox(_T("打开文件失败"), _T("提示"), MB_OK|MB_ICONERROR);
return -1;
}
long cx, cy;
cx = cy = 0;
m_FileStreamTask.GetVideoSize(cx, cy); //获取视频的分辨率
if(cx > 0 && cy > 0)
{
m_Painter.SetVideoWindow(m_wndView.GetSafeHwnd()); //设置视频预览窗口
m_Painter.SetRenderSurfaceType(SURFACE_TYPE_YV12);
m_Painter.SetSourceSize(CSize(cx, cy));
m_Painter.Open(); //打开渲染器
}
m_FileStreamTask.SetVideoCaptureCB(VideoCaptureCallback);
m_FileStreamTask.StartReadFile();
StartTime = timeGetTime();
m_frmCount = 0;
m_nFPS = 0;
m_bCapture = TRUE;
return 0;
}
而“停止播放”触发的另外一个方法是OnStopStream,代码如下:
LRESULT CMainFrame:: OnStopStream(WPARAM wParam, LPARAM lParam)
{
m_FileStreamTask.StopReadFile();
//m_Painter.Close();
m_Painter.Stop();
m_wndView.Invalidate(); //刷新视频窗口
//TRACE("播放用时:%d 秒\n", (timeGetTime() - StartTime)/1000);
m_bCapture = FALSE;
StartTime = 0;
return 0;
}
在OnStartStream方法中,我们设置了视频解码后回调图像的回调函数:m_FileStreamTask.SetVideoCaptureCB(VideoCaptureCallback);
其中VideoCaptureCallback回调函数的实现代码是:
//视频图像回调
LRESULT CALLBACK VideoCaptureCallback(AVStream * input_st, enum PixelFormat pix_fmt, AVFrame *pframe, INT64 lTimeStamp)
{
if(gpMainFrame->IsPreview())
{
gpMainFrame->m_Painter.PlayAVFrame(input_st, pframe);
}
return 0;
}
m_Painter是CDXDrawPainter类的对象,负责渲染视频和叠加字幕,图标。回调函数中调用了m_Painter对