隨着計算機技術、網絡技術迅猛發展,在嵌入式電腦上,多媒體功能變得愈來愈普及。倒如如今不少智能手機、PDA以及嵌入式電腦部具備聽MP3、觀看VIDEO視頻以及使用攝像頭拍照、錄像等功能,這些功能極大地加強了用戶體驗。微軟提供Direct Show技術標準,使得能夠很是方便地開發多媒體應用程序。本章就來重點介紹Direct Show技術,並以實例來展示Direct Show技術應用。c++
本章主要介紹DirectShow的相關知識,以及如何利用DirectShow來進行多媒體編程。本章還提供了在Windows Embedded Compact 7環境下使用DirectShow編程的兩個實例。編程
10.1 Direct Show介紹windows
DirectShow有時簡稱爲DS或DShow,它是微軟公司對以前Windows視頻技術的一次更新,是微軟公司在ActiveMovie和Video for Windows的基礎上推出的新一代基於組建對象模型(Component Object Model,COM)的流媒體處理開發包。做爲DirectX你們族中的一員,DirectShow爲Windows平臺上處理各類格式的媒體文件播放、音頻和視頻採集等高性能的多媒體應用提供了完整的解決方案。DirectShow 9.0以前的版本與DirectX開發包一塊兒發佈,以後成爲Windows SDK的一部分。緩存
DirectShow爲大部分微軟公司程序設計語言提供了一個媒體的通用接口,並且是一個基於Filter並能在用戶或開發者的命令下播放或記錄媒體文件的可擴展框架。DirectShow的應用很普遍。經過DirectShow,軟件開發者可以對ASF、MPEG、DV、MP三、WAVE等格式的媒體文件執行各類不一樣的處理。例如,對於本地應用,開發者能夠利用DirectShow實現不一樣格式媒體文件的解碼播放以及不一樣媒體格式間的相互轉換,能夠從本地機器的採集設備捕獲音視頻數據並保存爲文件,能夠接受並觀看模擬電視等。此外,DirectShow可用於視頻點播、視頻監控以及視頻會議等網絡應用。廣而言之,DirectShow還能夠用於除了音視頻等多媒體數據之外的其餘流式數據的處理。網絡
在Windows Embedded Compact 7中,咱們一樣可使用DirectShow來捕獲、回放和轉換多媒體流數據。接下來咱們將分別介紹DirectShow技術框架、過濾器(Filter)、過濾器圖表管理器(Filter Graph Manager)、PIN以及DirectShow接口定義。app
介紹下DirectX家族。DirectX是一個多煤休API,它提供標準接口來與圖形卡、聲卡、輸入設備等進行交互。若是沒有這組標準API,用戶就須要爲圖形卡和聲卡的每一個組臺和每種類型的鍵盤、鼠標和遊戲杆編寫不一樣的代碼。DirectX從具體的硬件中抽象出來,而且將一組通用指令轉換成硬件的具體命令。框架
DirectX的家族成員不少,並且各有各的本領,相似於DirectDraw和Direct3D負責二維圖形圖像,三維動面加速、DirectMusic和DirectSound負責交互式音樂,環境音效處理同樣,ide
DirectShow爲Windows平臺上處理各類格式的媒體文件播放,音視頻採集等高性能要求的多媒體應用,提供了完整的解決方案。模塊化
DirectShow是微軟公司提供的一套在Windows平臺上進行流媒體處理的開發包,與函數
DirectX開發包一塊兒發佈。DirectShow是Windows平臺上的流媒體框架,提供了高質量的多媒體流採集和回放功能。它支持多種多樣的媒體文件格式,包括ASF、MPEO、AVI. MP3和WAV文件,目時支持使用WDM驅動或早期的VFW驅動來進行多媒體流的採集。DirectShow整合了其餘的DirectX技術,能自動偵測井使用可利用的音視頻硬件加速,也能支持沒有硬件加速的系統。
Microsoft經過DirectShow爲多媒體程序開發員提供了標準的、統一的、高效的API接口。
DirectShow技術是創建在DirectX的DirectDraw和DirectSound的基礎之上的,它經過
DirectShow對顯卡進行控制以顯示視師,經過DirectSound對聲卡進行控制以播放聲音。
DirectX爲了最大限度提升效率而容許用戶直接訪問硬件,如容許用戶直接讀寫顯存,所以,DirectShow也一樣具備快速的優點。
下面就對DirectShow的關鍵技術點作簡要介紹,以便讀者對DirectShow技術有了初步瞭解,也便於理解後面講述的媒體播放器、攝像頭捕捉示例代碼。DirectShow是一門很是系統的技術,關於更爲詳細的技術細節,請讀者參考相關專業DirectShow書籍或者MSDN。
10.1.1 DirectShow技術框架
DirectShow的技術框架如圖10-1所示。看了此框架圖就會發現DirectShow的工做原理也很是簡單、清晰。此圖能夠簡單理解成3部分,輸入→邏輯處理→輸出,這是一個典型的計算機處理流程。輸入部分:它能夠是本地視頻、音頻文件、Internet網絡、視頻和音頻採集卡:輸出部分:它能夠是本地文件、顯示器(顯卡)和聲卡;邏輯處理部分:也就是DirectShow的核心部分,從圖上看包括:源過濾器、傳輸過濾器、渲染過濾器以及管理這3個過濾器的過濾器圖管理器對象。
圖10-1 DirectShow 技術框架圖
在應用程序中,爲了完成對多媒體數據的處理,須要將若干過濾器鏈接起來,一個的輸出做爲另外一個的輸入,這樣鏈接在一塊兒的一組過濾器稱爲過濾器圖(Filter Graph)。過濾器圖也決定着每一步該使用哪個過濾囂及這些過濾器之間是如何鏈接的。這樣,多媒體數據流就在過濾器流水線上,從源過濾器經由中間過濾器移動到播放過濾器,從而最終完成播放。在這個過程當中完成了對數據的讀取、解碼、將數據輸出到相應的設備、播放等操做。
10.1.2過濾器( Filter)
DirectShow是基於模塊化的開發框架,每一個功能操做如捕獲、回放和轉換都採用COM組件方式,每一個COM組件就稱爲Filter。Filter是完成DirectShow處理過程的基本單元。DirectShow提供了一系列標準的Filters用於應用開發,開發者也能夠開發本身的Filter來擴展DirectShow的功能,但必須是以COM形式創建的。DirectX爲用戶提供了DirectShow基類庫(DirectShow Base Class Library),用戶自定義的過濾器均可以從基類庫提供的基類和接口派生出來。
過濾器主要分爲如下幾種類型:
(1)源過濾器(source filter):源過濾器引入數據到過濾器圖表中,數據來源能夠是文件、網絡、照相機等。不一樣的源過濾器處理不一樣類型的數據源。
(2)變換過濾器(transform filter):變換過濾器的工做是獲取輸入流,處理數據,並生成輸出流。變換過濾器對數據的處理包括編解碼、格式轉換、壓縮解壓縮等。
(3)渲染過濾器(renderer filter):渲染過濾器在過濾器圖表裏處於最後一級,它們接收數據並把數據提交給外設。
(4)分割過濾器(splitter filter):分割過濾器把輸入流分割成多個輸出。例如,AVI分割過濾器把一個AVI格式的字節流分割成視頻流和音頻流。
(5)混合過濾器(mux filter):混合過濾器把多個輸入組合成一個單獨的數據流。例如,AVI混合過濾器把視頻流和音頻流合成一個AVI格式的字節流。
過濾器的這些分類並非絕對的,例如一個ASF讀過濾器(ASF Reader Filter)既是一個源過濾器又是一個分割過濾器。
在DirectShow中,一組相連的過濾器稱爲一個過濾器圖表(Filter Graph),從而完成比單個過濾器更復雜的多媒體任務。過濾器圖表用來鏈接過濾器以控制媒體流,它也能夠將數據返回給應用程序,並搜索所支持的過濾器。過濾器有三種可能的狀態:運行、中止和暫停。暫停是一種中間狀態,中止狀態到運行狀態一定通過暫停狀態。暫停能夠理解爲數據就緒狀態,是爲了快速切換到運行狀態而設計的。在暫停狀態下,數據線程是啓動的,但被渲染過濾器阻塞了。一般狀況下,過濾器圖表中全部過濾器的狀態是一致的。
過濾器接收輸入和產生輸出,信息經過過濾器管腳( PIN)在過濾器之間傳遞。一個管腳(PIN)是一個過濾器端口,它能夠是輸入端口也能夠是輸出端口。
過濾器Filter具備三個狀態:運行、中止、暫停。當一個Filter運行時,它就處理媒體數據流:當中止時,Filter就再也不處理數據,暫停狀態用於臨時暫停過濾器運行。
10.1.3 Filter Graph Manager
DirectShow專門提供了一個高級接口——過濾器圖表管理器(Filter Graph Manager,FGM)——用來控制過濾器圖表中的過濾器。同時,它也能爲一個特定的媒體文件選配過濾器,渲染播放該文件。過濾器圖表管理器是COM 形式的,它的功能有:協調過濾器間的狀態轉變;創建參考時鐘;把事件(event)傳送給應用程序;爲應用程序提供創建過濾器圖表的方法等。
這些功能可經過過濾器圖表管理器接口IGraphBuilder、IMediaControl、IMediaEvent、IMediaEventEx、IVideoWindow、IBasicAudio、IBasicVideo、IMediaSeeking、IMediaPosition、IVideoFrameStep等實現。
在開發基於DirectShow技術的應用程序時,都必須建立多個過濾器(Filter)並進行恰當的鏈接,因而數據流就能夠從源過濾器輸入,通過一系列過濾器(Filter)傳遞,最後到達RenderFilter輸出,被用戶所使用。這些過濾器的集合就叫作過濾器圖(Filter Graph)。圖10-2演示如何使用Filter來播放AVI視頻文件。
圖10-2播放帶聲音的視頻文件過濾器圖形
下面簡要介紹採用Filter播放視頻文件的流程。本流程將會在本章第2節的媒體播放器中
被具體代碼實現。
(l)首先從一個文件中讀取AVI數據,造成字節流。(這個工做由源Filter完成)。
(2)檢查AVI數據流的頭格式,而後經過AVI分割Filter,將視頻流和音頻流分開。
(3)解碼視頻流,根據壓縮格式的不一樣,選取不一樣的decoder filters。
(4)經過Renderer Filter重畫視頻圖像。
(5)音頻流送到聲卡進行播放,通常採用缺省的DirectSound Device Filter。
Filter Graph Manager也是一個COM對象,用來控制Filter Graph中全部的Filter,主要有如下的功能:
協調Filters之間的狀態改變。Filters的狀態改變必須以一種特殊順序發生。所以,應用程序並不將狀態改變的命令直接發給Filter,而是發送給Filter Graph Manager一個簡單命令,由Manager將命令分發給Graph中的每個Filter。Seeking也是按一樣的方式工做,先由應用程序將Seek命令發送到Filter Graph Manager,而後由其分發給每一個Filters。
創建參考時鐘。Graph中的Filter都採用同一個時鐘,稱爲參考時鐘(Reference Clock),參考時鐘能夠確保全部的數據流同步。視頻楨或者音頻Sample應該被提交的時間稱爲Presentation Time,它是相對於參考時鐘來肯定的。Filter Graph Manager應該選擇一個參考時鐘,能夠選擇聲卡上的時鐘,也能夠選擇系統時鐘。
傳遞事件到應用程序。Filter Graph Manager採用事件隊列機制將Graph中發生的事件通知給應用程序,這個機制相似於Windows的消息循環。
提供應用程序創建Filter Graph的方法。Filter Graph Manager給應用程序提供了將Filter添加進Graph的方法、鏈接Filter以及斷開Filter鏈接的方法。
Filter Graph Manager沒有的一個功能就是把數據從一個Filter移動到另外一個Filter,這是由Filters本身經過它們的Pin鏈接完成的。處理過程老是在不一樣的線程進行。
10.1.4 Pin
過濾器能夠和一個或多個過濾器相連,鏈接的單向接口也是COM形式的,稱爲引腳,就是Pin。過濾器利用引腳在各個過濾器間傳輸數據。每一個引腳都是從Ipin這個COM對象派生出來的。每一個引腳都是過濾器的私有對象,過濾器能夠動態的建立引腳,銷燬引腳,自由控制引腳的生存時間。引腳能夠分爲輸入引腳(Input pin)和輸出引腳(Output pin)兩種類型,兩個相連的引腳必須是不一樣種類的,即輸入引腳只能和輸出引腳相連,且鏈接的方向老是從輸出引腳指向輸入引腳。
Pin就是兩個過濾器相連的接口。每一個Pin都是從IPin這個COM對象派生出來的。每一個Pin都是過濾器私有對象,過濾器能夠動態地建立和銷燬Pin.自由地控制Pin的生存時間。Pin能夠分爲兩類:輸入Pin和輸出Pin。兩個相連的Pin必須是不一樣種類的,即輸入Pin只能同輸出Pin相連。數據就從相連的Pin中流動,從上一級過濾器到下一級過濾器。兩個過濾器的Pin相連的時候,有一個協商的過程,二者必須統一數據流的類型、緩存的大小和數據傳送的機制等。若是協商沒有統一,這兩個過濾器就沒法鏈接。
10.1.5 DirectShow接口定義介紹
上面簡要介紹了DirectShow技術框架,下面就來介紹DirectShow SDK提供的相關COM接口,經過這些接口能夠很是方便地實現DirectShow應用編程,DirectShow經常使用接口如表10-1所示。
表 10-1DirectShow經常使用接口
接口 |
描述 |
IAMCollection |
過濾器圖表對象集合,例如過濾器或引腳。 |
IAMStats |
容許應用程序從圖表管理器中檢索性能數據。過濾器可使用此接口記錄性能數據。 |
IBasicAudio |
容許應用程序控制音頻流的音量和平衡。 |
IBaseFilter |
提供用於控制過濾器的方法。應用程序可使用此接口枚舉引腳和查詢過濾器信息。 |
IBasicVideo |
容許應用程序設置視頻屬性,例如目標矩形和源矩形 |
IBasicVideo2 |
從 IBasicVideo 接口派生,爲應用程序提供了一個附加方法,經過它能夠檢索視頻流的首選縱橫比。 |
IConfigAsfWriter2 |
提供用於獲取和設置 WM ASF Writer過濾器寫文件要使用的高級流格式(Advanced Streaming Format,ASF)配置文件的方法和用於支持 Windows Media Format 9 Series SDK 中的新功能(例如雙向編碼和對反交錯視頻的支持)的方法。 |
IDeferredCommand |
容許應用程序取消或修改該應用程序先前使用 IQueueCommand 接口排入隊列的圖形-控制命令。 |
IFilterInfo |
管理過濾器的信息並提供訪問過濾器和表示過濾器上的引腳的 IPinInfo 接口。 |
IFileSinkFilter |
在將媒體流寫入文件的過濾器上實現。 |
IFileSourceFilter |
在從文件讀媒體流的過濾器上實現。 |
IGraphBuilder |
爲應用程序提供建立過濾器圖表的方法。 |
IMediaControl |
提供方法來控制通過過濾器圖表的數據流。它包含運行、暫停和中止圖形的方法。 |
IMediaEvent |
包含用來檢索事件通知和用於重寫過濾器圖表管理器的默認事件處理的方法。 |
IMediaEventEx |
從 IMediaEvent 派生並添加方法來啓用一個應用程序窗口,以便在事件發生時接收消息。 |
IMediaPosition |
用於尋找數據流的位置。 |
IMediaSeeking |
提供搜索數據流位置和設置播放速率的方法。 |
IMediaTypeInfo |
包含用於檢索引腳鏈接的媒體類型的方法。 |
IPinInfo |
包含用於檢索引腳信息和鏈接引腳的方法。 |
IQueueCommand |
容許應用程序預先將圖形-控制命令排入隊列。 |
IRegFilterInfo |
提供對 Windows 註冊表中的過濾器的訪問,以及向過濾器圖表中添加已註冊的過濾器。 |
IVideoFrameStep |
用於步進播放視頻流,可以使DirectShow應用程序,包括DVD播放器一次只播放一幀視頻。 |
IVideoWindow |
包含用於設置窗口全部者、窗口的位置和尺寸及其餘窗口屬性的方法。 |
IWmProfileManager |
用於建立配置文件、加載現有的配置文件和保存配置文件。 |
10.2媒體播放器示例
Windows Embedded Compact7支持使用DirectShow技術來捕獲、播放和轉換多媒體內容。嵌入式設備一般包含音頻和視頻硬件×××。因爲嵌入式設備中的硬件專屬於嵌入式平臺,開發者須要實現DirectShow過濾器來讓DirectShow在特定的平臺運行。爲了儘快實現一個DirectShow過濾器,通常推薦在WINCE7下使用DirectShow基類來完成。本節主要介紹利用DirectShow在WINCE平臺上如何實現一個媒體播放器。一個播放過濾器圖表包括一個源過濾器,一個多路分配過濾器,一個活多個×××濾波器以及一個或多個渲染過濾器。對於某些格式,WINCE7還包括一個新的緩衝過濾器,即Buffering Stream Filter。該過濾器位於源過濾器和多路分配器之間,管理IMediaSample緩衝池。若是使用Buffering Stream Filter,下行的過濾器只須要不多的緩衝區管理工做。
圖3提供了一個包含Buffering Stream Filter的過濾器圖表。值得一提的是,在圖10-3中,有的過濾器由操做系統中提供,有的過濾器是平臺特有的,還有過濾器既可由操做系統提供,也能夠爲平臺特有。
圖10-3 DirectShow 實現媒體播放器的過濾器圖表
固然,咱們在實現視頻和音頻×××、渲染器時能夠有不一樣的選擇。例如,能夠利用一個軟件模塊將視頻×××和渲染器合併,另外一個軟件模塊將音頻×××和渲染器合併。還有一種方式是建立單個模塊,該模塊包括視頻×××和渲染器、音頻×××和渲染器。取決於不一樣的實現方式,圖10-3所示的過濾器圖表可修改成圖10-4所示的配置。
圖10-4 媒體播放器的過濾器圖表的其餘配置
若是某些媒體格式不須要緩衝流過濾器,圖3和圖4中的Buffering Stream Filter能夠去除,此時源過濾器和多路分配過濾器直接相連。
WINCE7所支持的編解碼格式如表2所示。
表10-2 WINCE7支持的DirectShow編×××
在實現DirectShow過濾器時,咱們必須實現一些接口和方法。爲了減小須要開發的代碼量,DirectShow基類提供了許多通用代碼,因此咱們推薦在過濾器開發過程當中儘量地使用基類。一個過濾器的主要功能部分包括輸入引腳,輸出引腳以及過濾器自己的邏輯。可是須要注意的是,特殊的過濾器可能沒有輸入引腳或輸出引腳。例如,如圖2所示,源過濾器沒有輸入引腳,渲染過濾器沒有輸出引腳。通常而言,咱們繼承一些相關的基類來開發一個類所對應的輸入引腳、輸出引腳以及過濾器邏輯。
圖10-5DirectShow×××過濾器類圖
在第1節中簡要介紹了Direct Show的基本技術框架,下面就以一個實際的媒體播放器爲例進行說明,使讀者對應用DirectShow編程有更深刻地瞭解。本卻是經過DirectShow的IFilterGpaph->Render函數來自動渲染視頻、音頻文件。這裏可以播放哪些媒體文件,跟系統註冊相關的Filter有很大關係。
下面就來說述如何利用DirectShow技術來搭建一個媒體播放器。
(l)創建新項目。
使用VS2008選擇智能設備模板,利用MFC智能設備應用程序嚮導建立一個基於對話框的應用程序EricMeidaPlayer,編譯環境設置爲yinchengOS SDK。
(2)新建CEricMediaControl類,用於封裝媒體文件播放等功能。
CEricMediaControl類是一個通用c++類,無基類。其類圖如圖10-6所示。
圖10-6 媒體播放類圖
CEricMediaControl類基於DirectShow技術而且封裝了媒體播放技術,提供打開媒體文件、播放、暫停、終止、全屏、設置當前播放位置等經常使用方法。基於CEricMediaControl類能夠很是方便地製做媒體播放程序,讀者也能夠在此類的基礎上加以修改升級,以實現更強的功能。
下面就來具體實現CEricMediaControl類。
1)爲CEricMediaControl類添加以下私有變量,用於定義播放媒體文件播放所需的
DSHOW接口,代碼如下所示。
private:
//DSHOW 接口
IGraphBuilder *m_pGB ;
IMediaControl *m_pMC ;
IMediaEventEx *m_pME ;
IVideoWindow *m_pVW ;
IBasicAudio *m_pBA ;
IBasicVideo *m_pBV ;
IMediaSeeking *m_pMS ;
//顯示視頻的窗口句柄
HWND m_hOwnerWnd;
備註:這裏須要引用dshow.h頭文件。
#include<show.h>
2)定義媒體播放事件對應的WINDOWS消息常量WM_GRAPHNOTIFY,定義以下:
//定義DSHOW事件通知消息
#define WM_GRAPHNOTIFY WM_USER+ 101
3)實現CEricMediaControl類的構造函數和析構函數。在構造函數裏,將DSHOW相關
接口初始化爲NULL,並初始化COM環境;在析構函數裏,釋放DSHOW接口並釋放COM
環境。CEricMediaControl類構造函數和析構函數的實現如下所示。
//構造函數
CEricMediaControl::CEricMediaControl(void)
{
//將DSHOW接口置空
m_pGB = NULL;
m_pMC = NULL;
m_pME = NULL;
m_pVW = NULL;
m_pBA = NULL;
m_pBV = NULL;
m_pMS = NULL;
//初始化 COM 環境
CoInitialize(NULL);
}
//析構函數
CEricMediaControl::~CEricMediaControl(void)
{
//釋放DSHOW接口
UnInitDShow();
//釋放COM 環境
CoUninitialize();
}
4)爲CEricMediaControl類添加InitDShow和UninitDShow兩個私有方法。InitDShow方法用於初始化DShow接口並渲染媒體文件;UnlnitDShow方法用於釋放DShow接口。讀者應特別注意InitDShow方法,該方法直接描述瞭如何利用DShow接口逐步播放視頻文件。
這個兩個方法的定義以下:
private:
//初始化DSHOW接口
BOOL InitDShow(LPCTSTR strFileName /*視頻文件名*/
,HWND hOwnerWnd /*顯示視頻的窗口句柄*/
,HWND hNotifyWnd /*接收DSHOW事件消息的串口句柄*/
);
//釋放DSHOW接口
BOOL UnInitDShow();
InitDShow和UnlnitDShow方法的實現如下所示。
/*
*函數介紹: 初始化DShow接口,並渲染好視頻文件
*入口參數: strFileName: 視頻文件名
hOwnerWnd: 顯示視頻的窗口句柄
hNotifyWnd: 接收DSHOW事件消息的串口句柄
*出口參數:(無)
*返回值:TRUE:成功;FALSE:失敗
*/
BOOL CEricMediaControl::InitDShow(LPCTSTR strFileName /*視頻文件名*/
,HWND hOwnerWnd /*顯示視頻的窗口句柄*/
,HWND hNotifyWnd /*接收DSHOW事件消息的串口句柄*/
)
{
HRESULT hResult;
//第1步:建立IGraphBuilder接口
hResult = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC, IID_IGraphBuilder, (void **)&m_pGB);
if (hResult != S_OK)
{
return FALSE;
}
//第2步:利用IGraphBuilder渲染視頻文件
hResult = m_pGB->RenderFile(strFileName,NULL);
if (hResult != S_OK )
{
//
if (hResult == VFW_S_PARTIAL_RENDER)
{
TRACE(L"Some of the streams in this movie are in an unsupported format.\n");
}
else if (hResult == VFW_S_AUDIO_NOT_RENDERED)
{
TRACE(L"Partial success; the audio was not rendered.\n");
}
else if (hResult == VFW_S_DUPLICATE_NAME)
{
TRACE(L"Success; the Filter Graph Manager modified the filter name to avoid duplication..\n");
}
else if (hResult == VFW_S_VIDEO_NOT_RENDERED)
{
TRACE(L"Partial success; some of the streams in this movie are in an unsupported format.\n");
}
else
{
//釋放DSHOW接口
UnInitDShow();
return FALSE;
}
}
//第3步:獲得媒體播放控制接口
hResult = m_pGB->QueryInterface(IID_IMediaControl, (void **)&m_pMC);
if (hResult != S_OK)
{
//釋放DSHOW接口
UnInitDShow();
return FALSE;
}
//第4步:獲得媒體播放位置搜索接口
hResult = m_pGB->QueryInterface(IID_IMediaSeeking,(void**)&m_pMS);
if (hResult != S_OK)
{
//釋放DSHOW接口
UnInitDShow();
return FALSE;
}
//設置查找定位的時間單位,這裏設置:100納秒(十億分之一秒)
GUID guid_timeFormat = TIME_FORMAT_MEDIA_TIME;
m_pMS->SetTimeFormat(&guid_timeFormat);
//第5步:獲得Filter Graph媒體事件接口
hResult = m_pGB->QueryInterface(IID_IMediaEventEx,(void**)&m_pME);
if (hResult != S_OK)
{
//釋放DSHOW接口
UnInitDShow();
return FALSE;
}
//設置媒體事件通知消息窗口
m_pME->SetNotifyWindow((OAHWND)hNotifyWnd, WM_GRAPHNOTIFY, 0);
//第6步:獲得視頻播放窗口接口
hResult = m_pGB->QueryInterface(IID_IVideoWindow, (void **)&m_pVW);
if (hResult != S_OK)
{
//釋放DSHOW接口
UnInitDShow();
return FALSE;
}
//設置視頻播放窗口句柄
m_pVW->put_Owner((OAHWND)hOwnerWnd);
//設置視頻窗口格式
m_pVW->put_WindowStyle(WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN);
//第7步:獲得基礎視頻流接口
hResult = m_pGB->QueryInterface(IID_IBasicVideo, (void **)&m_pBV);
if (hResult != S_OK)
{
//釋放DSHOW接口
UnInitDShow();
return FALSE;
}
//第8步:獲得基礎音頻流接口
hResult = m_pGB->QueryInterface(IID_IBasicAudio, (void **)&m_pBA);
if (hResult != S_OK)
{
//釋放DSHOW接口
UnInitDShow();
return FALSE;
}
return TRUE;
}
/*
*函數介紹: 卸載DShow系列接口
*入口參數: (無)
*出口參數:(無)
*返回值:TRUE:成功;FALSE:失敗
*/
BOOL CEricMediaControl::UnInitDShow()
{
//1,釋放媒體播放控制接口
if (m_pMC != NULL)
{
//中止視頻播放
m_pMC->Stop();
m_pMC->Release();
m_pMC = NULL;
}
//2,釋放媒體事件接口
if (m_pME != NULL)
{
//消息通知窗口置空
m_pME->SetNotifyWindow(NULL, 0, 0);
m_pME->Release();
m_pME = NULL;
}
//3,釋放視頻播放窗口接口
if (m_pVW != NULL)
{
//隱藏視頻窗口
m_pVW->put_Visible(OAFALSE);
//設置視頻播放窗口句柄爲空
m_pVW->put_Owner(NULL);
m_pVW->Release();
m_pVW = NULL;
}
//4,釋放基礎音頻流接口
if (m_pBA != NULL)
{
m_pBA->Release();
m_pBA = NULL;
}
//5,釋放基礎視頻流接口
if (m_pBV != NULL)
{
m_pBV->Release();
m_pBV = NULL;
}
//6,釋放媒體搜索接口
if (m_pMS != NULL)
{
m_pMS->Release();
m_pMS = NULL;
}
//7,最後釋放FilterGpaph接口
if (m_pGB != NULL)
{
m_pGB->Release();
m_pGB = NULL;
}
return TRUE;
}
5)爲CEricMediaControl類添加4個共有方法,供外部調用。這4個方法分別爲OpenFile(打開視頻文件)、VfideoRun(視頻播放)、VideoPause(視頻暫停)、VideoStop(視頻中止)。打開視頻文件方法,調用前面講述的InitDShow方法,來初始化視頻播放文件。視頻播放、暫停、中止等方法經過IMediaControl接口調用相關方法來實現媒體流的控制。
這4個方法具體定義以下:
public:
//打開視頻文件
BOOL OpenFile(LPCTSTR strFileName /*視頻文件名*/
,HWND hOwnerWnd /*顯示視頻的窗口句柄*/
,HWND hNotifyWnd /*接收DSHOW事件消息的串口句柄*/
);
//播放視頻
BOOL VideoRun();
//暫停視頻
BOOL VideoPause();
//中止視頻
BOOL VideoStop();
這4個方法的實現如下所示。
/*
*函數介紹:打開視頻文件
*入口參數:strFileName:視頻文件名
hOwnerWnd:顯示視頻的窗口句柄
hNotifyWnd:接收DSHOW事件消息的串口句柄
*出口參數:(無)
*返回值:TRUE,成功打開視頻文件;FALSE:打開視頻文件失敗
*/
BOOL CEricMediaControl::OpenFile(LPCTSTR strFileName /*視頻文件名*/
,HWND hOwnerWnd/*顯示視頻的窗口句柄*/
,HWND hNotifyWnd/*接收DSHOW事件消息的串口句柄*/
)
{
//存儲顯示視頻窗口句柄
m_hOwnerWnd = hOwnerWnd;
//重置DSHOW接口
UninitDShow();
//打開視頻文件,並對DSHOW接口作初始化工做
return InitDShow( strFileName, hOwnerWnd,hNotifyWnd);
}
/*
*函數介紹:括放視頻
*入口參數;(無)
*出口參數:(無)
*返回值:TRUE:成功;FALSE:失敗
*/
BOOL CEricMediaControl::VideoRun()
//檢測IMediaControl控制接口有效性
if(m_pMC== NULL)
{
Return FALSE;
}
//播放視頻
HRESULT hResult = m_pMC->Run();
if(hResult !=S_OK)
{
Return FALSE;
}
Return TRUE;
}
/*
*函數介紹;暫停視頻
*入口參數:(無)
*出口參數:(無)
*返回值: TRUE:成功:FALSE:失敗
*/
BOOL CEricMediaControl::VideoPause()
//檢測IMediaControl控制接口有效性
if (m_pMC==NULL)
{
return FALSE;
}
//暫停視頻
HRESULT hResult = m_pMC->Pause();
If(hResult i= S_OK)
{
Return FALSE;
}
Return TRUE;
}
/*
*函數介紹:中止視頻
*入口參數:(無)
*出口參數:(無)
*返回值:TRUE:成功;FALSE:失敗
*/
BOOL CEricMediaControl::VideoStop()
{
HRESULT hResult;
//檢測IMediaControl控制接口有效性
if(m_pMC ==NULL)
{
Return FALSE;
}
//中止視頻
hReSult = m_pMC->Stop();
if (hResult != S_OK)
{
Return FALSE;
}
//將當前播放位置置0
LONGLONG pos:0;
hResult : m_pMS->SetPositions(&pos, AM_SEEKIFIG_AbsolutePositionin9
NULL, AM SEEKING NoPositioning);
6)爲類CEricMediaControl類添加7個共有方法,用於設置媒體播放屬性以及獲得媒體播放屬性。這7個方法分別爲FitVideoWindow(設置視頻顯示比例)、FuIIScreen(全屏顯示)、GetFuIIScreenStatus(獲得是不是全屏顯示)、GetMediaEvent(獲得DShow播放事件)、SetPositions(設置播放進度)、GetCurrentPos(獲得當前播放進度)、GelDuration(獲得媒體文件時間長度)。具體定義以下:
public:
//設置視頻顯示比例
BOOL FitVideoWindow(FLOAT fScale);
//全屏顯示
BOOL FullScreen();
//獲得是不是全屏顯示
BOOL GetFullScreenStatus();
//獲得DShow播放事件
BOOL GetMediaEvent(long *lEventCode);
//設置播放進度,單位: 秒
BOOL SetPositions(DWORD dwPos /*設置當前播放進度*/);
//獲得視頻播放當前的位置,單位:秒
BOOL GetCurrentPos(DWORD &dwPos /*out 當前播放進度*/);
//獲得視頻文件時間長度,單位:秒
BOOL GetDuration(DWORD &dwLength);
這7個函數的實現如下所示。
/*
*函數介紹: 設置視頻顯示比例
*入口參數: fScale : 顯示比例, <= 1.0
*出口參數: (無)
*返回值:TRUE:成功;FALSE:失敗,此處有點問題
*/
BOOL CEricMediaControl::FitVideoWindow(FLOAT fScale)
{
LONG lHeight, lWidth;
int iSeek = 0;
double dblScaleX,dblScaleY;
HRESULT hr = S_OK;
LONG lDeflateX = 0;
LONG lDeflateY = 0;
CRect clientRect;
CRect dstRect;
//
if (m_pBV == NULL)
{
return FALSE;
}
//放縮比例必須小於等於1
if (fScale > 1.0)
{
return FALSE;
}
//獲得原始視頻尺寸
hr = m_pBV->GetVideoSize(&lWidth, &lHeight);
if (hr != S_OK)
{
return FALSE;
}
//設置拉伸後的尺寸
lWidth = lWidth * fScale;
lHeight = lHeight * fScale;
//獲得視頻播放窗口的尺寸
GetClientRect(m_hOwnerWnd, &clientRect);
lDeflateX = (clientRect.Width() - clientRect.Width() * fScale) / 2;
lDeflateY = (clientRect.Height() - clientRect.Height() * fScale) / 2;
//從新設置客戶區域
clientRect.DeflateRect(lDeflateX,lDeflateY);
if ( (lWidth <= clientRect.Width())
&& (lHeight <= clientRect.Height()))
{
dstRect.left = (clientRect.right - clientRect.left - lWidth) /2;
dstRect.right = dstRect.left + lWidth;
dstRect.top = (clientRect.bottom - clientRect.top - lHeight) /2;
dstRect.bottom = dstRect.top + lHeight;
}
else
{
dblScaleX =double(clientRect.Width()) / double(lWidth) ;
dblScaleY = double(clientRect.Height()) / double(lHeight) ;
if (dblScaleX <= dblScaleY)
{
dstRect.left = clientRect.left;
dstRect.right = clientRect.right;
iSeek =(clientRect.Height() - clientRect.Width()*(double(lHeight) / double(lWidth)))/2;
dstRect.top = clientRect.top + iSeek;
dstRect.bottom = dstRect.top + clientRect.Width()*(double(lHeight) / double(lWidth));
}
else
{
dstRect.top = clientRect.top;
dstRect.bottom = clientRect.bottom;
iSeek =(clientRect.Width() - clientRect.Height()*(double(lWidth) / double(lHeight)))/2;
dstRect.left = clientRect.left + iSeek;
dstRect.right = dstRect.left + clientRect.Height()*(double(lWidth) / double(lHeight));
}
}
//設置視頻播放位置
m_pVW->SetWindowPosition(dstRect.left,dstRect.top,dstRect.Width(),dstRect.Height());
return TRUE;
}
/*
*函數介紹: 全屏顯示
*入口參數: (無)
*出口參數: (無)
*返回值:TRUE:成功;FALSE:失敗
*/
BOOL CEricMediaControl::FullScreen()
{
LONG lMode = 0;
static HWND hDrain=0;
if (m_pBV == NULL)
{
return FALSE;
}
//獲得全屏狀態
m_pVW->get_FullScreenMode(&lMode);
if (lMode == OAFALSE)
{
// Save current message drain
m_pVW->get_MessageDrain((OAHWND *) &hDrain);
// Set message drain to application main window
m_pVW->put_MessageDrain((OAHWND)m_hOwnerWnd );
//設置全屏幕
lMode = OATRUE;
m_pVW->put_FullScreenMode(lMode);
}
else
{
//切換到非全屏模式
lMode = OAFALSE;
m_pVW->put_FullScreenMode(lMode);
// Undo change of message drain
m_pVW->put_MessageDrain((OAHWND) hDrain);
// Reset video window
FitVideoWindow(1);
m_pVW->SetWindowForeground(-1);
}
return TRUE;
}
/*
*函數介紹: 獲得是否全屏幕播放
*入口參數: (無)
*出口參數: (無)
*返回值:TRUE:成功;FALSE:失敗
*/
BOOL CEricMediaControl::GetFullScreenStatus()
{
LONG lMode = 0;
if (m_pBV == NULL)
{
return FALSE;
}
m_pVW->get_FullScreenMode(&lMode);
if (lMode == OAFALSE)
{
return FALSE;
}
else
{
return TRUE;
}
}
/*
*函數介紹: 獲得媒體事件
*入口參數: (無)
*出口參數: lEventCode
*返回值:TRUE:成功;FALSE:失敗
*/
BOOL CEricMediaControl::GetMediaEvent(long *lEventCode)
{
LONG evCode, evParam1, evParam2;
HRESULT hr=S_OK;
if (m_pME == NULL)
{
return FALSE;
}
hr = m_pME->GetEvent(&evCode, &evParam1, &evParam2, 0);
if (SUCCEEDED(hr))
{
*lEventCode = evCode;
// Spin through the events
hr = m_pME->FreeEventParams(evCode, evParam1, evParam2);
return TRUE;
}
return FALSE;
}
/*
*函數介紹: 設置播放進度
*入口參數: dwPos :播放進度,單位秒
*出口參數: (無)
*返回值:TRUE:成功;FALSE:失敗
*/
BOOL CEricMediaControl::SetPositions(DWORD dwPos /*設置當前播放進度*/)
{
//設置絕對位置,轉化成納秒爲單位
LONGLONG llPos = dwPos * 10000 * 1000;
if (m_pMS == NULL)
{
return FALSE;
}
//設置視頻播放當前位置
HRESULT hr = m_pMS->SetPositions(&llPos,AM_SEEKING_AbsolutePositioning ,
NULL, AM_SEEKING_NoPositioning);
if (hr == S_OK)
{
return TRUE;
}
else
{
return FALSE;
}
}
/*
*函數介紹: 獲得視頻文件播放長度,單位秒
*入口參數: dwLength :視頻文件時間長度
*出口參數: dwLength :視頻文件時間長度
*返回值:TRUE:成功;FALSE:失敗
*/
BOOL CEricMediaControl::GetDuration(DWORD &dwLength)
{
dwLength = 0;
if (m_pMS == NULL)
{
return FALSE;
}
//獲得視頻總時間長度
LONGLONG llDuration;
HRESULT hResult = m_pMS->GetDuration(&llDuration);
if (hResult != S_OK)
{
return FALSE;
}
//轉換成以秒爲單位
llDuration = llDuration / 10000 ;
llDuration = llDuration / 1000;
dwLength = (DWORD)llDuration;
return TRUE;
}
/*
*函數介紹: 獲得媒體當前播放進度位置,單位秒
*入口參數: dwPos :當前播放進度,單位秒
*出口參數: dwPos :當前播放進度,單位秒
*返回值:TRUE:成功;FALSE:失敗
*/
BOOL CEricMediaControl::GetCurrentPos(DWORD &dwPos)
{
dwPos = 0;
LONGLONG llPos = 0;
if (m_pMS== NULL)
{
return FALSE;
}
//獲得當前視頻播放位置
HRESULT hResult = m_pMS->GetCurrentPosition(&llPos);
if (hResult != S_OK)
{
return FALSE;
}
//轉換成以秒爲單位
llPos = llPos / 10000 ;
llPos = llPos / 1000;
dwPos = (DWORD)llPos;
return TRUE;
}
至此,CEricMediaContorl類就編寫完成了。下面將基於CEricMediaContorl類演示如何使用它實現播放視頻、音頻文件的功能。若此時編譯工程,將會發現有一堆鏈接錯誤提示。那是由於沒有連接strmiids.lib靜態庫。
選擇「EricMediaPlayer屬性頁」一「連接囂」 一「輸入」一「附加依賴項」,輸入strmiids.lib,便可無誤地編譯本工程。
(3)設計媒體播放器主窗口。
1) include「CEricMedia Control.h」
2)爲CEricMeidaPlayerDlg類添加mVideoControl成員變量,類型爲以前建立的CEricMediaControl類。定義以下。
privrate:
CEricMediaControl m_VideoControl;//媒體播放對象
備註:因爲引用了CEricMediaControl類,所以須要在EricMediaPJayerDlg.h文件中添加對CEricMediaControl類定義文件的引用。
3)實現「打開」按鈕單擊事件。單擊「打開」按鈕,將能夠選擇要播放的媒體文件。「打開」按鈕單擊事件的實現如下所示。
//打開視頻文件
void CEricMeidaPlayerDlg::OnBnClickedBtnOpen()
{
DWORD dwMax = 0;
//獲得媒體播放窗口
CWnd *pVideoWnd = GetDlgItem(IDC_WND_VIDEO);
//獲得進度條窗口
CProgressCtrl *pPrgWnd = (CProgressCtrl*)GetDlgItem(IDC_PRG_VIDEO);
//定義媒體播放文件名
CString strFileName;
TCHAR szFilters[]= _T("windows media video Files (*.wmv)|*.wmv|video Files (*.avi)|*.avi|All Files (*.*)|*.*||");
CFileDialog fileDlg (TRUE, _T("Open video files"), _T("*.wmv"),
OFN_FILEMUSTEXIST , szFilters, this);
//打開文件選擇對話框
if( fileDlg.DoModal () !=IDOK )
{
return;
}
//獲得要播放的視頻文件名
strFileName = fileDlg.GetPathName();
//打開視頻文件
if (m_VideoControl.OpenFile(strFileName,pVideoWnd->m_hWnd,m_hWnd))
{
//設置視頻尺寸
m_VideoControl.FitVideoWindow(1.0);
//獲得視頻文件時間長度
m_VideoControl.GetDuration(dwMax);
//設置進度條範圍
pPrgWnd->SetRange(0,dwMax);
pPrgWnd->SetPos(0);
}
else
{
AfxMessageBox(L"Can't play the video,because the system can't find some codec program!");
}
}
4)實現「播放」、「暫停」、「中止」和「全屏」按鈕單擊事件,具體實現代碼以下所示。
//播放視頻
void CEricMeidaPlayerDlg::OnBnClickedBtnPlay()
{
m_VideoControl.VideoRun();
}
//暫停視頻
void CEricMeidaPlayerDlg::OnBnClickedBtnPause()
{
m_VideoControl.VideoPause();
}
//中止視頻
void CEricMeidaPlayerDlg::OnBnClickedBtnStop()
{
m_VideoControl.VideoStop();
}
//全屏
void CEricMeidaPlayerDlg::OnBnClickedBtnFull()
{
m_VideoControl.FullScreen();
}
5)實現視頻窗口(IDC_WND_VIDEO)單擊事件,用於從全屏狀態回到正常狀態,如下所示。
//視頻窗口單擊事件
//用於從全屏狀態回到正常狀態
void CEricMeidaPlayerDlg::OnStnClickedWndVideo()
{
if (m_VideoControl.GetFullScreenStatus())
{
//切換到正常狀態
m_VideoControl.FullScreen();
}
}
6)實現媒體播放事件通知消息(WM ORAPHNOTIFY)函數。
首先爲CEricMediaPlayerDlg類添加自定義消息處理函數定義,定義以下:
//媒體插放事件消息處理函數
afx_msg LRESULT OnNotifyMedia(WPARAM wParam, LPARAM lParam);
注意,該定義應添加在DECLARE_MESSAOE_MAP()語句以前。
OnNotifyMedia函數的實現如下所示。
//媒體播放事件通知
LRESULT CEricMeidaPlayerDlg::OnNotifyMedia(WPARAM WParam, LPARAM LParam)
{
long lEventCode;
if (m_VideoControl.GetMediaEvent(&lEventCode))
{
//收到播放結束事件
if (lEventCode == EC_COMPLETE)
{
if (m_VideoControl.VideoStop())
{
//
}
}
}
return (LRESULT)0;
}
最後,須要將OnNotifyMedia消息處理函數和消息WM GRAPHNOTIFY關聯起來,即在
BEGIN_MESSAGE_MAP宏中添加以下代碼實現關聯:
ON_MESSAGE(WM_GRAPHNOTIFY, OnNotifyMedia)
7)添加1個定時器,用於更新媒體播放進度。
在BOOL CEricMeidaPlayerDlg::OnInitDialog()函數中添加以下代碼,用於啓動1個定時器。
//啓動定時器,用於更新媒體播放進度
SetTimer(1,1000,NULL);
定時器消息處理函數的實現以下所示。
//WM_TIMER,定時器處理函數
void CEricMeidaPlayerDlg::OnTimer(UINT_PTR nIDEvent)
{
//獲得進度條窗口
CProgressCtrl *pPrgWnd = (CProgressCtrl*)GetDlgItem(IDC_PRG_VIDEO);
DWORD dwPos = 0;
//獲得媒體當前播放進度
m_VideoControl.GetCurrentPos(dwPos);
//設置進度條位置
pPrgWnd->SetPos(dwPos);
CDialog::OnTimer(nIDEvent);
}
至此,媒體播放器示例就編寫完成了。
打開Windows Embedded Compact 7虛擬機,將該程序複製到共享目錄,
執行之,如圖10-7
圖10-7選擇程序並運行
運行效果以下圖10-8
圖10-8程序效果圖
單擊「打開」按鈕,選擇一個電影,咱們這裏選擇鴻門宴的剪輯,而後單擊「播放」按鈕就能夠觀看視頻了。
圖10-9播放電影圖