| 現(xiàn)在的圖像越來越花巧了,有浮雕、馬賽克、相框等特效,看得人眼花繚亂。本來圖像特效沒什么稀奇的,在PhotoShop等圖像處理軟件中我們早已見得多了,不過用在視頻上就令人感覺有點(diǎn)神奇。我一直都想擁有這些效果,但我的攝像頭是很早就買到的,沒福氣奢望驅(qū)動(dòng)程序給
它帶來的全新精彩。剛好我學(xué)習(xí)DirectShow有一段時(shí)間了,既為了挑戰(zhàn)自己(我從未寫過令自己感到滿意的程序),也為了檢驗(yàn)學(xué)習(xí)成果,我就下了決心
用DirectShow實(shí)現(xiàn)這些效果。幾經(jīng)努力,終于有了一些成績,我完成了其中一些效果,并發(fā)現(xiàn)程序可以用在DirectShow支持的影音文件上,又
自己把它應(yīng)用到D3D中去,感覺還不錯(cuò)。 
 先看看效果吧,以激勵(lì)斗志。我是對(duì)著攝像頭廣告中的效果圖來做程序的,我怕編程的熱情像以前那樣很快冷卻,只留下一堆亂糟糟的代碼,我需要它來不斷興奮被代碼搞得昏頭轉(zhuǎn)向的大腦。
 
 
 
    
        
            |   (圖一:幾種效果) (圖二:D3D中的攝像展示) |  編程的思路是這樣的:寫一個(gè)DirectShow的VideoRenderer Filter用于實(shí)時(shí)獲取圖像,之后在用DirectShow連接并使用攝像頭、播放視頻時(shí)用上該Filter,這樣就可以實(shí)時(shí)處理視頻的圖片并進(jìn)行顯示了。
 
 由于要用到DirectShow,我想在這里說一些關(guān)于DirectShow的初級(jí)知識(shí),初學(xué)或沒學(xué)過DirectShow的朋友就請(qǐng)耐下性子來聽我這個(gè)初學(xué)者羅嗦一陣子(要學(xué)DirectShow的朋友可以看天極的DirectShow相關(guān)文章,介紹很詳細(xì))。
 
 在新版的DX9 SDK中已看不到DirectShow的影子了(DirectShow發(fā)展到了盡頭?),不過DirectShow還可用,而且相當(dāng)有用。聽說VC.net自帶有DX8 SDK開發(fā)文件,但我仍希望您
能找到DX8 SDK和VC6。DX8
SDK幫助文件對(duì)DirectShow詳細(xì)的說明和SDK豐富的DirectShow例子對(duì)DirectShow開發(fā)是大有裨益的。我在開發(fā)Filter
過程中發(fā)現(xiàn)VC.net不能編譯通過,出現(xiàn)“InterLockedExchange
重定義”的編譯錯(cuò)誤,VC6則一切正常,我把這歸咎為微軟的問題,菜鳥的我無力解決。是了,我只用VC6編寫Filter,其它編碼使用的是
VC.net,VC.net可完成一般的DirectShow編程,我本人更喜歡VC.net的編碼環(huán)境。
 
 “請(qǐng)問Filter是什么?”這個(gè)問題在我腦中很久了,DirectShow的功能是由Filter搭建起來的,但我很久以來都被“Filter”這個(gè)詞困惑著,直到現(xiàn)在才有些眉目。當(dāng)然,我學(xué)識(shí)淺,很可能會(huì)說錯(cuò),請(qǐng)海涵。Filter在影音風(fēng)暴等軟件中稱為“濾鏡”,
在微軟中國上一些譯文把它譯為“篩選器”,五花八門的(看來沒有核心技術(shù)真的很被動(dòng))。而Filter在數(shù)字信號(hào)處理等專業(yè)課程中稱為“濾波器”!這下您
應(yīng)該知道一點(diǎn)了吧,DirectShow對(duì)視頻、音頻的處理過程就是數(shù)字信號(hào)處理的過程,可以把數(shù)字信號(hào)處理的理論應(yīng)用于此,微軟為此把這個(gè)
DirectShow部件稱為Filter。
 
 再說Sample,我把它譯為“采樣”,也就是一個(gè)數(shù)據(jù)包,可理解為攝像頭攝像或聲卡錄音時(shí)每掃描一次得到的數(shù)據(jù)、音視頻文件每一幀的數(shù)據(jù)。
 
 開發(fā)VideoRenderer Filter
 
 Filter要做以下工作:接受24bit RGB格式的圖片,這由上級(jí)Filter肢解視頻得到,并把它處理成32bit ARGB圖片,之后傳給外部函數(shù)進(jìn)行進(jìn)一步處理。
 
 我要Filter這樣工作的理由是:幾乎所有的視頻Filter都接受24bit RGB格式,不用擔(dān)心會(huì)連接失??;32bit
ARGB可以很好地支持MMX加速,如果你會(huì)用MMX的話,我在本文中會(huì)涉及一點(diǎn)MMX,不過和我水平相同,都是初級(jí)的;調(diào)用外部函數(shù)能提供更多的靈活
性,不用費(fèi)盡心思在Filter中封裝圖像處理函數(shù),可以在寫程序時(shí)隨能力和水平提高而加入新的處理函數(shù),同時(shí)也保證了能夠及時(shí)處理。
 
 怎么樣,F(xiàn)ilter要做的很少很簡單吧,與此一樣,寫一個(gè)Filter也比想象中的簡單,我們一步步地看看。
 
 新建一個(gè)簡單的DLL項(xiàng)目,設(shè)置名稱為VR,刪除VR.cpp中的DllMain函數(shù),添加VR.h和VR.def兩個(gè)文件,在VR.def中加入以下代碼,以完成函數(shù)導(dǎo)出。
 
 LIBRARY VR
 
 EXPORTS
 
 DllMain PRIVATE
 
 DllGetClassObject PRIVATE
 
 DllCanUnloadNow PRIVATE
 
 DllRegisterServer PRIVATE
 
 DllUnregisterServer PRIVATE
 
 再做些沒有創(chuàng)意的東西 —— Filter注冊、類工廠定義等,在VR.cpp中加入,我是從DirectShow的Filter例子中復(fù)制,再略加修改得來的。
 
 
 
    
        
            | #include "stdafx.h" #include "VR.h"
 #pragma comment(lib,"strmbase.lib")
 #pragma comment(lib,"winmm.lib")
 
 // Setup data
 
 const AMOVIESETUP_MEDIATYPE sudIpPinTypes =
 {
 &MEDIATYPE_Video, // MajorType
 &MEDIASUBTYPE_NULL // MinorType
 };
 
 const AMOVIESETUP_PIN sudIpPin =
 {
 L"Input", // The Pins name
 FALSE, // Is rendered
 FALSE, // Is an output pin
 FALSE, // Allowed none
 FALSE, // Allowed many
 &CLSID_NULL, // Connects to filter
 NULL, // Connects to pin
 1, // Number of types
 &sudIpPinTypes // Pin details
 };
 
 const AMOVIESETUP_FILTER sudVRAx =
 {
 &CLSID_lwVideoRenderer, // Filter CLSID /**/
 L"lwVideoRenderer", // String name /**/
 MERIT_NORMAL, // Filter merit
 1, // Number of pins
 &sudIpPin // Pin details
 };
 
 // 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
 // function when it is asked to create a CLSID_VideoRenderer object
 
 CFactoryTemplate g_Templates[] = {
 { L"lwVideoRenderer" /**/
 , &CLSID_lwVideoRenderer /**/
 , CVideoRenderer::CreateInstance
 , NULL
 , &sudVRAx },
 };
 
 int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]);
 // DllRegisterServer
 // Used to register and unregister the filter
 STDAPI DllRegisterServer()
 {
 return AMovieDllRegisterServer2( TRUE );
 } // DllRegisterServer
 
 // DllUnregisterServer
 
 STDAPI DllUnregisterServer()
 {
 return AMovieDllRegisterServer2( FALSE );
 } // DllUnregisterServer
 
 extern "C" BOOL WINAPI DllEntryPoint(HINSTANCE, ULONG, LPVOID);
 
 // DllMain
 
 BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, LPVOID lpReserved)
 {
 return DllEntryPoint((HINSTANCE)(hModule), dwReason, lpReserved);
 }// DllMain
 |  經(jīng)過一番復(fù)制后,需要增加一些簡單的業(yè)務(wù)邏輯。我們先來完成Filter的類定義,從CBaseVideoRendeer派生一個(gè)新類,重寫四個(gè)函數(shù)就可奠定這個(gè)Filter的基本功能,如下,在VR.h中加入。
 
 
 
    
        
            | #include <streams.h> 
 // 回調(diào)類定義
 
 class FunCLS
 
 {public: virtual void procFun(BITMAPINFO* pBmpInfo, BYTE* pb){return;};
 };
 
 // 回調(diào)函數(shù)指針定義
 
 typedef void (CALLBACK* pProcFun)(BITMAPINFO* pBmpInfo,BYTE* pb);
 
 // {F81331DB-2E46-43e7-8709-BE57205D8914} Filter的全局標(biāo)識(shí)符
 
 static const GUID CLSID_lwVideoRenderer =
 { 0xf81331db, 0x2e46, 0x43e7, { 0x87, 0x9, 0xbe, 0x57, 0x20, 0x5d, 0x89, 0x14 } };
 // Filter 類定義
 class CVideoRenderer : public CBaseVideoRenderer
 {
 public:
 // 創(chuàng)建進(jìn)程。
 static CUnknown * WINAPI CreateInstance(LPUNKNOWN, HRESULT *);
 // 構(gòu)造、釋構(gòu)函數(shù)
 CVideoRenderer(LPUNKNOWN pUnk,HRESULT* phr);
 ~CVideoRenderer();
 public:
 // 檢查是否有可以接受格式的數(shù)據(jù)
 HRESULT CheckMediaType(const CMediaType* pmt);
 // 設(shè)置具體的數(shù)據(jù)格式,如視頻圖像的寬、高等
 HRESULT SetMediaType(const CMediaType* pmt);
 // 遞交數(shù)據(jù),即顯示、呈現(xiàn)數(shù)據(jù)
 HRESULT DoRenderSample(IMediaSample* pMediaSample);
 private:
 BITMAPINFO m_bmpInfo; // 圖片信息
 BYTE* m_pCopyBuffer; // 復(fù)制緩沖區(qū)
 UINT m_pixelNum; // 像素點(diǎn)的數(shù)目
 FunCLS* m_pFunCLS; // 回調(diào)類指針
 pProcFun m_pPF; // 回調(diào)函數(shù)指針
 };
 |  我在上面曾提過在Filter中要在接受到新數(shù)據(jù)時(shí)調(diào)用外部函數(shù)進(jìn)行處理,因此我定義了一個(gè)回調(diào)類(我自己稱呼的)和一個(gè)回調(diào)函數(shù)指針。這樣可以把回調(diào)
