怎么在视频上叠加字幕和Logo--技术实现2

本文介绍如何使用修改视频像素的方法叠加字幕和Logo,详细解析了叠加原理及其实现过程,包括字幕与Logo的二色位图创建、叠加函数实现,并提供了动态设置OSD属性和性能优化的说明。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

上一篇博文介绍了渲染时叠加字幕的技术实现方法,而这一篇给大家讲解怎么用修改视频像素的方法叠加字幕和Logo。下面我把叠加字幕和Logo都统称为叠加OSD。

图像有分YUV和RGB格式,每个像素都有一个坐标和地址,我们要在图像指定地方叠加OSD,其实就是修改这些地方某些像素的像素值。叠加字幕原理简单描述就是:在视频图像上指定位置的像素值用OSD前景的颜色值代替。什么是OSD前景颜色?对字幕来说,前景色就是字符的文字颜色,即字体部分的像素的颜色,一般颜色我们用RGB表示,如果我们要显示红色字体的字符,那么可指定OSD前景颜色为RGB(255,0,0),举个例子,如果我们要在坐标(3,3)-(8,8)这个矩形区域上显示一段红色的文字“中国”,OSD的前景色为RGB(255,0,0),进行字幕叠加的时候,我们将颜色值为RGB(255,0,0)的像素保留,而背景像素(非红色的像素)则用原视频图像相应位置的像素代替,换句话说,在OSD区域除OSD的字符部分,其他部分都用视频图像的原来的像素值。对Logo(可看作是一个位图)来说,位图也有分前景和背景,但是跟叠加字幕有一点点区别,一张丰富颜色的位图前景往往包括很多种颜色(除非那张是一个单色位图),我们不能通过单一的前景色来过滤像素,常用的方法是根据背景色或Alpha通道来设置背景是否透明。位图的背景色可指定为跟前景不一样的一种颜色值,这种颜色也叫遮罩色。位图上跟背景遮罩色颜色相同的像素就“隐藏”掉,而跟遮罩色不同的像素则“覆盖”到视频图像上。另外对于位图还可以根据Alpha通道过滤背景,对32位位图,一个颜色有4个分量:R,G,B,A,其中A就是Alpha通道,Alpha通道的值表示这个像素在图像上的透明度,最小是0(透明),最大是255(完全不透明)。Alpha通道让叠加透明背景的图片更加方便,能让OSD的某一部分像素显示,而另一部分隐藏,现在很多PNG图片就是带Alpha通道的。

下面将给大家讲解一个例子,用到修改像素值的方法来叠加OSD(包括字幕和位图)。这个例子名字叫SubtitleMixVideo,参考了陆琪明先生的《DirectShow实务精选》一书中的一个例子--TitleOverlayFilter,例子是在RGB图像上进行叠加OSD,其中视频图像格式支持RGB565/RGB555/RGB24/RGB32,还没有实现YUV上进行叠加。事实上,在YUV图像上叠加OSD也是可以的(很多电影的字幕插件就实现了这个功能),原理跟RGB图像的一样,只是因为图像像素排列不同,计算像素的坐标时有点区别。

我写的SubtitleMixVideo例子基于TitleOverlayFilter的例程修改,但在上面做了很多修改和优化,主要改进点是:

1. 支持叠加字幕和Logo位图,原例程只支持叠加字幕。

2. 可动态设置OSD的属性(文字内容、字体颜色、坐标等),可动态打开或关闭OSD。

3. 支持多个OSD区域,显示OSD的数量可自由设置。

4. 优化OSD叠加的性能,如果OSD文字内容没有修改,则不重建OSD的DIB位图。

5. 去除DirectShow Filter的封装。原例程叠加字幕是做成一个DirectShow插件的,要依赖于DirectShow SDK,现在去除相关引用后,更方便用户使用。

6. 简化了其中的一些复杂流程,将多个子类合并到一个类中处理。

SubtitleMixVideo程序针对字幕叠加的处理流程图(叠加Logo的流程与之相似)如下:

其中,读取视频,解码视频的模块用到开源的FFmpeg库,这个对大家来说应该不陌生。而显示图像(渲染模块)用GDI,支持显示RGB格式的图像,虽然用GDI渲染性能不是很好,但是这里显示图像只是作为测试用,重要工作是做字幕叠加,所以就没有用到更复杂的画图API。

关于叠加字符的流程,这里再详细说一下:首先在内存中建立一张二色位图,然后在这个位图画出字符内容(主要通过一些Windows GDI函数)。于是,得到了一块含有字符的字符点阵:0--表示背景,1--表示前景像素。在实际叠加的时候,我们将图像帧知道位置的像素与字符点阵的像素对应,如果点阵的像素位值为0,则保持图像帧对应的像素值不变;如果为1,则图像帧对应的像素值替换为用户设置的字符颜色值。

