( DirectX 系列 06 ) DirectShow 字符疊加 Filter 編碼分析
在很多視頻播放的軟件當中,字幕的處理是免不了的,有些可能本身已經加載到圖像當中未做處理,但大部分都是通過字符疊加來進行處理的。 DirectShow 的字符疊加 Filter 在這些軟件中都扮演這絕佳的作用。這一節來介紹 DirectShow 字符疊加 Filter 編碼的實現,如下詳細介紹;
這個 Filter 的大概作用是在視頻流指定的一段時間內進行字符疊加,字符字體、大小、顏色都進行控制,普遍支持 RGB 的各種編碼格式,同時實現字符的其他效果例如滾動的。如下來看看具體的編碼實現;
注冊表配置
注冊表的配置對于 Filter 的開發者來說都大同小異,主要對 g_Templates 進行配置,如下代碼;
// {E3FB4BFE-8E5C-4aec-8162-7DA55BE486A1}
DEFINE_GUID ( CLSID_HQTitleOverlay ,
0xe3fb4bfe, 0x8e5c, 0x4aec, 0x81, 0x62, 0x7d, 0xa5, 0x5b, 0xe4, 0x86, 0xa1);
// {E70FE57A-19AA-4a4c-B39A-408D49D73851}
DEFINE_GUID ( CLSID_HQTitleOverlayProp ,
0xe70fe57a, 0x19aa, 0x4a4c, 0xb3, 0x9a, 0x40, 0x8d, 0x49, 0xd7, 0x38, 0x51);
……
// List of class IDs and creator functions for the class factory. This
// provides the link between the OLE entry point in the DLL and an object
// being created. The class factory will call the static CreateInstance
CFactoryTemplate g_Templates [] =
{
{
L "HQ Title Overlay Std." ,
& CLSID_HQTitleOverlay ,
CFilterTitleOverlay :: CreateInstance ,
NULL ,
& sudFilter
},
{
L "HQ Title Overlay Property Page" ,
& CLSID_HQTitleOverlayProp ,
CTitleOverlayProp :: CreateInstance
}
};
CFilterTitleOverlay 類的實現
CFilterTitleOverlay 類設計是整個 Filter 的關鍵所在,其中最重要的是父類的選擇。如下代碼;
class CFilterTitleOverlay : public CTransInPlaceFilter
, public ISpecifyPropertyPages
, public ITitleOverlay
CTransInPlaceFilter 類是 CFilterTitleOverlay 功能實現的關鍵只關鍵,該類可以提供在視頻傳輸的過程中截獲數據流,方便于字符疊加;
ISpecifyPropertyPages 類是 CFilterTitleOverlay 提供屬性頁面支持功能, ITitleOverlay 是一個 Interface 接口的純虛類,也就是所謂的接口,如下來看看這個接口是如何實現;
// ITitleOverlay
// 原型如下
// interface __declspec(novtable) ITitleOverlay : public IUnknown
DECLARE_INTERFACE_ ( ITitleOverlay , IUnknown )
{
// 設置 Filter 進行疊加的類型,如果需要改變類型,這個函數必須第一個設置,
// 調用這個函數成功后,才能調用其他的函數進行參數設置。
// 可以設置的疊加類型參見枚舉類型 OVERLAY_TYPE 的定義。
// 如下原型
// virtual HRESULT _stcall put_TitleOverlayType(long inOverlayType) = 0
STDMETHOD ( put_TitleOverlayType ) ( THIS_
long inOverlayType
) PURE ;
……
};
構造函數
先從 CFilterTitleOverlay 類的構造函數說起,在 CFilterTitleOverlay 構造函數中有一個對象是必須創建的,這個對象就是 COverlayController , COverlayController 是用來控制疊加效果的通用類,如下代碼;
CFilterTitleOverlay :: CFilterTitleOverlay ( TCHAR * tszName , LPUNKNOWN punk , HRESULT * phr ) :
CTransInPlaceFilter ( tszName , punk , CLSID_HQTitleOverlay , phr )
{
mOverlayType = OT_STATIC ;
mOverlayController = new COverlayController ();
mNeedEstimateFrameRate = FALSE ;
char szTitle [] = "Hello, DirectShow!" ;
put_Title ( szTitle , sizeof ( szTitle ));
}
CreateInstance
函數
這個函數不用多說了,是 CUnknown 中的一個虛函數,其中這個函數主要用來創建 CFilterTitleOverlay 對象,是 COM 組件必須具備的函數,否則就失去 COM 組件的意義,如下代碼詳解;
CUnknown * WINAPI CFilterTitleOverlay :: CreateInstance ( LPUNKNOWN punk , HRESULT * phr )
{
#if 1
// 做指定應用程序驗證
char szCreatorPath [256], szCreatorName [256];
:: strcpy ( szCreatorPath , "" );
:: strcpy ( szCreatorName , "" );
HMODULE hModule = :: GetModuleHandle ( NULL );
:: GetModuleFileName ( hModule , szCreatorPath , 256);
char * backSlash = :: strrchr ( szCreatorPath , '//' );
if ( backSlash )
{
strcpy ( szCreatorName , backSlash );
}
:: _strlwr ( szCreatorName );
// Please specify your app name with lowercase
if (:: strstr ( szCreatorName , "graphedt" ) == NULL &&
:: strstr ( szCreatorName , "ourapp" ) == NULL )
{
* phr = E_FAIL ;
return NULL ;
}
#endif
// 創建 CFilterTitleOverlay 對象
CFilterTitleOverlay * pNewObject = new CFilterTitleOverlay ( NAME ( "TitleOverlay" ), punk , phr );
return pNewObject ;
}
Transform
函數
Transform 函數是整個字符疊加處理的關鍵,再這個函數中可以捕獲需要處理的數據( RGB 格式)如下來看看具體的實現;
HRESULT CFilterTitleOverlay :: Transform ( IMediaSample * pSample )
{
// If we cann't read frame rate info from input pin's connection media type,
// We estimate it from the first sample's time stamp!
……
if ( mOverlayType != OT_NONE )
{
PBYTE pData = NULL ;
pSample -> GetPointer (& pData );
mOverlayController -> DoTitleOverlay ( pData );
}
return NOERROR ;
}
代碼中最為關鍵的 DoTitleOverlay 函數就是實現字符疊加的函數,這個函數是 COverlayController 類中的一個成員函數,如下來看看它是如何實現的;
if ( mImageHeight > mTitleSize . cy && mTitleSize . cx > 0 && mTitleSize . cy > 0)
{
……
PBYTE pStartPos = pTopLine + mStartPos . y * strideInBytes + mStartPos . x * mImageBitCount / 8;
for ( DWORD dwY = 0; dwY < ( DWORD ) mTitleSize . cy ; dwY ++)
{
PBYTE pbTitle = mTitleDIBBits + mDIBWidthInBytes * (( DWORD ) mTitleSize . cy - dwY - 1);
// Point to the valid start position of title DIB
pbTitle += ( mValidTitleRect . left >> 3);
long startLeft = mValidTitleRect . left % 8;
long endRight = startLeft + mValidTitleRect . right - mValidTitleRect . left ;
for ( long dwX = startLeft ; dwX < endRight ; dwX ++)
{
if ( !((0x80 >> ( dwX & 7)) & pbTitle [ dwX >> 3]) )
{
PBYTE pbPixel = mPixelConverter -> NextNPixel ( pStartPos , dwX - startLeft );
if ( mIsOverlayByCover )
{
// 進行 RGB 數據復值, 24 三占用三字節
mPixelConverter -> ConvertByCover ( pbPixel );
}
else
{
mPixelConverter -> ConvertByReverse ( pbPixel );
}
}
}
pStartPos += strideInBytes ;
}
}
ActualCreateTitleDIB
函數
這個函數用于創建字符位圖,創建一個 DIB 位圖的虛擬內存空間,保存 RGB 數據格式,再通過 GetDIBits 函數獲取數據緩沖區用于字符疊加之用,如下代碼;
HBITMAP COverlayController :: ActualCreateTitleDIB ( HDC inDC )
{
// DIB info we used to create title pixel-mapping.
// The system default color policy is:
// Initial Whole Black, while output area White-background and Black-text.
struct {
BITMAPINFOHEADER bmiHeader ;
DWORD rgbEntries [2];
} bmi =
{
{
sizeof ( BITMAPINFOHEADER ),
0,
0,
1,
1,
BI_RGB ,
0,
0,
0
},
{
0x00000000,
0xFFFFFFFF
}
};
……
// Set proper DIB size here! Important!
bmi . bmiHeader . biHeight = mTitleSize . cy ;
bmi . bmiHeader . biWidth = mTitleSize . cx ;
HBITMAP hbm = CreateDIBitmap ( inDC , & bmi . bmiHeader , 0, NULL , NULL , 0);
BOOL pass = ( hbm != NULL );
// Draw title after selecting DIB into the DC
if ( pass )
{
HGDIOBJ hobj = SelectObject ( inDC , hbm );
pass = ExtTextOut ( inDC , 0, 0, ETO_OPAQUE | ETO_CLIPPED , NULL ,
mTitle , lstrlen ( mTitle ), NULL );
SelectObject ( inDC , hobj );
}
// Get the title-drew DIB bits
if ( pass )
{
ReleaseTitleDIB ();
// Attention: To get bitmap data from the DIB object,
// the scan line must be a multiple of 4 (DWORD)!
// If the actual bitmap data is not exactly fit for DWORD,
// The rest of DWORD bits will be filled automatically.
// So we should expand to bytes and round up to a multiple of 4.
mDIBWidthInBytes = (( mTitleSize . cx + 31) >> 3) & ~3;
mTitleDIBBits = new BYTE [ mDIBWidthInBytes * mTitleSize . cy ];
memset ( mTitleDIBBits , 0, mDIBWidthInBytes * mTitleSize . cy );
LONG lLines = GetDIBits ( inDC , hbm , 0, mTitleSize . cy , ( PVOID ) mTitleDIBBits ,
( BITMAPINFO *)& bmi , DIB_RGB_COLORS );
pass = ( lLines != 0);
}
……
return hbm ;
}
CompleteConnect
函數
CompleteConnect 函數是用來完成 output pin 與 下一個 input pin 連接只用,也是構建 Fiters 鏈的必備函數,如下代碼;
HRESULT CFilterTitleOverlay :: CompleteConnect ( PIN_DIRECTION direction , IPin * pReceivePin )
{
HRESULT hr = CTransInPlaceFilter :: CompleteConnect ( direction , pReceivePin );
if ( SUCCEEDED ( hr ) && direction == PINDIR_INPUT )
{
hr = SetInputVideoInfoToController ();
}
return hr ;
}
屬性頁設置( CTitleOverlayProp )
這個類從 CBasePropertyPage 直接繼承的,用于配置和觀察這個 Filters 屬性之用。 CTitleOverlayProp 其實是一個窗體,類似一個應用程序,不過我們可以直接對其進行消息捕捉,如下代碼;
BOOL CTitleOverlayProp :: OnReceiveMessage ( HWND hwnd ,
UINT uMsg ,
WPARAM wParam ,
LPARAM lParam )
{
switch ( uMsg )
{
case WM_INITDIALOG :
{
// Get windows' handles
m_hOverlayType = GetDlgItem ( hwnd , IDC_COMBO_OVERLAY_TYPE );
m_hEditTilte = GetDlgItem ( hwnd , IDC_EDIT_TITLE );
m_hEditStartX = GetDlgItem ( hwnd , IDC_EDIT_STARTX );
m_hEditStartY = GetDlgItem ( hwnd , IDC_EDIT_STARTY );
m_hEditStartTime = GetDlgItem ( hwnd , IDC_EDIT_STARTTIME );
m_hEditEndTime = GetDlgItem ( hwnd , IDC_EDIT_ENDTIME );
m_hEditColorR = GetDlgItem ( hwnd , IDC_EDIT_COLORR );
m_hEditColorG = GetDlgItem ( hwnd , IDC_EDIT_COLORG );
m_hEditColorB = GetDlgItem ( hwnd , IDC_EDIT_COLORB );
break ;
}
case WM_COMMAND :
{
if ( HIWORD ( wParam ) == BN_CLICKED )
{
switch ( LOWORD ( wParam ))
{
case IDC_BUTTON_CHANGE_FONT :
OnButtonChangeFont ();
break ;
}
}
SetDirty ();
break ;
}
}
return CBasePropertyPage :: OnReceiveMessage ( hwnd , uMsg , wParam , lParam );
} // OnReceiveMessage
有了這個消息捕捉函數當然就可以直接對所需要配置的參數進行配置了。同時在繼承 CBasePropertyPage 的時候為了方便 CBasePropertyPage 還提供了其他幾個接口如 CreateInstance 、 OnConnect 、 OnActivate 等。
整個字符疊加的 Fiters 編碼就基本上完成了,其中幾個地方還是需要在次提醒,第一、對基類的選擇,一定要選擇正確的基類,這樣才能達到事半工倍。第二、處理字符疊加時一定要注意幀頻率,否則會產生錯位。第三、字符需要繪制到一段虛擬的內存當中,不能直接繪制。字符疊加的應用非常廣泛,估計暴風影音的字符疊加功能就是這樣做的!
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