類作為MFC視圖類的一個(gè)基類,以方便地使用MFC視圖類中的成員變量。而同時(shí)提供回調(diào)函數(shù)指針就可以滿足同時(shí)播放多個(gè)視頻文件、使用多個(gè)攝像頭的需要。
這是我在使用中感到有必要而后來修改得來的,使Filter的使用具有足夠的靈活性。下面看看以上Filter類中函數(shù)的具體實(shí)現(xiàn)。
 
 
 
    
        
            | //=========================================================== // 創(chuàng)建進(jìn)程。
 
 CUnknown* WINAPI CVideoRenderer::CreateInstance(LPUNKNOWN pUnk,HRESULT* phr)
 {
 return new CVideoRenderer(pUnk,phr);
 }
 
 //===========================================================
 // 構(gòu)造函數(shù)
 
 CVideoRenderer::CVideoRenderer(LPUNKNOWN
            pUnk,HRESULT *phr) : CBaseVideoRenderer(CLSID_lwVideoRenderer,"lw Video
            Renderer",pUnk,phr)
 {
 m_pCopyBuffer = NULL;
 m_pFunCLS = NULL;
 m_pPF = NULL;
 m_pixelNum = 0;
 }
 
 //===========================================================
 // 釋構(gòu)函數(shù)
 
 CVideoRenderer::~CVideoRenderer()
 {
 if(this->m_pCopyBuffer){
 delete [] m_pCopyBuffer;
 }
 }
 
 //===========================================================
 // 檢查媒體類型
 
 HRESULT CVideoRenderer::CheckMediaType(const CMediaType* pmt)
 {
 VIDEOINFO *pvi;
 // 只接受視頻
 if( *pmt->FormatType() != FORMAT_VideoInfo ) {
 return E_INVALIDARG;
 }
 // 只接受 RGB24 格式,即 R、G、B各 1 Byte
 pvi = (VIDEOINFO *)pmt->Format();
 if(IsEqualGUID( *pmt->Type(),MEDIATYPE_Video) && IsEqualGUID( *pmt->Subtype(),MEDIASUBTYPE_RGB24)){
 return S_OK;
 }
 return E_INVALIDARG;
 }
 
 //===========================================================
 // 設(shè)置媒體類型,獲取圖像的各種信息(寬、高等具體信息),處理圖像要用到
 
 HRESULT CVideoRenderer::SetMediaType(const CMediaType* pmt)
 {
 VIDEOINFO *pviBmp; // Bitmap info header
 pviBmp = (VIDEOINFO *)pmt->Format();
 memset(&m_bmpInfo,0,sizeof(BITMAPINFO)); // 清零
 m_bmpInfo.bmiHeader = pviBmp->bmiHeader;
 // 改為 32bit,因?yàn)槲視?huì)把它處理成 32bit 的
 m_bmpInfo.bmiHeader.biBitCount = 32;
 // 當(dāng)然,緩沖區(qū)大小也變了
 m_bmpInfo.bmiHeader.biSizeImage = m_bmpInfo.bmiHeader.biSizeImage * 4 / 3;
 // 開辟新 32bit 圖片的緩沖區(qū)
 if(m_pCopyBuffer){ delete [] m_pCopyBuffer;}
 m_pCopyBuffer = new BYTE[m_bmpInfo.bmiHeader.biSizeImage];
 m_pixelNum = m_bmpInfo.bmiHeader.biWidth * m_bmpInfo.bmiHeader.biHeight;
 return S_OK;
 }
 
 //===========================================================
 // 處理媒體采樣
 
 HRESULT CVideoRenderer::DoRenderSample(IMediaSample* pMediaSample)
 {
 // 獲取采樣的數(shù)據(jù)區(qū)指針,即 24bit 圖片的數(shù)據(jù)區(qū)指針
 BYTE* pb = NULL;
 pMediaSample->GetPointer(&pb);
 if(!pb){
 return E_FAIL;
 }
 // 加鎖!鎖住我要操作的數(shù)據(jù)區(qū),以防處理到一半的時(shí)候被打斷而造成錯(cuò)誤
 // 其實(shí)就是多線程編程中經(jīng)常使用的臨界區(qū)的類形式,
 // 利用構(gòu)造函數(shù)和釋構(gòu)函數(shù)來進(jìn)入和退出臨界區(qū)
 // m_RendererLock 是 CBaseVideoRenderer 的成員,繼承得來。
 CAutoLock cAutoLock(&this->m_RendererLock);
 // 把 24bit 圖片處理成 32bit
 BYTE* pb32 = m_pCopyBuffer; // 指向 32bit 緩沖區(qū)的指針
 for(UINT i = 0; i < m_pixelNum; i ++){
 pb32[0] = pb[0];
 pb32[1] = pb[1];
 pb32[2] = pb[2];
 pb32[3] = 0xff; // 0xff 即 255
 pb += 3;
 pb32 += 4;
 }
 // 如果有回調(diào)類,進(jìn)行回調(diào)處理
 if(m_pFunCLS){
 m_pFunCLS->procFun(&m_bmpInfo,m_pCopyBuffer);
 }
 // 如果有回調(diào)函數(shù),進(jìn)行處理
 if(m_pPF){
 m_pPF(&m_bmpInfo,m_pCopyBuffer);
 }
 return S_OK;
 }
 |  至此,一個(gè)簡單的 Filter 完成了,可以編譯成功、用regsvr32.exe 注冊并到GraphEdit.exe
中進(jìn)行測試了。不過如果要在程序中使用的話,您會(huì)發(fā)現(xiàn)無法設(shè)置回調(diào)函數(shù)或回調(diào)類。這個(gè)Filter 是如此的無用,除了IBaseFilter
接口的基本功能外我們從它身上得不到任何有用的東西。所以,還得給它寫個(gè)接口,讓我們可以設(shè)置一些東西。寫接口也不是難事,只要有一個(gè)接口的例子,隨便誰
都可以對(duì)照寫出一個(gè)來,我就抄寫了一個(gè)。新建一個(gè) IVRControl.h 文件,加入下面代碼。
 
 
 
    
        
            | // {244DF760-7E93-4cf0-92F4-DCB79F646B7E} 接口的 GUID 
 static const GUID IID_IVRControl = {0x244df760, 0x7e93, 0x4cf0, {0x92, 0xf4, 0xdc, 0xb7, 0x9f, 0x64, 0x6b, 0x7e}};
 
 // 接口定義
 
 DECLARE_INTERFACE_(IVRControl, IUnknown)
 {
 STDMETHOD(GetBmpInfo) (THIS_ // 方法一:獲取圖片信息
 BITMAPINFO** ppBmpInfo ) PURE;
 
 STDMETHOD(GetPointer) (THIS_ // 方法二:獲取緩沖區(qū)指針
 BYTE** ppb // 緩沖區(qū)指針的指針 ) PURE;
 
 STDMETHOD(SetFunCLS) (THIS_ // 方法三:設(shè)置回調(diào)類
 FunCLS* pFunCLS // 回調(diào)類指針 ) PURE;
 
 STDMETHOD(SetFun) (THIS_ // 方法四:設(shè)置回調(diào)函數(shù)
 pProcFun pPF ) PURE;
 };
 |  寫完接口后就要實(shí)現(xiàn)它,在VR.h 中添加 #include "IVRControl.h",而把接口作為Filter 類的一個(gè)基類,像這樣:
 
 
 
    
        
            | class CVideoRenderer : public CBaseVideoRenderer, public IVRControl |  在CVideoRenderer 類中加入接口函數(shù)和詢問接口函數(shù):
 
 
 
    
        
            | // 詢問接口,一般可以不要的,但這里需要使用接口,也重載了 STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void ** ppv);
 // 接口函數(shù)
 DECLARE_IUNKNOWN;
 STDMETHODIMP GetBmpInfo(BITMAPINFO** ppBmpInfo);
 STDMETHODIMP GetPointer(BYTE** ppb);
 STDMETHODIMP SetFunCLS(FunCLS* pFunCLS);
 STDMETHODIMP SetFun(pProcFun pPF);
 |  再在VR.cpp 中加入上述函數(shù)的具體實(shí)現(xiàn)代碼:
 
 
 
    
        
            | //=========================================================== // 詢問接口
 
 STDMETHODIMP CVideoRenderer::NonDelegatingQueryInterface(REFIID riid,void** ppv)
 {
 CheckPointer(ppv,E_POINTER);
 if(riid == IID_IVRControl){
 // 返回接口。這里有個(gè)細(xì)節(jié):返回接口時(shí),F(xiàn)ilter 的引用計(jì)數(shù)會(huì)增一,所以外部程序用完接口后也要對(duì)它進(jìn)行釋放
 return GetInterface((IVRControl*) this,ppv);
 }else{
 return CBaseVideoRenderer::NonDelegatingQueryInterface(riid,ppv);
 }
 }
 
 //===========================================================
 // 以下為接口函數(shù)的具體實(shí)現(xiàn),只是簡單的賦值
 
 STDMETHODIMP CVideoRenderer::GetBmpInfo(BITMAPINFO** ppBmpInfo)
 {
 *ppBmpInfo = &this->m_bmpInfo;
 return S_OK;
 }
 
 STDMETHODIMP CVideoRenderer::GetPointer(BYTE** ppb)
 {
 *ppb = m_pCopyBuffer;
 return S_OK;
 }
 
 STDMETHODIMP CVideoRenderer::SetFunCLS(FunCLS* pFunCLS)
 {
 m_pFunCLS = pFunCLS;
 return S_OK;
 }
 
 STDMETHODIMP CVideoRenderer::SetFun(pProcFun pPF)
 {
 m_pPF = pPF;
 return S_OK;
 }
 |  不知您注意到了沒有:接口其實(shí)就是一個(gè)虛基類。類在 C++ 等現(xiàn)代編程語言中無處不在,也沒什么好驚奇的,只是有利于更好理解。再有一個(gè),看似功能強(qiáng)大的接口可能偏偏很容易實(shí)現(xiàn),它依附于對(duì)象,它的復(fù)雜可能都隱藏在對(duì)象內(nèi)了。
 
 可以看出在接口定義中也要用到回調(diào)類和回調(diào)函數(shù)指針的定義,所以我把它們連同 Filter CLSID 的定義一起移到 IVRControl.h 文件的開頭,使用到此 Filter 時(shí)只把 IVRControl.h 這一個(gè)文件包含進(jìn)去就行了。
 
 不錯(cuò),我們已經(jīng)一步步、一個(gè)個(gè)函數(shù)的把設(shè)想中的 Filter 寫出來了,已成功完成了Filter,以 Release
模式把它編譯出來足有80多K,用 UPX 壓縮后就是30
多K。這樣把代碼鋪出來看,好像蠻多的,不過我在敲代碼時(shí)一點(diǎn)也不覺得,因?yàn)槊總€(gè)函數(shù)所做的的確很少,循著邏輯規(guī)矩、步步為營地寫真的很easy。
 
 為方便使用DirectShow而寫一個(gè)封裝類
 
   如果您使用 DirectShow 有一陣子,您一定會(huì)選擇寫一個(gè)類來封裝 DirectShow,誰也愿意只調(diào)用“PlayMovie”這些只用傳入文件名就能播放的函數(shù)來播放文件,而不想每次都作一大堆初始化和使用一大堆對(duì)象,而且類可以方便的移動(dòng)到不同項(xiàng)目中。
 我在寫這個(gè)類是遇到一個(gè)問題:怎樣使用自寫的 Filter,先注冊再使用還是不必注冊直接手工載入呢?想到綠色軟件是不往注冊表中添加多余信息的,而且要使程序能在進(jìn)行重裝系統(tǒng)等
導(dǎo)致注冊表信息丟失的操作后仍無需重裝而正常運(yùn)行,因此我選擇了手工方式。這樣做也不難,只是模擬一遍 COM 的載入,也就是
CoCreateInstance 工作流程的模擬。我不知道高手們是怎樣做的,這里只是我的做法,流程如下:用 LoadLibrary
載入VR.dll,使用 GetProcAddress 查找 GetClassObject 函數(shù)的地址,再調(diào)用 GetClassObject
得到類工廠,然后用類工廠詢問并得到 Filter。
 
 先看一個(gè)可以從已載入的 dll 得到對(duì)象的函數(shù):
 
 
 
    
        
            | // 定義 DllGetClassObject 函數(shù)的指針 
 typedef HRESULT (CALLBACK *lpDllGetClassObject)(REFCLSID,REFCLSID,void**);
 HRESULT lwGetClassObject(HMODULE hLib,const CLSID& clsid,const CLSID& riid,void** ppv)
 {
 // 裝入和卸載dll由用戶執(zhí)行,以免后來忘記了卸載dll
 HRESULT hr;
 if(!hLib){
 // dll沒有裝載成功
 hr = E_FAIL;
 return hr;
 }
 lpDllGetClassObject lwDGCO = NULL;
 lwDGCO =(lpDllGetClassObject)GetProcAddress(hLib,"DllGetClassObject");
 if(!lwDGCO){
 // 查找函數(shù)失敗
 hr = E_FAIL;
 return hr;
 }
 IClassFactory* pCF = NULL;
 hr = lwDGCO(clsid,IID_IClassFactory,(void**)&pCF);
 if(!pCF){
 // 獲取類廠失敗
 return hr;
 }
 hr = pCF->CreateInstance(NULL,riid,ppv);
 if(!ppv) hr = E_FAIL;
 pCF->Release();
 return hr;
 }
 |  我在類中作了如下這些定義:
 
 
 
    
        
            | // 類成員的定義,要包含“IVRControl.h”文件 
 IGraphBuilder* m_pGB; // Filter Graph
 IBaseFilter* m_pFVR; // 自寫的 Filter
 IVRControl* m_pVRControl; // 接口
 HMODULE m_hLib; // dll 庫的句柄
 |  在類的構(gòu)造函數(shù)中載入 dll:
 
 
 
    
        
            | m_hLib = LoadLibrary("VR.dll"); // 載入 dll,“VR.dll”要和程序在同一目錄 |  之后在類的成員函數(shù) InitDS 中做獲取 Filter 等工作:
 
 
 
    
        
            | HRESULT hr; // 創(chuàng)建 Filter Graph
 hr = CoCreateInstance(CLSID_FilterGraph,NULL,CLSCTX_INPROC,IID_IGraphBuilder,(void**)&m_pGB);
 if(FAILED(hr)){
 return hr;
 }
 
 // 調(diào)用上面的函數(shù)從已載入的 dll 中獲取自寫的 Filter
 
 hr = lwGetClassObject(m_hLib,CLSID_lwVideoRenderer,IID_IBaseFilter,(void**)&m_pFVR);
 if(FAILED(hr)){
 return hr;
 }
 
 // 詢問接口
 
 hr = m_pFVR->QueryInterface(IID_IVRControl,(void**)&m_pVRControl);
 
 if(FAILED(hr)){
 return hr;
 }
 
 // 把 Filter 加入 Filter Graph
 
 hr = m_pGB->AddFilter(m_pFVR,L"VideoRenderer");
 
 if(FAILED(hr)){
 return hr;
 }
 |  最后在類的釋構(gòu)函數(shù)中卸載dll。本來我在一本2000年版的《COM 精髓》中看到要先調(diào)用 dll 的 DllCanUnloadNow
函數(shù)確定是否應(yīng)卸載才實(shí)行卸載的,不過試驗(yàn)中,特別是使用多個(gè)類播放幾個(gè)不同文件時(shí)發(fā)現(xiàn)就這樣直接卸載就行,用 DllCanUnloadNow
作為卸載條件反而不能正確卸載。
 
 
 
    
        
            | if(m_hLib){ // 確認(rèn)成功載入了 dll
 FreeLibrary(m_hLib);// 卸載 dll
 }
 |  以上的代碼有點(diǎn)亂,請(qǐng)對(duì)照流程耐心的看一下,我已作了詳細(xì)的注釋,應(yīng)該不難看懂。完成這些后,要播放視頻文件只需
m_pGB->RenderFile(L”c:\\sample.wmv”,0); 就可以建立“Filter Chain”了,再用
m_pMC->Run(); 就可開始播放(m_pMC 為 IMediaControl對(duì)象),完全是最一般的 DirectShow
操作,攝像頭控制也一樣,我不多貼代碼了,您可到我拙劣的程序中看。這兒要提的是接口,在上面代碼中如無意外我們已經(jīng)獲得了 Filter
的接口,不過到目前為止這個(gè)接口只通到此 DirectShow
類中,也就是只能在此類中使用,我們還得把它導(dǎo)通到類外以便設(shè)置函數(shù),它的傳遞代碼如下,僅以 SetFun 為例:
 
 
 
    
        
            | HRESULT CDSControl::SetFun(pProcFun pPF) {
 if(m_pVRControl){
 return m_pVRControl->SetFun(pPF);
 }
 return E_FAIL;
 }
 |  圖像處理
 
 在我的程序中圖像處理函數(shù)是作為 DirectShow 封裝類一部分的,我認(rèn)為這樣便于移動(dòng)和使用。沒有連著上面的 DirectShow 類而另外寫標(biāo)題是因?yàn)槲矣X得有必要把它提到重要位置。
 在程序完善階段您的工作基本就在這里了,除了花心思構(gòu)造D3D環(huán)境外幾乎所有效果都要在這里實(shí)現(xiàn),水平高下也體現(xiàn)于此。 在網(wǎng)上可以找到很多圖像特效的代碼和解說,我結(jié)合編程過程再說說。
 1. 訪問緩沖區(qū)的麻煩。
 
 這是最麻煩的,二維圖像在這里以一個(gè)連續(xù)的一維緩沖區(qū)呈現(xiàn),您要靠一個(gè)指針去訪問它,怎么辦呢?先弄懂 Pitch,例如 32bit ARGB
圖像,每個(gè)象素就占用 4 Byte 內(nèi)存空間(1 Byte = 8 bit),對(duì)于寬度為 20 像素的圖像,它的 Pitch 就是 80
Byte,即每一行占用的內(nèi)存。按 x 、y 坐標(biāo)就有如下公式(按 Byte 計(jì)算):
 
 B:y * Pitch + x * 4
 G:y * Pitch + x * 4 + 1
 R:y * Pitch + x * 4 + 2
 A:y * Pitch + x * 4 + 3
 
 可以看出在內(nèi)存中是按 BGRA 存儲(chǔ)的,我不明白為什么這樣,可能可以從計(jì)算機(jī)的內(nèi)存存儲(chǔ)方式找到答案。上述公式計(jì)算多,效率較低,在實(shí)際使用中應(yīng)適時(shí)作有益的改變。
 
 2. 浮雕。
 
 到目前為止我在網(wǎng)上找到的幾篇文章都說把一點(diǎn)的值減去其右下角點(diǎn)的值再加上128就行了。為什么要減去右下角的點(diǎn)呢?為什么要加上128呢?原來浮雕是
要把圖像的變化部分突出顯示出來,而把相同部分淡化,所以用一點(diǎn)減去其鄰域任意點(diǎn)都可以達(dá)到這個(gè)目的,倒也不一定是右下角的點(diǎn),包圍著它的八個(gè)點(diǎn)都可以,
甚至可以選擇減去更遠(yuǎn)的點(diǎn),只要規(guī)則明確、效果好就行。在相減后點(diǎn)的 RGB
值都減小了,大多接近黑色,黑乎乎一片的看不出什么來,一點(diǎn)也不像浮雕,所以要給它們都增加一個(gè)相同的亮度,通常加上128,其它的值,例如64、
100,當(dāng)然也行,一切都以實(shí)際效果為準(zhǔn)。說到效果,上面所說的RGB相減會(huì)造成浮雕有一些色點(diǎn),解決方法是計(jì)算兩點(diǎn)的亮度之差,RGB都賦值為亮度差,
畫面就沒色點(diǎn)了,因?yàn)橐呀?jīng)變成灰度圖了。亮度公式是 Brightness = 0.3 * R + 0.6 * G + 0.1 * B,其中G
的比重最大,可以近似的用 G 作為亮度,在RGB各自的分量圖中也可以明顯看出 G 分量的圖最亮,簡單的把 G 的值賦給 R 和 B
就得到灰度圖了,這可以減少計(jì)算,提高速度。后來我還看到這樣的話句,“用3 * 3
的小塊做的浮雕效果更好”,不過我不知道怎么用,可能這樣就可以實(shí)現(xiàn) PhotoShop 那樣更好的浮雕效果。
 
 原理是這樣了,到了編程卻是另外一回事:能夠把規(guī)則、數(shù)學(xué)公式轉(zhuǎn)換為程序也是能力的一種體現(xiàn)。如果要減去右下角的點(diǎn),那么最右一列和最后一行是要特殊處
