在视频上叠加字符或者画框(或者说添加水印)的方法有很多种,下面列出3种:
- 1.将我们要添加的水印与视频数据进行融合;
- 2.使用D3D;
- 3.使用ddraw。
这三种方式我都会进行一个简单的描述,本文先说说使用ddraw的方式。
先参考一下https://blog.csdn.net/ww506772362/article/details/51034549#commentsedit一文说的很好。
需要提醒一下的是,本文下方有个评论,非常有用。
我们如果是在在RGB视频上画图(直线,矩形等),一般采用双缓冲区继续,使用内存MemoryDC,来实现画的图形在视频上显示不闪烁的功能,但是我们知道用RGB显示视频都是使用GDI进行渲染,这样很耗CPU,那么我们能不能在YUV上进行视频渲染呢,答案是肯定的,使用ddraw直接显示yuv就ok了,可以支持yuv422和yuv420的直接使用显卡显示,不耗CPU,但是我们在使用ddraw显示时,然后在配合GDI画图(直线或矩形等),画的图形是闪烁的,原因是我们在ddraw直接显示yuv视频时,使用的是离屏表面的方法,将yuv数据拷贝到离屏表面,然后在blt到主表面,这样用gdi画图时,和视频刷新不同步,造成闪烁,那么我们怎么解决该问题呢?方法如下:
新增加一个离屏表面,我们定义成osd离屏表面吧,我们将yuv数据拷贝到离屏表面后,在将该离屏表面blt到osd离屏表面,然后在osd离屏表面上画直线或矩形,画完后在blt到主表面,这样画的图形就不会闪烁了。
这里说说上面链接中的源码,有特定情况下可能会有异常:
- 1、当我们视频的分辨率与电脑显示器的分辨率不一致时;
- 2、当播放窗口大小发生改变时;
对于异常一,我们要求主表面的rect要与离屏表面rect一致,否则,在叠加表面时,会返回0x88760096
即DDERR_INVALIDRECT
。
ZeroMemory(&m_ddsd, sizeof(m_ddsd));
m_ddsd.dwSize = sizeof(m_ddsd);
m_ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
m_ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH;
m_ddsd.dwWidth = m_rctDest.right;
m_ddsd.dwHeight = m_rctDest.bottom;
hr = m_lpddraw->CreateSurface(&m_ddsd, &m_pOsdSurface, NULL);
这里就要求m_rctDest要与主表面的一致:
对于异常二,我们要求在窗口rect发生改变时,我们对应的离屏表面rect也要对应做改变。
//创建OSD画图离屏表面
if (!m_pOsdSurface || m_lastwidth != nwidth || m_lastheight != nheight || m_rctDestLast.right != m_rctDest.right || m_rctDestLast.bottom != m_rctDest.bottom)
{
if (m_pOsdSurface)
{
m_pOsdSurface->Release();
m_pOsdSurface = NULL;
}
ZeroMemory(&m_ddsd, sizeof(m_ddsd));
m_ddsd.dwSize = sizeof(m_ddsd);
m_ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
m_ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH;
m_ddsd.dwWidth = m_rctDest.right;
m_ddsd.dwHeight = m_rctDest.bottom;
hr = m_lpddraw->CreateSurface(&m_ddsd, &m_pOsdSurface, NULL);
if (hr != DD_OK)
{
return false;
}
}
这里要求增加一个判断,当主表面窗口rect发生改变时,我们需要重新创建离屏表面。
现在我把全部源码放出来:
h文件:
#ifndef _H_DISPLAY_H_
#define _H_DISPLAY_H_
#include "ddraw.h"
#include <time.h>
#include <stdio.h>
#include <string>
class display
{
public:
display();
public:
virtual ~display();
public:
long init(HWND hwnd);
void uninit();
long inputsource(unsigned char * src, long nwidth, long nheight, long ntype, long nrate, __int64 npts, bool bOsd=false, std::string sOSD="", int x=0, int y=0, int size = 0, int fontr = 0, int fontg = 0, int fontb = 0, float diaphaneity = 0.0);
static BOOL WINAPI DDEnumCallBack( GUID *pguid, LPTSTR pszdesc, LPTSTR pszdevicename, LPVOID pcontext, HMONITOR hmonior );
long createoffscreen(LPDIRECTDRAWSURFACE7 * offscreen, DDSURFACEDESC2 * ddsd, long nwidth, long nheight, long ntype);
long createddsd(DDSURFACEDESC2 * ddsd, long nwidth, long nheight, long ntype);
void showOSD(HRESULT ddrval, std::string sOSD, int x, int y, int size, int fontr, int fontg, int fontb, float diaphaneity);
void reinit();
private:
struct MONITOR_INFO
{
LPDIRECTDRAW7 lpDD; // DirectDraw 对象指针
HMONITOR hMon;
};
LPDIRECTDRAW7 m_lpddraw; // DirectDraw 对象指针
LPDIRECTDRAWSURFACE7 m_lpddsprimary; // DirectDraw 主表面指针
LPDIRECTDRAWSURFACE7 m_lpddsoffscr; // DirectDraw 离屏表面指针
LPDIRECTDRAWSURFACE7 m_lpddsoffscr2; // DirectDraw 离屏表面指针
LPDIRECTDRAWCLIPPER m_lpclipper;
HWND m_hwnd;
DDSURFACEDESC2 m_ddsd;
bool m_init;
long m_lastwidth;
long m_lastheight;
RECT m_rctDest; // 目标区域
RECT m_rctSour; // 源区域
RECT m_rctDestLast;
DDBLTFX m_ddbltfx;
//osd
LPDIRECTDRAWSURFACE7 m_pOsdSurface; //画图表面
};
#endif
cpp文件:
#include "display.h"
#pragma comment (lib, "ddraw.lib")
#pragma comment(lib, "dxguid.lib")
display::display() : m_lpddraw(NULL),m_lpddsprimary(NULL), m_lpddsoffscr(NULL),
m_lpddsoffscr2(NULL), m_pOsdSurface(NULL), m_lpclipper(NULL), m_init(false), m_lastwidth(0), m_lastheight(0)
{
memset(&m_ddsd, 0, sizeof(m_ddsd));
}
display::~display()
{
}
BOOL WINAPI display::DDEnumCallBack( GUID *pguid, LPTSTR pszdesc, LPTSTR pszdevicename, LPVOID pcontext, HMONITOR hmonior )
{
LPDIRECTDRAW7 lpDD = NULL; // DirectDraw 对象指针
MONITOR_INFO *pthis = ( MONITOR_INFO *)pcontext;
if ( !pthis )
{
return FALSE;
}
if ( pthis->hMon == hmonior )
{
if (DirectDrawCreateEx(pguid, (void**)&lpDD, IID_IDirectDraw7, NULL) != DD_OK)
{
OutputDebugStringA("创建lpDD error!\r\n");
}
pthis->lpDD = lpDD;
lpDD = NULL;
}
OutputDebugStringA("创建DirectDraw begin\r\n");
// 创建DirectDraw对象
return TRUE;
}
void display::reinit()
{
uninit();
init(m_hwnd);
}
long display::init(HWND hwnd)
{
if (m_init)
{
return 0;
}
m_hwnd = hwnd;
//MONITOR_INFO mi_lpdd;
//mi_lpdd.hMon = ::MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
// 枚举监视器并创建DirectDraw对象
// DirectDrawEnumerateExA( display::DDEnumCallBack,
// &mi_lpdd,
// DDENUM_ATTACHEDSECONDARYDEVICES |
// DDENUM_DETACHEDSECONDARYDEVICES |
// DDENUM_NONDISPLAYDEVICES
// );
// m_lpddraw = mi_lpdd.lpDD;
if(m_lpddraw == NULL)
{
if (DirectDrawCreateEx(NULL, (VOID**)&m_lpddraw,IID_IDirectDraw7,NULL) != DD_OK)
{
OutputDebugStringA("display::init 创建DirectDraw NOK\r\n");
}
if (!m_lpddraw)
{
OutputDebugStringA("display::init 2创建DirectDraw NOK\r\n");
return -1;
}
}
if (m_lpddraw->SetCooperativeLevel(NULL, DDSCL_NORMAL | DDSCL_NOWINDOWCHANGES | DDSCL_ALLOWREBOOT ) != DD_OK)
{
OutputDebugStringA("display::init 创建lpDDxiezuo error!\r\n");
if (m_lpddraw)
{
m_lpddraw->Release();
m_lpddraw = NULL;
}
return -1;
}
// 创建主表面
DDSURFACEDESC2 ddsd; // DirectDraw 表面描述
ZeroMemory(&ddsd, sizeof(ddsd));
ddsd.dwSize = sizeof(ddsd);
ddsd.dwFlags = DDSD_CAPS;// | DDSD_BACKBUFFERCOUNT;
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
//ddsd.dwBackBufferCount = 1;
if (m_lpddraw->CreateSurface(&ddsd, &m_lpddsprimary, NULL) != DD_OK)
{
OutputDebugStringA("display::init 创建m_lpddsprimary error!\r\n");
if (m_lpddraw)
{
m_lpddraw->Release();
m_lpddraw = NULL;
}
return -1;
}
if(m_lpddraw->CreateClipper(0, &m_lpclipper, NULL) != DD_OK)
{
if (m_lpddraw)
{
m_lpddraw->Release();
m_lpddraw = NULL;
}
OutputDebugStringA("display::init 创建m_lpclipper error!\r\n");
return -1;
}
m_lpclipper->SetHWnd(0, hwnd);
m_lpddsprimary->SetClipper(m_lpclipper);
m_init = true;
return 0;
}
void display::uninit()
{
if (m_lpddsoffscr)
{
m_lpddsoffscr->Release();
m_lpddsoffscr = NULL;
}
if (m_lpddsoffscr2)
{
m_lpddsoffscr2->Release();
m_lpddsoffscr2 = NULL;
}
if (m_pOsdSurface)
{
m_pOsdSurface->Release();
m_pOsdSurface = NULL;
}
if (m_lpddsprimary)
{
m_lpddsprimary->Release();
m_lpddsprimary = NULL;
}
if (m_lpclipper)
{
m_lpclipper->Release();
m_lpclipper = NULL;
}
if (m_lpddraw)
{
m_lpddraw->Release();
m_lpddraw = NULL;
}
m_init = false;
m_lastwidth = 0;
m_lastheight = 0;
}
long display::createddsd(DDSURFACEDESC2 * ddsd, long nwidth, long nheight, long ntype)
{
ddsd->dwSize = sizeof(*ddsd);
ddsd->ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_VIDEOMEMORY ; //DDSCAPS_OVERLAY DDSCAPS_OFFSCREENPLAIN;
ddsd->dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT;// | DDSD_PITCH | DDSD_LPSURFACE;
ddsd->dwWidth = nwidth;
ddsd->dwHeight = nheight;
ddsd->ddpfPixelFormat.dwSize = sizeof(DDPIXELFORMAT);
ddsd->ddpfPixelFormat.dwFlags = DDPF_FOURCC;
if ( ntype == 2)
{
ddsd->ddpfPixelFormat.dwFourCC = MAKEFOURCC('R','G','B',' ');
ddsd->ddpfPixelFormat.dwFlags |= DDPF_RGB;
ddsd->ddpfPixelFormat.dwRGBBitCount = 32;
}
else if ( ntype == 3)
{
ddsd->ddpfPixelFormat.dwFourCC = MAKEFOURCC('Y','V','1','2');
ddsd->ddpfPixelFormat.dwFlags |= DDPF_YUV;
ddsd->ddpfPixelFormat.dwYUVBitCount = 12;
}
else if ( ntype == 4)
{
ddsd->ddpfPixelFormat.dwFourCC = MAKEFOURCC('U','Y','V','Y');
ddsd->ddpfPixelFormat.dwFlags |= DDPF_YUV;
ddsd->ddpfPixelFormat.dwYUVBitCount = 16;
}
else if ( ntype == 5)
{
ddsd->ddpfPixelFormat.dwFourCC = MAKEFOURCC(/*'I','4','2','0'*/'Y','V','1','2');
ddsd->ddpfPixelFormat.dwFlags |= DDPF_YUV;
ddsd->ddpfPixelFormat.dwYUVBitCount = 12;
}
else if ( ntype == 6)
{
ddsd->ddpfPixelFormat.dwFourCC = MAKEFOURCC('Y','U','Y','2'); //or YUYV. the same format.
ddsd->ddpfPixelFormat.dwFlags |= DDPF_YUV;
ddsd->ddpfPixelFormat.dwYUVBitCount = 16;
}
else
{
OutputDebugStringA("************LOG_RTM, unsupported pixel format: %d, pcd->m_bdInRender.nType\r\n");
return -1;
}
return 0;
}
long display::createoffscreen(LPDIRECTDRAWSURFACE7 * offscreen, DDSURFACEDESC2 * ddsd, long nwidth, long nheight, long ntype)
{
__int64 nerror = 0;
HRESULT result = m_lpddraw->CreateSurface(ddsd, offscreen, NULL);
if(FAILED(result))
{
if (result == DDERR_NODIRECTDRAWHW)
{
OutputDebugStringA("************createoffscreen DDERR_NODIRECTDRAWHW*************************\n");
reinit();
}
return -1;
}
return 0;
}
long display::inputsource(unsigned char * src, long nwidth, long nheight, long ntype, long nrate, __int64 npts, bool bOsd, std::string sOSD, int x, int y, int size, int fontr, int fontg, int fontb, float diaphaneity)
{
if (!m_init)
{
return -1;
}
__int64 nerror = 0;
if (!m_lpddsoffscr || m_lastwidth != nwidth || m_lastheight != nheight)
{
if (m_lpddsoffscr)
{
m_lpddsoffscr->Release();
m_lpddsoffscr = NULL;
}
nerror = createddsd(&m_ddsd, nwidth, nheight, ntype);
if (nerror)
{
OutputDebugStringA("display::inputsource createddsd error[m_lpddsoffscr equ null]\r\n");
return -1;
}
//创建数据离屏表面
nerror = createoffscreen(&m_lpddsoffscr, &m_ddsd, nwidth, nheight, ntype);
if (nerror)
{
OutputDebugStringA("display::inputsource createoffscreen error[m_lpddsoffscr equ null]\r\n");
return -1;
}
}
if (!m_lpddsoffscr)
{
OutputDebugStringA("display::inputsource error[m_lpddsoffscr equ null]\r\n");
return -1;
}
HRESULT hr = m_lpddsoffscr->Lock(NULL,&m_ddsd, /*DDLOCK_WAIT |*/ DDLOCK_WRITEONLY,NULL);
if (hr == DDERR_SURFACELOST)
{
OutputDebugStringA("DDERR_SURFACELOST DDERR_SURFACELOST DDERR_SURFACELOST...1\n");
hr = m_lpddsoffscr->Restore();
hr = (hr != DD_OK)? hr : m_lpddsoffscr->Lock(NULL,&m_ddsd,/*DDLOCK_WAIT |*/ DDLOCK_WRITEONLY,NULL);
}
if (hr != DD_OK)
{
OutputDebugStringA("display::inputsource Lock error\r\n");
return -1;
}
LPBYTE lpSurf = (LPBYTE)m_ddsd.lpSurface;
LPBYTE lpSurfV = lpSurf + m_ddsd.dwHeight*m_ddsd.lPitch;
LPBYTE lpSurfU = lpSurfV + (m_ddsd.dwHeight*m_ddsd.lPitch>>2);
if (3 == ntype || 5 == ntype)
{
LPBYTE lpY = src;
LPBYTE lpU;
LPBYTE lpV;
if (3 == ntype)
{
lpV = lpY + nwidth * nheight;
lpU = lpV + (nwidth * nheight>>2);
}
else
{
lpU = lpY + nwidth * nheight;
lpV = lpU + (nwidth * nheight>>2);
}
// 填充离屏表面
unsigned long lddsdHeightHalf = m_ddsd.dwHeight>>1;
unsigned long lddsdlPitchHalf = m_ddsd.lPitch>>1;
unsigned long lWidhtHalf = nwidth>>1;
for(unsigned long i = 0; i < m_ddsd.dwHeight; i++)
{
memcpy(lpSurf, lpY, nwidth);
lpY += nwidth;
lpSurf += m_ddsd.lPitch;
if (i < lddsdHeightHalf)
{
memcpy(lpSurfU, lpU, lWidhtHalf);
lpU += lWidhtHalf;
lpSurfU += lddsdlPitchHalf;
memcpy(lpSurfV, lpV, lWidhtHalf);
lpV += lWidhtHalf;
lpSurfV += lddsdlPitchHalf;
}
}
}
m_lpddsoffscr->Unlock(NULL);
m_rctDestLast = m_rctDest;
m_rctSour.left = 0;
m_rctSour.top = 0;
m_rctSour.right = m_ddsd.dwWidth;
m_rctSour.bottom = m_ddsd.dwHeight;
GetClientRect(m_hwnd, &m_rctDest);
MONITORINFO mi;
memset(&mi, 0, sizeof(MONITORINFO));
mi.cbSize = sizeof(MONITORINFO);
HMONITOR hmon = ::MonitorFromWindow(m_hwnd, MONITOR_DEFAULTTONEAREST);
if (!hmon)
{
OutputDebugStringA("display::inputsource MonitorFromWindow error[hmon equ null]\r\n");
return -1;
}
::GetMonitorInfo(hmon, &mi);
m_rctDest.left -= mi.rcMonitor.left;
m_rctDest.right -= mi.rcMonitor.left;
m_rctDest.top -= mi.rcMonitor.top;
m_rctDest.bottom -= mi.rcMonitor.top;
ClientToScreen(m_hwnd, (LPPOINT)&m_rctDest.left);
ClientToScreen(m_hwnd, (LPPOINT)&m_rctDest.right);
memset(&m_ddbltfx, 0, sizeof(m_ddbltfx));
m_ddbltfx.dwSize = sizeof(m_ddbltfx);
m_ddbltfx.dwROP = SRCCOPY;
if (bOsd)
{
//创建OSD画图离屏表面
if (!m_pOsdSurface || m_lastwidth != nwidth || m_lastheight != nheight || m_rctDestLast.right != m_rctDest.right || m_rctDestLast.bottom != m_rctDest.bottom)
{
if (m_pOsdSurface)
{
m_pOsdSurface->Release();
m_pOsdSurface = NULL;
}
ZeroMemory(&m_ddsd, sizeof(m_ddsd));
m_ddsd.dwSize = sizeof(m_ddsd);
m_ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
m_ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH;
m_ddsd.dwWidth = m_rctDest.right;
m_ddsd.dwHeight = m_rctDest.bottom;
hr = m_lpddraw->CreateSurface(&m_ddsd, &m_pOsdSurface, NULL);
if (hr != DD_OK)
{
return false;
}
}
//加入Osd离屏表面内容
HRESULT ddrval;
ddrval = m_pOsdSurface->Blt(&m_rctDest, m_lpddsoffscr, NULL, DDBLT_WAIT, NULL);
if (ddrval != DD_OK)
{
ddrval = m_lpddsprimary->Blt(&m_rctDest, m_lpddsoffscr, &m_rctSour, DDBLT_WAIT, NULL);
}
else
{
showOSD(ddrval, sOSD, x, y, size, fontr, fontg, fontb, diaphaneity);
}
}
else
{
// Blt到主表面上
HRESULT hr2 = m_lpddsprimary->Blt(&m_rctDest, m_lpddsoffscr, &m_rctSour, DDBLT_WAIT, &m_ddbltfx);
if (hr2 == DDERR_SURFACELOST)
{
OutputDebugStringA("DDERR_SURFACELOST DDERR_SURFACELOST DDERR_SURFACELOST...\n");
hr2 = m_lpddsprimary->Restore();
}
if (hr2 != DD_OK)
{
OutputDebugStringA("display::inputsource Blt error\r\n");
return -1;
}
}
m_lastwidth = nwidth;
m_lastheight = nheight;
return 0;
}
void display::showOSD(HRESULT ddrval, std::string sOSD, int x, int y, int size, int fontr, int fontg, int fontb, float diaphaneity)
{
HDC hDC = NULL;
ddrval = m_pOsdSurface->GetDC(&hDC);
if ((ddrval == DD_OK) && (hDC != NULL))
{
LOGFONT lf; //定义字体结构
lf.lfWeight = 400; //字体磅数=10
lf.lfHeight = size*3/2; //字体高度(旋转后的字体宽度)=56
lf.lfWidth = size/2; //字体宽度(旋转后的字体高度)=20
lf.lfUnderline = FALSE; //无下划线
lf.lfStrikeOut = FALSE; //无删除线
lf.lfItalic = FALSE; //非斜体
lf.lfEscapement = 0; //字体显示角度=270°
lf.lfCharSet = DEFAULT_CHARSET; //使用缺省字符集
//lf.lfPitchAndFamily = DEFAULT_PITCH;
strcpy(lf.lfFaceName, "Arial "); //字体名=@system
HFONT myLogFont = CreateFontIndirect(&lf);
HGDIOBJ pOldFont = SelectObject(hDC, myLogFont);//选入设备描述表
//SetTextCharacterExtra(hDC, 0);
//叠加文字
SetBkMode(hDC, TRANSPARENT);
COLORREF rgb = RGB(fontr, fontg, fontb);
SetTextColor(hDC, rgb);
int nWidth = m_rctDest.right - m_rctDest.left;
int nHeight = m_rctDest.bottom - m_rctDest.top;
x = m_rctDest.left + x % nWidth;
y = m_rctDest.top + y % nHeight;
int nPx = (double)(lf.lfWidth) * (sOSD.size());
int nPy = lf.lfHeight;
if ((x + nPx) > m_rctDest.right)
{
x = m_rctDest.right - nPx;
}
if (y + nPy > m_rctDest.bottom)
{
y = m_rctDest.bottom - nPy;
}
if (y < nPy)
{
y = nPy;
}
if (x < m_rctDest.left)
{
x = m_rctDest.left;
}
TextOut(hDC, x, y, sOSD.c_str(), sOSD.size());
SelectObject(hDC, pOldFont); //将myFont从设备环境中分离
DeleteObject(myLogFont); //删除myLogFont对象
m_pOsdSurface->ReleaseDC(hDC);
m_lpddsprimary->Blt(&m_rctDest, m_pOsdSurface, &m_rctDest, DDBLT_WAIT, NULL);
}
}