字符叠加的类是CFilterTitleOverlay,我们先认识一下CFilterTitleOverlay类,看看它有什么方法和成员变量,下面是这个类的声明:

enum OverlayFilterState
{
	ST_FILTER_STOPPED = 0,
	ST_FILTER_ACTIVE = 1,
};

#define MAX_OVERLAY_NUM   4 //叠加OSD的最大个数

class CFilterTitleOverlay
{

public:
	CFilterTitleOverlay();
	~CFilterTitleOverlay();

	HRESULT SetInputVideoInfoToController(RGB_FORMAT  colorSpace, VIDEOINFOHEADER * pbFormat); //设置输入视频的信息,第一个参数为像素格式,第二个参数为视频头信息

	virtual HRESULT StartStreaming();
    virtual HRESULT StopStreaming();

	HRESULT DoTitleOverlay(BYTE * pData);


	void     LockOSD(int nIndex);   //对OSD属性加锁
	void     UnlockOSD(int nIndex); //对OSD属性解锁
	//说明:下面设置OSD或获取OSD属性的接口中用到了锁,作用是为了防止在多线程环境中设置OSD属性与OSD叠加(DoTitleOverlay)在不同线程中调用引起状态不同步的问题。但是,当要同时多个OSD属性时,频繁的加锁解锁引起比较大的性能开销,
	//所以,为了优化性能,我将加锁与解锁分离出来,做成两个单独的接口,开发者如果要设置OSD属性,请按如下方式调用:
	/*
	LockOSD(nIndex);
    put_TitleXXX(nIndex, ...);
	put_TitleYYY(nIndex, ...);
	put_TitleEnable(nIndex, TRUE);
	UnlockOSD(nIndex);
	*/

	// --- ITitleOverlay methods ---
	HRESULT put_TitleOverlayType(int nIndex, long inOverlayType);
	HRESULT get_TitleOverlayType(int nIndex, long * outOverlayType);
	HRESULT put_TitleOverlayStyle(int nIndex, int inUsingCover);
	HRESULT get_TitleOverlayStyle(int nIndex, int * outUsingCover);
	HRESULT put_Title(int nIndex, const char * inTitle, int inLength);
	HRESULT get_Title(int nIndex, char * outBuffer, int * outLength);
	HRESULT put_TitleColor(int nIndex, BYTE inR, BYTE inG, BYTE inB);
	HRESULT get_TitleColor(int nIndex, BYTE * outR, BYTE * outG, BYTE * outB);
	HRESULT put_TitleStartPosition(int nIndex, POINT inStartPos);
	HRESULT get_TitleStartPosition(int nIndex, POINT * outStartPos);
	HRESULT put_TitleFont(int nIndex, LOGFONT inFont);
	HRESULT get_TitleFont(int nIndex, LOGFONT * outFont);
	//HRESULT put_TitleDuration(double inStart, double inEnd);
	//HRESULT get_TitleDuration(double * outStart, double * outEnd);
	HRESULT  put_TitleEnable(int nIndex, BOOL bEnable);  //是否使OSD生效

	HRESULT  put_TitleBitmap(int nIndex, HBITMAP hBmp, BOOL byAlpha, COLORREF colorKey); //传入的位图句柄必须是DIB位图
	HRESULT  put_TitleBitmap(int nIndex, LPCTSTR lpszImagePath, BOOL byAlpha, COLORREF colorKey); //传入OSD图标的路径
 
protected:
	void ReleaseOverlayController(int nIndex);
	//void SideEffectOverlayTypeChanged(int nIndex);

private:
	OVERLAY_TYPE            mOverlayType[MAX_OVERLAY_NUM];
	COverlayController *    mOverlayController[MAX_OVERLAY_NUM];

	CCritSec                mITitleOverlaySync; //互斥锁
	//BOOL                    mNeedEstimateFrameRate;

	RGB_FORMAT              m_colorSpace; //输入视频的像素格式
	VIDEOINFOHEADER         m_VideoInfoHeader; //视频头信息

	OverlayFilterState      m_State; //0--非活动,1--活动(开始叠加字幕)
};

CFilterTitleOverlay类有个很重要的成员指针变量mOverlayController,它指向一个COverlayController结构类型的数据成员(数组),这成员变量负责真正的叠加OSD的工作,它是一个数组,每个元素管理一个OSD区域,目前设置的最大OSD区域数为4,即最多可叠加4个OSD。

接着看看COverlayController类的声明:

class COverlayController
{
protected:
	//CBasePixel *        mPixelConverter;
	BOOL                mCanDoOverlay;      // Title overlay work
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值