理的,否則肯定會(huì)發(fā)生內(nèi)存訪問錯(cuò)誤,想一想就知道為什么;如果要減去左邊的點(diǎn),第一列也要特殊處理,請(qǐng)問第一列的點(diǎn)到哪里找它左邊的點(diǎn)呢?不要小視此問
題,它會(huì)令你訪問內(nèi)存時(shí)遇到一些問題。
 
 3. 鉛筆畫
 
 鉛筆畫原理和浮雕差不多,也是亮度相減,認(rèn)為變化大的是邊緣,然后設(shè)置一個(gè)閥值,例如差值大于8,則把該點(diǎn)設(shè)為黑色(0,0,0),要不設(shè)為白色(255,255,255)。閥值、色彩都可任意設(shè)置,沒人要您拘束就不要忸忸怩怩的不敢改動(dòng)。
 
 按照此方法得到的效果實(shí)在不怎么樣,可惜我不是研究圖像的料,對(duì)數(shù)據(jù)的處理能力很差,同樣一幅在專家手中可以玩出很多花樣的圖片,淪落到我手上也只能飲
恨屈膝投降無奈了。這是我看了一些圖像處理方面資料和書籍所發(fā)的呆嘆,圖像處理實(shí)在太精深了,既要數(shù)學(xué)、物理知識(shí)雄厚,又要腦子靈活能東移西就把各種知識(shí)
綜合運(yùn)用,不然就只好望洋興嘆。
 
 4. 加亮、對(duì)比度等。
 
 首先悲痛的說明,我曾努力的要實(shí)現(xiàn)色度、飽和度的調(diào)
整,知道是要把 RGB 轉(zhuǎn)換成 HLS
之類的顏色空間才能實(shí)現(xiàn),也找到了一些它們之間轉(zhuǎn)換的說明和轉(zhuǎn)換函數(shù),可惜看不明白,或者說那些材料根本不打算讓我明白!這不單是氣話,而且事實(shí),我真的
十分氣憤:怎么能夠在前面鋪了一大堆“效果圖”說了一大堆廢話然后給個(gè)只有幾行無大用的注釋的代碼就可以呢?!盡管如此憤概,我還是乖乖的抄了程序,希冀
能發(fā)揮作用,結(jié)果卻是失望:不僅效率低下,而且在調(diào)整了飽和度的同時(shí)使圖像出現(xiàn)不協(xié)調(diào)的彩色方塊。由于不知道原理,無法改動(dòng),于是我放棄了它。
調(diào)整亮度很簡單,例如要加亮10,把RGB 都加上 10 就可以了,減亮就減10。
 
 對(duì)比度調(diào)整也不難,書上說是要令亮點(diǎn)更亮、暗點(diǎn)
更暗,好像是要找出亮點(diǎn)來增亮、找出暗點(diǎn)加暗,其實(shí)不然,把所有點(diǎn)都乘以一個(gè)數(shù),把亮暗點(diǎn)的差距拉大或減小就能調(diào)整對(duì)比度了。把圖像原來的對(duì)比度定為
1,要增大對(duì)比度就調(diào)整為 1.3 、2 等大于1 的數(shù),把每個(gè)點(diǎn)的 RGB 都乘以它,就行了,要降低就把數(shù)值設(shè)為 0 至 1 的數(shù)。只要注意保持
RGB 的值在 0 ~ 255 中即可。
 
 5. 馬賽克
 
 馬賽克效果就是把圖像分解成 m * n 個(gè)小塊 或長寬為 x 、y 的小塊,用小塊內(nèi)的某點(diǎn)顏色作為整塊的顏色,通常用左上角的顏色。
 
 動(dòng)起手編程會(huì)很麻煩,要定位到每小塊的左上角,才能改變塊內(nèi)的顏色,因此要用很多循環(huán),我在代碼中就用了12個(gè)循環(huán)!除此,還有邏輯麻煩,拿分成寬高為
x、y 的小塊這種情況為例,您不能保證圖像的長寬剛好都是
x、y的倍數(shù),很多時(shí)會(huì)余出一些“邊角料”,這就是麻煩,不可能舍棄它們不進(jìn)行處理,因?yàn)楹苡绊懶Ч?。因此如圖所示,要先處理藍(lán)色的倍數(shù)部分,再處理綠色
的寬度上余下部分,處理紅色的高度上余下部分,還有黃色的寬高夾縫的小塊。
 
 
   
 除了這種長方形的處理,還可以試驗(yàn)上圖菱形等形狀,當(dāng)然,您要付出很大的勞動(dòng),而我沒能做到這些。
 
 6. MMX。
 
 記得在上面我說過會(huì)在文章中涉及一點(diǎn)MMX,不妨在這里涉及。在 VC
中可以鑲?cè)雲(yún)R編使匯編變得很容易,完全不是純匯編代碼所能相比的,所以不用怕匯編,可以先用 C
語言寫出實(shí)現(xiàn)代碼,再用匯編“翻譯”過來。如果譯不出來,更加可以把代碼中斷一下,讓 VC 反匯編,看 VC
的匯編代碼,再行改進(jìn),為什么不行呢,有人用槍指著您么?記住哦,如果沒辦法改進(jìn)就放棄匯編,不要做多余的事。其實(shí)要用 MMX
也不一定非用匯編不可,VC 也提供了 MMX 的 C++ 封裝,學(xué)習(xí)后可用它,我則懶于學(xué)習(xí)。
 
 MMX 最大的好處是可以自動(dòng)保證處理的值范圍為0 ~ 255,節(jié)省判斷,而且MMX寄存器是64位的,一次可處理32bit圖像的兩個(gè)點(diǎn)。其它的我也不太懂,您可參考相關(guān)資料。
 
 下面列出浮雕效果代碼,它是減去右邊點(diǎn)的,由于不進(jìn)行行列判斷,每行最后一點(diǎn)減去的是下一行的首點(diǎn)。
 
 
 
    
        
            | __int64 Mask = 0x8080808080808080; // 0x80 = 128,就是亮度的增加值 
 UINT a = bmpBufferLen >> 3; // 緩沖區(qū)長度(按 BYTE 計(jì)算)除以 8(兩個(gè)點(diǎn)的大?。?,計(jì)算要循環(huán)的次數(shù)
 
 _asm{
 
 mov esi,pIn; // 要處理的緩沖區(qū)指針
 
 mov edi,pOut; // 結(jié)果緩沖區(qū)指針
 
 mov eax,a; // 循環(huán)次數(shù)
 
 dec eax; // 循環(huán)次數(shù)減一,因?yàn)樽詈髢牲c(diǎn)沒法減,可以在后面特殊處理,這里不作處理
 
 movq mm1,Mask; // 增加值,movq 是 MMX 的專用匯編指令,請(qǐng)找資料看
 
 _loop: // 循環(huán)
 
 mov ecx,esi; // ecx 存儲(chǔ)右邊點(diǎn)的指針
 
 add ecx,4; // 只加 4 就跳過一點(diǎn)到右邊點(diǎn)了
 
 movq mm0,[esi]; // 移動(dòng)要處理的兩點(diǎn)的值到 MMX 寄存器
 
 movq mm2,[ecx]; // 移右邊兩點(diǎn)的值
 
 psubusb mm0,mm2; // 相減
 
 paddusb mm0,mm1; // 加上增加值
 
 movq [edi],mm0; // 移到結(jié)果緩沖區(qū)
 
 add esi,8; // 移動(dòng)到下兩點(diǎn)
 
 add edi,8; // 同上
 
 dec eax; // 循環(huán)計(jì)數(shù)減一
 
 jnz _loop; // 不為零就繼續(xù)循環(huán)
 
 emms; // 結(jié)束 MMX 使用
 
 }
 |  7.來點(diǎn)高級(jí)的,用攝像頭控制鼠標(biāo)!
 
 看著這個(gè)有點(diǎn)神奇吧,其實(shí)比什么都要簡單。先做好“硬件準(zhǔn)備”:把攝像頭如圖擺放,鏡頭下方放張白紙以使圖像中物體界限分明。
 
 
  
 軟件方面,把圖片作閥值處理—— B 值大于 128 的設(shè)為黑色,其它的設(shè)為白色。因?yàn)榘准埖淖饔?,您的手或其他物體會(huì)在閥值圖中顯示為白色,如下圖,再找出圖中紅點(diǎn),即第一點(diǎn)白點(diǎn)的在圖中位置(x,y),再把圖的坐標(biāo)影射到屏幕坐標(biāo)就行了。
 
 
    
 下面要討論具體做法。先解決圖像坐標(biāo)問題。我獲取第一點(diǎn)白點(diǎn)的程序如下:
 
 
 
    
        
            | void CDSControl::GetMousePos(BYTE* pb,int *xPos,int* yPos) {
 int x,y;
 BOOL mouseFound = FALSE;
 for(y = 0; y < m_bmpHei; y ++){
 for(x = 0; x < m_bmpWid; x ++){
 if(pb[0] == 255){ // 因?yàn)榘咨珵椋?55,255,255),判斷一個(gè)255 即可
 pb[2] = 255; // 設(shè)為紅色,別忘了 BGRA 的內(nèi)存排列方式
 pb[1] = pb[0] = 0; //
 // 計(jì)算坐標(biāo)
 *xPos = ScreenWid - x * ScreenWid / m_bmpWid;
 *yPos = y * ScreenHei / m_bmpHei;
 mouseFound = TRUE;
 break;
 }
 pb += 4;
 }
 if(mouseFound){
 break;
 }
 }
 }
 |  可以看出我是從所得的圖像緩沖區(qū)的第一點(diǎn)開始檢索的,這也是攝像頭掃描 CCD 的順序,看上面右邊的圖,那是右手的圖像,可以推想出攝像頭的掃描順序如下圖所示:
 
 
 
    
        
            |   (攝像頭掃描坐標(biāo)) (與屏幕坐標(biāo)(藍(lán))相對(duì)的攝像頭坐標(biāo)(紅)) |  而相對(duì)于屏幕坐標(biāo)很容易得到上面右圖,說明屏幕坐標(biāo)以左上角為原點(diǎn),x的正方向?yàn)橛?,而攝像頭坐標(biāo)以右上角為原點(diǎn),x的正方向?yàn)樽螅簿褪钦f我按順序?qū)ふ宜玫降淖鴺?biāo)值是這樣的:y 與屏幕坐標(biāo) y 相符,x 則與屏幕坐標(biāo) x 剛好相反。因此推算出鼠標(biāo)位置應(yīng)該是:
 
 
 
    
        
            | *xPos = ScreenWid - x * ScreenWid / m_bmpWid; *yPos = y * ScreenHei / m_bmpHei;
 |  其中 ScreenWid 、ScreenHei 分別是屏幕的寬和高,用 ScreenWid =
GetSystemMetrics(SM_CXSCREEN) 和 ScreenHei =
GetSystemMetrics(SM_CYSCREEN) 得到,而m_bmpWid、m_bmpHei 當(dāng)然是圖片的寬和高了。得到鼠標(biāo)坐標(biāo)后再用
SetCursorPos
設(shè)置即可,只不過鼠標(biāo)晃動(dòng)會(huì)比較厲害,這與圖像噪音有關(guān),可能先做個(gè)柔化處理會(huì)好一點(diǎn),但鑒于攝像頭的攝像質(zhì)量,我不想作無謂的掙扎,您不會(huì)真的想用攝像
頭代替鼠標(biāo)吧?!
 
   在使用前應(yīng)該調(diào)整二值圖的閥值,使整個(gè)圖都變成黑色,保證能正確濾除干擾,不然在按下“鼠標(biāo)控制”按鈕后您的鼠標(biāo)就不會(huì)聽話,您會(huì)無法控制好它。請(qǐng)問沒有鼠標(biāo)的幫忙您將如何關(guān)閉程序?對(duì)了,“Alt + F4”,別忘了,否則您得硬著動(dòng)手把攝像頭拔掉??!
 可以說控制鼠標(biāo)真的很容易實(shí)現(xiàn),不過效果出奇的不錯(cuò),這種好像無影無蹤的控制方式相當(dāng)令人驚奇,記得我的大哥看程序時(shí)對(duì)我前面的圖像處理沒有一絲反應(yīng),
看到這個(gè)卻大大的驚奇!呵呵。如果您有興趣的話可以在此方面做更多的試驗(yàn),例如可以把手裁剪出來,讓它參加撥動(dòng)一個(gè)小球等游戲,只要您的幾何過關(guān)、有毅力
就可以實(shí)現(xiàn)。
 
 8.更實(shí)用的數(shù)字減影技術(shù)
 
 
 請(qǐng)看上面三幅圖,左邊的是先存儲(chǔ)一幅背景圖然后把手放到攝像頭前攝像,用攝到的圖片減去背景圖得到的圖;中圖是根據(jù)左圖把手的顏色設(shè)為原來顏色得到
的;右圖是不斷用新圖片減去上一幅圖得到的手移動(dòng)痕跡圖。這充分顯示了數(shù)字減影的功用:能夠從背景分解物體和偵測到物體的運(yùn)動(dòng)。
 
 如此說來此技術(shù)在安保方面的應(yīng)用會(huì)很突出,像上面右圖那樣不斷減去上一幅圖片,當(dāng)減影后得到的圖片差別大于某一程度的點(diǎn)多到一定數(shù)值時(shí)就說明有情況發(fā)生,這時(shí)候就提醒保安工作,彌補(bǔ)保安的人為失誤,也可以在此時(shí)啟動(dòng)錄像錄取有價(jià)值的情況。請(qǐng)看程序:
 
 
 
    
        
            | void CDSControl::DNS(BYTE* pIn,BYTE* pReduce,BYTE* pOut) {
 // pIn 新圖的數(shù)據(jù)區(qū)指針,pReduce 背景指針,pOut 存儲(chǔ)區(qū)指針
 if(!pReduce) return; // 沒有背景圖就不處理
 int differentPoint = 0;
 for(int i = 0; i < m_bmpBufferLen; i ++){
 // m_bmpBufferLen 為數(shù)據(jù)區(qū)長度
 pOut[i] = abs(pIn[i] - pReduce[i]); // 相減,取差值的絕對(duì)值
 if(pOut[i] > 32){
 // 相差大于 32 就認(rèn)為是不同的點(diǎn),此值因攝像頭而異,與噪音有關(guān),請(qǐng)自行試驗(yàn)
 differentPoint ++; // 不同點(diǎn)增加
 pOut[i] = pIn[i]; // 把不同點(diǎn)賦回它的顏色 }
 if(differentPoint > 200){
 // 不同點(diǎn)大于 200 就認(rèn)為有情況,應(yīng)適當(dāng)改變
 // 調(diào)用警報(bào)等……
 }
 }
 }
 |  當(dāng)然了,我可舍不得整天整夜開著電腦守著我睡覺,只是試驗(yàn)這項(xiàng)技術(shù)獲取了卻罷了。
 
 9. 廣闊的圖像處理天地
 
 因?yàn)閷懗绦虻男枰惨驗(yàn)闈夂竦呐d趣,我在此段期間找了不少圖像處理的資料,不過正如前面說過的,圖像處理需要數(shù)學(xué),大多資料都有一大堆公式,看不看得
懂就得看您的造詣了。雖然我看不懂那些公式,但我也得到了很多有益的啟示,它們是我從更多更新的角度去看待圖像,改變了我的思維方式,例如圖像是平面的,
但可以把其RGB 分量看作是高度,使圖形呈現(xiàn)“立體模式”,從而可以對(duì)它應(yīng)用立體幾何的方法。
 
 看看我理解的線性放縮吧,這也是下面 D3D 應(yīng)用的鋪墊。
 
 
 如上圖,很容易寫出此直線段的方程 y = 2 * x (0<= x <=
10)。我不知道您是怎么理解此方程的,我自己認(rèn)為以前一點(diǎn)也不理解“映射”這個(gè)概念,現(xiàn)在從線性放縮中明白到:x 被映射到 y 上,長度被拉長了 2
倍。這和單單知道方程是兩回事,我認(rèn)為這比原來理解要好。利用這個(gè)映射就可以把 11 個(gè)像素點(diǎn)(下標(biāo)為 0 ~ 10)的圖像放大為 21
個(gè)像素點(diǎn)大小,使用下面程序:
 
 
 
    
        
            | for(y = 0; y < 21; y ++){ x = y / 2;
 newPicture[y] = oldPicture[x];
 }
 |  就這樣把圖像的寬度擴(kuò)大后再擴(kuò)大高度就可完成整個(gè)圖像的放大,縮小也一樣,把直線段的斜率減小就行。直線段代表線性放大,那么拋物線等曲線呢,分段曲線呢?它們都可以實(shí)現(xiàn)放縮,因?yàn)槎际?x 到 y 的映射,曲線代表的是映射規(guī)則。呵呵,都怪中學(xué)沒學(xué)好!
 
 在實(shí)際放縮時(shí)不可能都除得整數(shù),這時(shí)就有兩種解決方法,一是插值,上面的右圖就是線性插值的示意圖,二是取整數(shù)部分,也就是最近點(diǎn)法,這兩種方法的效果和運(yùn)算量大家都明瞭,自不必我說。 此放縮原理還可以應(yīng)用于灰度拉伸等方面,對(duì)比度增減就是一個(gè)例子。
 
 現(xiàn)在攝像頭熱門的人臉跟蹤也是應(yīng)用圖像處理的,其它高級(jí)的物體識(shí)別、運(yùn)動(dòng)分析等都是基于圖像處理,其應(yīng)用前景十分廣闊,您足可投身暢游其中,只要您有足夠的興趣和知識(shí)。使用圖像處理時(shí)您是否有這樣一個(gè)想法:軟件讓硬件價(jià)值倍增!
 
 應(yīng)用到D3D中去
 平面的圖像、影片看的多了,我們不妨到3D 環(huán)境中看看影片。
 
 我不會(huì)在這里介紹 D3D,您要學(xué)習(xí)它就得自己找資料,這里只是講在 3D 環(huán)境中播放的關(guān)鍵—— 圖片到紋理。
 
 3D 紋理有一個(gè)特點(diǎn):寬高都必須是 2 的倍數(shù)。您是知道的,通常影像都是 320 * 240 等大小的,把這個(gè)寬高傳入創(chuàng)建得到的紋理卻是
512 * 256
大小的。所以非得把影像圖片拉伸到紋理不可,不然您得到的紋理會(huì)有一片黑色,談不上美觀,盡管我的審美能力讓我不覺得黃金分割很美麗,但我敢肯定這樣的紋
理很丑陋。在我的程序中利用了最近點(diǎn)法把圖像拉伸為紋理大小。不過這樣也引起圖像寬高比改變,造成一定程度的扭曲,您可自寫個(gè)程序把圖像保持寬高比拉伸。
請(qǐng)看看如何動(dòng)態(tài)改變紋理。
 
 
 
    
        
            | HRESULT d3d::SetTex(BYTE* pb) {
 if(!pTexture) return E_FAIL; // 紋理創(chuàng)建不成功
 if(!pb) return E_FAIL; // 指針錯(cuò)誤
 // 鎖定紋理
 D3DLOCKED_RECT d3dlr;
 if (FAILED(pTexture->LockRect(0, &d3dlr, 0, 0)))
 return E_FAIL;
 BYTE* pTexBits = (BYTE*)d3dlr.pBits; // 取紋理數(shù)據(jù)區(qū)指針
 UINT texPitch = d3dlr.Pitch; // 紋理的 Pitch
 UINT bmpPitch = bmpWid * 4; // 圖片的 Pitch
 
 float xStep = float(bmpWid - 1) / float(texWid - 1); //
 float yStep = float(bmpHei - 1) / float(texHei - 1); //
 
 BYTE* pNewBits = pTexBits;
 BYTE* pOldBits = pb;
 
 BYTE* pNewPixel;
 BYTE* pOldPixel;
 // 最近點(diǎn)放大
 for(int y = 0; y < texHei; y ++){
 pOldBits = pb + int(yStep * y) * bmpPitch; // 定位 y
 pNewBits = pTexBits + y * texPitch;
 for(int x = 0; x < texWid; x ++){
 pPixel = pOldBits + 4 * int(xStep * x);// 定位 x
 pNewPixel = pNewBits + 4 * x;
 pNewPixel[0] = pOldPixel[0];
 pNewPixel[1] = pOldPixel[1];
 pNewPixel[2] = pOldPixel[2];
 pNewPixel[3] = 255;// 紋理的 alpha 值,如果啟用透明,可更改實(shí)現(xiàn)透明效果
 }
 }
 // 解鎖紋理
 
 if (FAILED(pTexture->UnlockRect(0)))
 return E_FAIL;
 return S_OK;
 }
 |  其中 pTexture 是專為影像而設(shè)置的紋理,其他不明來歷的變量也是 d3d 類的成員,在下面函數(shù)中被賦值。
 
 
 
    
        
            | HRESULT d3d::CreateTex(int wid,int hei) {
 // 根據(jù)傳入的寬高創(chuàng)建紋理
 if(FAILED(D3DXCreateTexture(this->m_pd3dDevice,wid,hei,1,0,D3DFMT_A8R8G8B8,D3DPOOL_MANAGED,&pTexture))){
 return E_FAIL;
 }
 
 // 紋理描述
 
 D3DSURFACE_DESC ddsd;
 if ( FAILED(pTexture->GetLevelDesc( 0, &ddsd ) ) ) {
 return E_FAIL;
 }
 
 // 核對(duì)紋理格式,規(guī)定為 A8R8G8B8 的 32bit ARGB 格式
 
 if(ddsd.Format != D3DFMT_A8R8G8B8){
 pTexture->Release();
 pTexture = NULL;
 return E_FAIL;
 }
 texWid = ddsd.Width; // 紋理寬
 texHei = ddsd.Height;// 紋理高
 bmpWid = wid; // 圖片寬
 bmpHei = hei; // 圖片高
 return S_OK;
 }
 |  我不想每次使用紋理時(shí)創(chuàng)建一個(gè)新的,每次渲染后就釋放它,這會(huì)影響性能,所以在使用中先在恰當(dāng)?shù)牡胤?,例如按下播放按鈕后就創(chuàng)建紋理,然后在有新影像圖
片到來時(shí)更新紋理。我用的是Direct3D8,并非Direct3D9,因?yàn)槲业?GF4(我很想它消失了,可嘆袋中空空如也)運(yùn)行 D3D9
很慢,簡單的場景用 D3D8 也足夠了。附加一句,美妙的場景可使影片播放增色不少。
 
 另外,我也試過在OpenGL中使用影片紋理,不過我的方法使 CPU 占用率達(dá) 100%,但有一點(diǎn)可以肯定,這同樣可應(yīng)用于OpenGL。
 
 敬告:如果您有興趣讀我的程序,請(qǐng)不要試圖通過看我的 d3d 類來學(xué)習(xí) Direct3D,那只是我前段時(shí)間為了學(xué) D3D
而搭的框架類,有很多不規(guī)范的地方,而且 3D
物體更是一團(tuán)糟,所寫的代碼是臨時(shí)性的,根本沒考慮可讀性,現(xiàn)在我見到它們也很頭痛,不想修整。所以為了不令您對(duì) D3D
產(chǎn)生不好的印象,也為了保持您對(duì) 3D 世界探索的熱情,請(qǐng)另行找資料系統(tǒng)學(xué)習(xí) D3D。對(duì)于我的程序,您只要知道我在哪里使用了 d3d
類的哪些功能來實(shí)現(xiàn)效果就行了,切記!
 
 程序的效率和其他問題
 1. 性能
 
 在DS
封裝類中我寫了幾個(gè)GDI畫圖函數(shù),可以比較方便的顯示圖片,不過效率低下,我曾發(fā)現(xiàn)它的效能竟比D3D畫 3D場景還差。這是GDI
的問題,大概它沒有很好利用顯卡而依賴CPU的緣故吧。所以我在使用 D3D 的時(shí)候順便用上了 ID3DXSprite 接口,它是在沒有
DirectDraw的情況下很好的2D 畫圖工具,而應(yīng)用了DX
的特性使它效率很高,充分使用顯卡加速,很容易做到旋轉(zhuǎn)、縮放、透明等功能,效率很高。不過應(yīng)注意一個(gè)問題:通常看到的D3D
初始化程序都是為得到最大的3D 效能,即使沒干什么CPU占用率也會(huì)直上到100%,而播放視頻文件一般都需要解碼,CPU 都讓D3D
占去后就沒法流暢解碼視頻,所以應(yīng)該這樣創(chuàng)建D3D 設(shè)備,完成初始化:
 
 
 
    
        
            | m_pD3D->CreateDevice(D3DADAPTER_DEFAULT,D3DDEVTYPE_HAL, hWnd,D3DCREATE_SOFTWARE_VERTEXPROCESSING|D3DCREATE_MULTITHREADED,&d3dpp,&m_pd3dDevice)
 |  一定要加上一個(gè)D3DCREATE_MULTITHREADED,讓D3D 解箍 CPU,D3D不忙的時(shí)候降至 0%,當(dāng)然了,3D 效能會(huì)減掉一些,不過效果尚可。
 
 還有一個(gè),我發(fā)現(xiàn)程序運(yùn)行需要占很多內(nèi)存,但最小化后再打開就只占原來的一半左右,十分想不通。
 
 2.程序錯(cuò)誤的修正
 
 為了程序正確運(yùn)行,還要解決另一件事。您還記得回調(diào)函數(shù)嗎?就是它惹起了禍?;卣{(diào)函數(shù)帶來了靈活性的同時(shí)也帶來了災(zāi)難。到目前為止我發(fā)現(xiàn)了兩點(diǎn)。一是不
能在里面調(diào)用DirectShow 對(duì)象,例如在里面調(diào)用 IMediaPosition 來設(shè)置進(jìn)度條,這樣當(dāng)按下“停止”時(shí)會(huì)發(fā)現(xiàn)
IMediaControl 的Stop
方法不能返回,我認(rèn)為是遞歸調(diào)用所引起的,因此絕對(duì)不能這樣做,無法在Filter內(nèi)采取任何措施防止這種情況。
 
 第二點(diǎn)直接體現(xiàn)靈活
性弱點(diǎn)。想想我需要在回調(diào)函數(shù)中干些什么,我需要調(diào)用函數(shù)設(shè)置D3D紋理吧,那我就要處理紋理在Filter還運(yùn)作過程中發(fā)生參數(shù)改變或被釋放的問題,在
播放過程中打開新文件或關(guān)閉程序都有可能發(fā)生這種情況。打開新文件時(shí)我們會(huì)先釋放舊的Filter
Graph再重建,釋放并創(chuàng)建新的紋理會(huì)在啟動(dòng)播放后才進(jìn)行,因?yàn)橐玫揭曨l參數(shù),所以有可能Filter已經(jīng)運(yùn)作了而使用的紋理還是舊的,引起訪問錯(cuò)
誤。DirectShow 的Filter Chain 有自己的線程,調(diào)用IMediaControl 的Stop
并不能讓它一下子停下來,突然關(guān)閉程序就有可能使紋理釋放比真正結(jié)束播放早,因?yàn)槲覀儾粫?huì)在回調(diào)函數(shù)中管理紋理而在D3D類中進(jìn)行管理,關(guān)閉時(shí)紋理會(huì)隨
D3D類的釋放而釋放。
 
 我剛解決問題的辦法很稀奇古怪,也許任何一個(gè)大蝦都反對(duì),不過它工作的很好。定義一個(gè)BOOL變量,在打開文
件時(shí)先把它設(shè)為FALSE,結(jié)束打開時(shí)再設(shè)為TRUE,并重載對(duì)話框的OnDestroy函數(shù),在該函數(shù)中把BOOL變量設(shè)為FALSE,這樣在回調(diào)函數(shù)
開始處檢查此變量,為FALSE時(shí)直接返回。后來發(fā)現(xiàn)把回調(diào)函數(shù)設(shè)為NULL也可,而且似乎更好。
 
 3.與其他實(shí)用程序相比
 
 其實(shí)微軟為DirectShow新配備的VMR9可以無縫與D3D結(jié)合,更有很多強(qiáng)大的功能,絕非我寫的Filter可比,不過我懶于學(xué)習(xí)使用它,自認(rèn)
為寫Filter比學(xué)習(xí)它更容易。從VMR9可以自動(dòng)提供紋理給D3D使用,我萌生一個(gè)想法:能不能在一個(gè)D3D中創(chuàng)建紋理給另一個(gè)D3D
使用呢?我要進(jìn)行驗(yàn)證。
 
 最近得到一個(gè)叫做“CamTrack”的軟件,它很厲害,自帶的人臉跟蹤功能自不必說,它還能自動(dòng)偵測到攝像
頭的使用,在比Filter更低的層級(jí)就接收并處理了數(shù)據(jù),并把處理后的數(shù)據(jù)作為攝像頭數(shù)據(jù)傳送下來!我驚呆了,好像要從攝像頭的WDM
驅(qū)動(dòng)模型去找突破口,但完全沒有頭緒。
 
 4.界面
 
 除了技術(shù)落后、結(jié)構(gòu)混雜之外,我的程序還有很大的一個(gè)缺陷:界面丑陋,元素排列不合理。這是因?yàn)槲也皇煜FC,也不懂得如何設(shè)計(jì)界面造成的,所以您看到的我的程序是古板的win98
樣式,用起來可能很不就手。我初三暑假開始接觸編程,一直用了3年多VB6,鄙視VC,轉(zhuǎn)到VC編程還是近大半年的事,雖已樂不知返卻從沒想過要努力學(xué)
MFC,畢竟VC.net的net界面編寫已不再依賴MFC了,它更像VB,更易用,只是程序載入到界面顯示很慢,我用的VC.net2003 是這樣。
 
 總結(jié)
 
 在這次編寫中我的體會(huì)良多,記得看過這樣一句話“沒寫過一萬行代碼的項(xiàng)目是不會(huì)懂得軟件工程的”,
此時(shí)真的把它奉為真理了,想我這個(gè)程序才多大點(diǎn)兒,文件已有七八個(gè),平時(shí)我看文件有幾個(gè)的程序都感到頭疼,更不敢想象上萬行程序的管理,可能管理上花的精
力比編碼更多。在標(biāo)準(zhǔn)化工廠里,工人都被分工,他們可能不知道自己在干什么,只有管理人員才對(duì)一切了如指掌。如果生產(chǎn)的是軟件,單會(huì)編碼的程序員就只能做
埋頭的工人了,更高層次由不編碼的管理人員掌握,事實(shí)也是這樣。我還發(fā)現(xiàn)有時(shí)花在界面的時(shí)間比所謂的核心功能還多,正如為了控制發(fā)動(dòng)機(jī)和輪子,汽車在控制
方面要花費(fèi)很多,而在舒適的駕駛環(huán)境和新潮的外型方面更要努力,因?yàn)楸仨氁獮槿怂?、易用、好用。現(xiàn)在回過頭看所寫的Filter,它的確是各種功能的基
礎(chǔ),僅此而已,編碼的比重并不大??偟恼f,我的感想是:現(xiàn)代軟件開發(fā)應(yīng)用的是現(xiàn)代工業(yè)生產(chǎn)模式,標(biāo)準(zhǔn)化、分工合作、流水線操作。當(dāng)然,我的認(rèn)識(shí)還很膚淺,
請(qǐng)指正。 最讓我苦惱的是 DirectShow 不支持 rm 文件,或者說 RealNetworks 不支持 DirectShow,畢竟 rm
的算法是保密的,這導(dǎo)致無法用我的程序欣賞很多美妙的影片,強(qiáng)烈希望大蝦們用real sdk 寫個(gè) Filter
發(fā)布出來,讓我等享用,我找不著real sdk。
 
 總算勞累后也有成果,總體效果沒有受技術(shù)限制,再請(qǐng)看看幾幅效果圖,以結(jié)全文。程序路上,你我共勉。
 
 
    
    
     |