隨著計(jì)算機(jī)技術(shù)、網(wǎng)絡(luò)技術(shù)迅猛發(fā)展,在嵌入式電腦上,多媒體功能變得越來(lái)越普及。倒如現(xiàn)在很多智能手機(jī)、PDA以及嵌入式電腦部具有聽(tīng)MP3、觀看VIDEO視頻以及使用攝像頭拍照、錄像等功能,這些功能極大地增強(qiáng)了用戶(hù)體驗(yàn)。微軟提供Direct Show技術(shù)標(biāo)準(zhǔn),使得可以非常方便地開(kāi)發(fā)多媒體應(yīng)用程序。本章就來(lái)重點(diǎn)介紹Direct Show技術(shù),并以實(shí)例來(lái)展現(xiàn)Direct Show技術(shù)應(yīng)用。 本章主要介紹DirectShow的相關(guān)知識(shí),以及如何利用DirectShow來(lái)進(jìn)行多媒體編程。本章還提供了在Windows Embedded Compact 7環(huán)境下使用DirectShow編程的兩個(gè)實(shí)例。 10.1 Direct Show介紹 DirectShow有時(shí)簡(jiǎn)稱(chēng)為DS或DShow,它是微軟公司對(duì)之前Windows視頻技術(shù)的一次更新,是微軟公司在ActiveMovie和Video for Windows的基礎(chǔ)上推出的新一代基于組建對(duì)象模型(Component Object Model,COM)的流媒體處理開(kāi)發(fā)包。作為DirectX大家族中的一員,DirectShow為Windows平臺(tái)上處理各種格式的媒體文件播放、音頻和視頻采集等高性能的多媒體應(yīng)用提供了完整的解決方案。DirectShow 9.0之前的版本與DirectX開(kāi)發(fā)包一起發(fā)布,之后成為Windows SDK的一部分。 DirectShow為大部分微軟公司程序設(shè)計(jì)語(yǔ)言提供了一個(gè)媒體的通用接口,而且是一個(gè)基于Filter并能在用戶(hù)或開(kāi)發(fā)者的命令下播放或記錄媒體文件的可擴(kuò)展框架。DirectShow的應(yīng)用很廣泛。通過(guò)DirectShow,軟件開(kāi)發(fā)者能夠?qū)SF、MPEG、DV、MP3、WAVE等格式的媒體文件執(zhí)行各種不同的處理。例如,對(duì)于本地應(yīng)用,開(kāi)發(fā)者可以利用DirectShow實(shí)現(xiàn)不同格式媒體文件的解碼播放以及不同媒體格式間的相互轉(zhuǎn)換,可以從本地機(jī)器的采集設(shè)備捕獲音視頻數(shù)據(jù)并保存為文件,可以接受并觀看模擬電視等。此外,DirectShow可用于視頻點(diǎn)播、視頻監(jiān)控以及視頻會(huì)議等網(wǎng)絡(luò)應(yīng)用。廣而言之,DirectShow還可以用于除了音視頻等多媒體數(shù)據(jù)以外的其他流式數(shù)據(jù)的處理。 在Windows Embedded Compact 7中,我們同樣可以使用DirectShow來(lái)捕獲、回放和轉(zhuǎn)換多媒體流數(shù)據(jù)。接下來(lái)我們將分別介紹DirectShow技術(shù)框架、過(guò)濾器(Filter)、過(guò)濾器圖表管理器(Filter Graph Manager)、PIN以及DirectShow接口定義。 介紹下DirectX家族。DirectX是一個(gè)多煤休API,它提供標(biāo)準(zhǔn)接口來(lái)與圖形卡、聲卡、輸入設(shè)備等進(jìn)行交互。如果沒(méi)有這組標(biāo)準(zhǔn)API,用戶(hù)就需要為圖形卡和聲卡的每個(gè)組臺(tái)和每種類(lèi)型的鍵盤(pán)、鼠標(biāo)和游戲桿編寫(xiě)不同的代碼。DirectX從具體的硬件中抽象出來(lái),并且將一組通用指令轉(zhuǎn)換成硬件的具體命令。 DirectX的家族成員很多,而且各有各的本領(lǐng),類(lèi)似于DirectDraw和Direct3D負(fù)責(zé)二維圖形圖像,三維動(dòng)面加速、DirectMusic和DirectSound負(fù)責(zé)交互式音樂(lè),環(huán)境音效處理一樣, DirectShow為Windows平臺(tái)上處理各種格式的媒體文件播放,音視頻采集等高性能要求的多媒體應(yīng)用,提供了完整的解決方案。 DirectShow是微軟公司提供的一套在Windows平臺(tái)上進(jìn)行流媒體處理的開(kāi)發(fā)包,與 DirectX開(kāi)發(fā)包一起發(fā)布。DirectShow是Windows平臺(tái)上的流媒體框架,提供了高質(zhì)量的多媒體流采集和回放功能。它支持多種多樣的媒體文件格式,包括ASF、MPEO、AVI. MP3和WAV文件,目時(shí)支持使用WDM驅(qū)動(dòng)或早期的VFW驅(qū)動(dòng)來(lái)進(jìn)行多媒體流的采集。DirectShow整合了其他的DirectX技術(shù),能自動(dòng)偵測(cè)井使用可利用的音視頻硬件加速,也能支持沒(méi)有硬件加速的系統(tǒng)。 Microsoft通過(guò)DirectShow為多媒體程序開(kāi)發(fā)員提供了標(biāo)準(zhǔn)的、統(tǒng)一的、高效的API接口。 DirectShow技術(shù)是建立在DirectX的DirectDraw和DirectSound的基礎(chǔ)之上的,它通過(guò) DirectShow對(duì)顯卡進(jìn)行控制以顯示視師,通過(guò)DirectSound對(duì)聲卡進(jìn)行控制以播放聲音。 DirectX為了最大限度提高效率而允許用戶(hù)直接訪問(wèn)硬件,如允許用戶(hù)直接讀寫(xiě)顯存,因此,DirectShow也同樣具有快速的優(yōu)勢(shì)。 下面就對(duì)DirectShow的關(guān)鍵技術(shù)點(diǎn)做簡(jiǎn)要介紹,以便讀者對(duì)DirectShow技術(shù)有了初步了解,也便于理解后面講述的媒體播放器、攝像頭捕捉示例代碼。DirectShow是一門(mén)非常系統(tǒng)的技術(shù),關(guān)于更為詳細(xì)的技術(shù)細(xì)節(jié),請(qǐng)讀者參考相關(guān)專(zhuān)業(yè)DirectShow書(shū)籍或者MSDN。 10.1.1 DirectShow技術(shù)框架 DirectShow的技術(shù)框架如圖10-1所示。看了此框架圖就會(huì)發(fā)現(xiàn)DirectShow的工作原理也非常簡(jiǎn)單、清晰。此圖可以簡(jiǎn)單理解成3部分,輸入→邏輯處理→輸出,這是一個(gè)典型的計(jì)算機(jī)處理流程。輸入部分:它可以是本地視頻、音頻文件、Internet網(wǎng)絡(luò)、視頻和音頻采集卡:輸出部分:它可以是本地文件、顯示器(顯卡)和聲卡;邏輯處理部分:也就是DirectShow的核心部分,從圖上看包括:源過(guò)濾器、傳輸過(guò)濾器、渲染過(guò)濾器以及管理這3個(gè)過(guò)濾器的過(guò)濾器圖管理器對(duì)象。 圖10-1 DirectShow 技術(shù)框架圖 在應(yīng)用程序中,為了完成對(duì)多媒體數(shù)據(jù)的處理,需要將若干過(guò)濾器連接起來(lái),一個(gè)的輸出作為另一個(gè)的輸入,這樣連接在一起的一組過(guò)濾器稱(chēng)為過(guò)濾器圖(Filter Graph)。過(guò)濾器圖也決定著每一步該使用哪一個(gè)過(guò)濾囂及這些過(guò)濾器之間是如何連接的。這樣,多媒體數(shù)據(jù)流就在過(guò)濾器流水線上,從源過(guò)濾器經(jīng)由中間過(guò)濾器移動(dòng)到播放過(guò)濾器,從而最終完成播放。在這個(gè)過(guò)程中完成了對(duì)數(shù)據(jù)的讀取、解碼、將數(shù)據(jù)輸出到相應(yīng)的設(shè)備、播放等操作。 10.1.2過(guò)濾器( Filter) DirectShow是基于模塊化的開(kāi)發(fā)框架,每個(gè)功能操作如捕獲、回放和轉(zhuǎn)換都采用COM組件方式,每個(gè)COM組件就稱(chēng)為Filter。Filter是完成DirectShow處理過(guò)程的基本單元。DirectShow提供了一系列標(biāo)準(zhǔn)的Filters用于應(yīng)用開(kāi)發(fā),開(kāi)發(fā)者也可以開(kāi)發(fā)自己的Filter來(lái)擴(kuò)展DirectShow的功能,但必須是以COM形式建立的。DirectX為用戶(hù)提供了DirectShow基類(lèi)庫(kù)(DirectShow Base Class Library),用戶(hù)自定義的過(guò)濾器都可以從基類(lèi)庫(kù)提供的基類(lèi)和接口派生出來(lái)。 過(guò)濾器主要分為以下幾種類(lèi)型: 在DirectShow中,一組相連的過(guò)濾器稱(chēng)為一個(gè)過(guò)濾器圖表(Filter Graph),從而完成比單個(gè)過(guò)濾器更復(fù)雜的多媒體任務(wù)。過(guò)濾器圖表用來(lái)連接過(guò)濾器以控制媒體流,它也可以將數(shù)據(jù)返回給應(yīng)用程序,并搜索所支持的過(guò)濾器。過(guò)濾器有三種可能的狀態(tài):運(yùn)行、停止和暫停。暫停是一種中間狀態(tài),停止?fàn)顟B(tài)到運(yùn)行狀態(tài)必定經(jīng)過(guò)暫停狀態(tài)。暫??梢岳斫鉃閿?shù)據(jù)就緒狀態(tài),是為了快速切換到運(yùn)行狀態(tài)而設(shè)計(jì)的。在暫停狀態(tài)下,數(shù)據(jù)線程是啟動(dòng)的,但被渲染過(guò)濾器阻塞了。通常情況下,過(guò)濾器圖表中所有過(guò)濾器的狀態(tài)是一致的。 過(guò)濾器接收輸入和產(chǎn)生輸出,信息通過(guò)過(guò)濾器管腳( PIN)在過(guò)濾器之間傳遞。一個(gè)管腳(PIN)是一個(gè)過(guò)濾器端口,它可以是輸入端口也可以是輸出端口。 過(guò)濾器Filter具有三個(gè)狀態(tài):運(yùn)行、停止、暫停。當(dāng)一個(gè)Filter運(yùn)行時(shí),它就處理媒體數(shù)據(jù)流:當(dāng)停止時(shí),Filter就不再處理數(shù)據(jù),暫停狀態(tài)用于臨時(shí)暫停過(guò)濾器運(yùn)行。 10.1.3 Filter Graph Manager DirectShow專(zhuān)門(mén)提供了一個(gè)高級(jí)接口——過(guò)濾器圖表管理器(Filter Graph Manager,F(xiàn)GM)——用來(lái)控制過(guò)濾器圖表中的過(guò)濾器。同時(shí),它也能為一個(gè)特定的媒體文件選配過(guò)濾器,渲染播放該文件。過(guò)濾器圖表管理器是COM 形式的,它的功能有:協(xié)調(diào)過(guò)濾器間的狀態(tài)轉(zhuǎn)變;建立參考時(shí)鐘;把事件(event)傳送給應(yīng)用程序;為應(yīng)用程序提供建立過(guò)濾器圖表的方法等。 在開(kāi)發(fā)基于DirectShow技術(shù)的應(yīng)用程序時(shí),都必須創(chuàng)建多個(gè)過(guò)濾器(Filter)并進(jìn)行恰當(dāng)?shù)倪B接,于是數(shù)據(jù)流就可以從源過(guò)濾器輸入,經(jīng)過(guò)一系列過(guò)濾器(Filter)傳遞,最后到達(dá)RenderFilter輸出,被用戶(hù)所使用。這些過(guò)濾器的集合就叫做過(guò)濾器圖(Filter Graph)。圖10-2演示如何使用Filter來(lái)播放AVI視頻文件。 圖10-2播放帶聲音的視頻文件過(guò)濾器圖形 下面簡(jiǎn)要介紹采用Filter播放視頻文件的流程。本流程將會(huì)在本章第2節(jié)的媒體播放器中 被具體代碼實(shí)現(xiàn)。 (l)首先從一個(gè)文件中讀取AVI數(shù)據(jù),形成字節(jié)流。(這個(gè)工作由源Filter完成)。 (2)檢查AVI數(shù)據(jù)流的頭格式,然后通過(guò)AVI分割Filter,將視頻流和音頻流分開(kāi)。 (3)解碼視頻流,根據(jù)壓縮格式的不同,選取不同的decoder filters。 (4)通過(guò)Renderer Filter重畫(huà)視頻圖像。 (5)音頻流送到聲卡進(jìn)行播放,一般采用缺省的DirectSound Device Filter。 Filter Graph Manager也是一個(gè)COM對(duì)象,用來(lái)控制Filter Graph中所有的Filter,主要有以下的功能: 協(xié)調(diào)Filters之間的狀態(tài)改變。Filters的狀態(tài)改變必須以一種特殊順序發(fā)生。因此,應(yīng)用程序并不將狀態(tài)改變的命令直接發(fā)給Filter,而是發(fā)送給Filter Graph Manager一個(gè)簡(jiǎn)單命令,由Manager將命令分發(fā)給Graph中的每一個(gè)Filter。Seeking也是按同樣的方式工作,先由應(yīng)用程序?qū)?/span>Seek命令發(fā)送到Filter Graph Manager,然后由其分發(fā)給每個(gè)Filters。 建立參考時(shí)鐘。Graph中的Filter都采用同一個(gè)時(shí)鐘,稱(chēng)為參考時(shí)鐘(Reference Clock),參考時(shí)鐘可以確保所有的數(shù)據(jù)流同步。視頻楨或者音頻Sample應(yīng)該被提交的時(shí)間稱(chēng)為Presentation Time,它是相對(duì)于參考時(shí)鐘來(lái)確定的。Filter Graph Manager應(yīng)該選擇一個(gè)參考時(shí)鐘,可以選擇聲卡上的時(shí)鐘,也可以選擇系統(tǒng)時(shí)鐘。 傳遞事件到應(yīng)用程序。Filter Graph Manager采用事件隊(duì)列機(jī)制將Graph中發(fā)生的事件通知給應(yīng)用程序,這個(gè)機(jī)制類(lèi)似于Windows的消息循環(huán)。 提供應(yīng)用程序建立Filter Graph的方法。Filter Graph Manager給應(yīng)用程序提供了將Filter添加進(jìn)Graph的方法、連接Filter以及斷開(kāi)Filter連接的方法。 Filter Graph Manager沒(méi)有的一個(gè)功能就是把數(shù)據(jù)從一個(gè)Filter移動(dòng)到另一個(gè)Filter,這是由Filters自己通過(guò)它們的Pin連接完成的。處理過(guò)程總是在不同的線程進(jìn)行。 10.1.4 Pin 過(guò)濾器可以和一個(gè)或多個(gè)過(guò)濾器相連,連接的單向接口也是COM形式的,稱(chēng)為引腳,就是Pin。過(guò)濾器利用引腳在各個(gè)過(guò)濾器間傳輸數(shù)據(jù)。每個(gè)引腳都是從Ipin這個(gè)COM對(duì)象派生出來(lái)的。每個(gè)引腳都是過(guò)濾器的私有對(duì)象,過(guò)濾器可以動(dòng)態(tài)的創(chuàng)建引腳,銷(xiāo)毀引腳,自由控制引腳的生存時(shí)間。引腳可以分為輸入引腳(Input pin)和輸出引腳(Output pin)兩種類(lèi)型,兩個(gè)相連的引腳必須是不同種類(lèi)的,即輸入引腳只能和輸出引腳相連,且連接的方向總是從輸出引腳指向輸入引腳。 10.1.5 DirectShow接口定義介紹 上面簡(jiǎn)要介紹了DirectShow技術(shù)框架,下面就來(lái)介紹DirectShow SDK提供的相關(guān)COM接口,通過(guò)這些接口可以非常方便地實(shí)現(xiàn)DirectShow應(yīng)用編程,DirectShow常用接口如表10-1所示。 表 10-1DirectShow常用接口
10.2媒體播放器示例 Windows Embedded Compact7支持使用DirectShow技術(shù)來(lái)捕獲、播放和轉(zhuǎn)換多媒體內(nèi)容。嵌入式設(shè)備通常包含音頻和視頻硬件解碼器。由于嵌入式設(shè)備中的硬件專(zhuān)屬于嵌入式平臺(tái),開(kāi)發(fā)者需要實(shí)現(xiàn)DirectShow過(guò)濾器來(lái)讓DirectShow在特定的平臺(tái)運(yùn)行。為了盡快實(shí)現(xiàn)一個(gè)DirectShow過(guò)濾器,一般推薦在WINCE7下使用DirectShow基類(lèi)來(lái)完成。本節(jié)主要介紹利用DirectShow在WINCE平臺(tái)上如何實(shí)現(xiàn)一個(gè)媒體播放器。一個(gè)播放過(guò)濾器圖表包括一個(gè)源過(guò)濾器,一個(gè)多路分配過(guò)濾器,一個(gè)活多個(gè)解碼器濾波器以及一個(gè)或多個(gè)渲染過(guò)濾器。對(duì)于某些格式,WINCE7還包括一個(gè)新的緩沖過(guò)濾器,即Buffering Stream Filter。該過(guò)濾器位于源過(guò)濾器和多路分配器之間,管理IMediaSample緩沖池。如果使用Buffering Stream Filter,下行的過(guò)濾器只需要很少的緩沖區(qū)管理工作。 圖3提供了一個(gè)包含Buffering Stream Filter的過(guò)濾器圖表。值得一提的是,在圖10-3中,有的過(guò)濾器由操作系統(tǒng)中提供,有的過(guò)濾器是平臺(tái)特有的,還有過(guò)濾器既可由操作系統(tǒng)提供,也可以為平臺(tái)特有。 圖10-3 DirectShow 實(shí)現(xiàn)媒體播放器的過(guò)濾器圖表 當(dāng)然,我們?cè)趯?shí)現(xiàn)視頻和音頻解碼器、渲染器時(shí)可以有不同的選擇。例如,可以利用一個(gè)軟件模塊將視頻解碼器和渲染器合并,另一個(gè)軟件模塊將音頻解碼器和渲染器合并。還有一種方式是創(chuàng)建單個(gè)模塊,該模塊包括視頻解碼器和渲染器、音頻解碼器和渲染器。取決于不同的實(shí)現(xiàn)方式,圖10-3所示的過(guò)濾器圖表可修改為圖10-4所示的配置。 圖10-4 媒體播放器的過(guò)濾器圖表的其他配置 如果某些媒體格式不需要緩沖流過(guò)濾器,圖3和圖4中的Buffering Stream Filter可以去除,此時(shí)源過(guò)濾器和多路分配過(guò)濾器直接相連。 WINCE7所支持的編解碼格式如表2所示。 表10-2 WINCE7支持的DirectShow編解碼器 在實(shí)現(xiàn)DirectShow過(guò)濾器時(shí),我們必須實(shí)現(xiàn)一些接口和方法。為了減少需要開(kāi)發(fā)的代碼量,DirectShow基類(lèi)提供了許多通用代碼,所以我們推薦在過(guò)濾器開(kāi)發(fā)過(guò)程中盡可能地使用基類(lèi)。一個(gè)過(guò)濾器的主要功能部分包括輸入引腳,輸出引腳以及過(guò)濾器本身的邏輯。但是需要注意的是,特殊的過(guò)濾器可能沒(méi)有輸入引腳或輸出引腳。例如,如圖2所示,源過(guò)濾器沒(méi)有輸入引腳,渲染過(guò)濾器沒(méi)有輸出引腳。一般而言,我們繼承一些相關(guān)的基類(lèi)來(lái)開(kāi)發(fā)一個(gè)類(lèi)所對(duì)應(yīng)的輸入引腳、輸出引腳以及過(guò)濾器邏輯。 圖10-5DirectShow解碼器過(guò)濾器類(lèi)圖 在第1節(jié)中簡(jiǎn)要介紹了Direct Show的基本技術(shù)框架,下面就以一個(gè)實(shí)際的媒體播放器為例進(jìn)行說(shuō)明,使讀者對(duì)應(yīng)用DirectShow編程有更深入地了解。本倒是通過(guò)DirectShow的IFilterGpaph->Render函數(shù)來(lái)自動(dòng)渲染視頻、音頻文件。這里能夠播放哪些媒體文件,跟系統(tǒng)注冊(cè)相關(guān)的Filter有很大關(guān)系。 下面就來(lái)講述如何利用DirectShow技術(shù)來(lái)搭建一個(gè)媒體播放器。 (l)建立新項(xiàng)目。 使用VS2008選擇智能設(shè)備模板,利用MFC智能設(shè)備應(yīng)用程序向?qū)?chuàng)建一個(gè)基于對(duì)話(huà)框的應(yīng)用程序EricMeidaPlayer,編譯環(huán)境設(shè)置為yinchengOS SDK。 (2)新建CEricMediaControl類(lèi),用于封裝媒體文件播放等功能。 CEricMediaControl類(lèi)是一個(gè)通用c++類(lèi),無(wú)基類(lèi)。其類(lèi)圖如圖10-6所示。 圖10-6 媒體播放類(lèi)圖 CEricMediaControl類(lèi)基于DirectShow技術(shù)并且封裝了媒體播放技術(shù),提供打開(kāi)媒體文件、播放、暫停、終止、全屏、設(shè)置當(dāng)前播放位置等常用方法。基于CEricMediaControl類(lèi)可以非常方便地制作媒體播放程序,讀者也可以在此類(lèi)的基礎(chǔ)上加以修改升級(jí),以實(shí)現(xiàn)更強(qiáng)的功能。 下面就來(lái)具體實(shí)現(xiàn)CEricMediaControl類(lèi)。 1)為CEricMediaControl類(lèi)添加如下私有變量,用于定義播放媒體文件播放所需的 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)定義媒體播放事件對(duì)應(yīng)的WINDOWS消息常量WM_GRAPHNOTIFY,定義如下: //定義DSHOW事件通知消息 #define WM_GRAPHNOTIFY WM_USER+ 101 3)實(shí)現(xiàn)CEricMediaControl類(lèi)的構(gòu)造函數(shù)和析構(gòu)函數(shù)。在構(gòu)造函數(shù)里,將DSHOW相關(guān) 接口初始化為NULL,并初始化COM環(huán)境;在析構(gòu)函數(shù)里,釋放DSHOW接口并釋放COM 環(huán)境。CEricMediaControl類(lèi)構(gòu)造函數(shù)和析構(gòu)函數(shù)的實(shí)現(xiàn)如下所示。 //構(gòu)造函數(shù) 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 環(huán)境 CoInitialize(NULL); } //析構(gòu)函數(shù) CEricMediaControl::~CEricMediaControl(void) { //釋放DSHOW接口 UnInitDShow(); //釋放COM 環(huán)境 CoUninitialize(); } 4)為CEricMediaControl類(lèi)添加InitDShow和UninitDShow兩個(gè)私有方法。InitDShow方法用于初始化DShow接口并渲染媒體文件;UnlnitDShow方法用于釋放DShow接口。讀者應(yīng)特別注意InitDShow方法,該方法直接描述了如何利用DShow接口逐步播放視頻文件。 這個(gè)兩個(gè)方法的定義如下: private: //初始化DSHOW接口 BOOL InitDShow(LPCTSTR strFileName /*視頻文件名*/ ,HWND hOwnerWnd /*顯示視頻的窗口句柄*/ ,HWND hNotifyWnd /*接收DSHOW事件消息的串口句柄*/ ); //釋放DSHOW接口 BOOL UnInitDShow(); InitDShow和UnlnitDShow方法的實(shí)現(xiàn)如下所示。 /* *函數(shù)介紹: 初始化DShow接口,并渲染好視頻文件 *入口參數(shù): strFileName: 視頻文件名 hOwnerWnd: 顯示視頻的窗口句柄 hNotifyWnd: 接收DSHOW事件消息的串口句柄 *出口參數(shù):(無(wú)) *返回值:TRUE:成功;FALSE:失敗 */ BOOL CEricMediaControl::InitDShow(LPCTSTR strFileName /*視頻文件名*/ ,HWND hOwnerWnd /*顯示視頻的窗口句柄*/ ,HWND hNotifyWnd /*接收DSHOW事件消息的串口句柄*/ ) { HRESULT hResult; //第1步:創(chuàng)建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; } //設(shè)置查找定位的時(shí)間單位,這里設(shè)置: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; } //設(shè)置媒體事件通知消息窗口 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; } //設(shè)置視頻播放窗口句柄 m_pVW->put_Owner((OAHWND)hOwnerWnd); //設(shè)置視頻窗口格式 m_pVW->put_WindowStyle(WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN); //第7步:得到基礎(chǔ)視頻流接口 hResult = m_pGB->QueryInterface(IID_IBasicVideo, (void **)&m_pBV); if (hResult != S_OK) { //釋放DSHOW接口 UnInitDShow(); return FALSE; } //第8步:得到基礎(chǔ)音頻流接口 hResult = m_pGB->QueryInterface(IID_IBasicAudio, (void **)&m_pBA); if (hResult != S_OK) { //釋放DSHOW接口 UnInitDShow(); return FALSE; } return TRUE; } /* *函數(shù)介紹: 卸載DShow系列接口 *入口參數(shù): (無(wú)) *出口參數(shù):(無(wú)) *返回值: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); //設(shè)置視頻播放窗口句柄為空 m_pVW->put_Owner(NULL); m_pVW->Release(); m_pVW = NULL; } //4,釋放基礎(chǔ)音頻流接口 if (m_pBA != NULL) { m_pBA->Release(); m_pBA = NULL; } //5,釋放基礎(chǔ)視頻流接口 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類(lèi)添加4個(gè)共有方法,供外部調(diào)用。這4個(gè)方法分別為OpenFile(打開(kāi)視頻文件)、VfideoRun(視頻播放)、VideoPause(視頻暫停)、VideoStop(視頻停止)。打開(kāi)視頻文件方法,調(diào)用前面講述的InitDShow方法,來(lái)初始化視頻播放文件。視頻播放、暫停、停止等方法通過(guò)IMediaControl接口調(diào)用相關(guān)方法來(lái)實(shí)現(xiàn)媒體流的控制。 這4個(gè)方法具體定義如下: public: //打開(kāi)視頻文件 BOOL OpenFile(LPCTSTR strFileName /*視頻文件名*/ ,HWND hOwnerWnd /*顯示視頻的窗口句柄*/ ,HWND hNotifyWnd /*接收DSHOW事件消息的串口句柄*/ ); //播放視頻 BOOL VideoRun(); //暫停視頻 BOOL VideoPause(); //停止視頻 BOOL VideoStop(); 這4個(gè)方法的實(shí)現(xiàn)如下所示。 /* *函數(shù)介紹:打開(kāi)視頻文件 *入口參數(shù):strFileName:視頻文件名 hOwnerWnd:顯示視頻的窗口句柄 hNotifyWnd:接收DSHOW事件消息的串口句柄 *出口參數(shù):(無(wú)) *返回值:TRUE,成功打開(kāi)視頻文件;FALSE:打開(kāi)視頻文件失敗 */ BOOL CEricMediaControl::OpenFile(LPCTSTR strFileName /*視頻文件名*/ ,HWND hOwnerWnd/*顯示視頻的窗口句柄*/ ,HWND hNotifyWnd/*接收DSHOW事件消息的串口句柄*/ ) { //存儲(chǔ)顯示視頻窗口句柄 m_hOwnerWnd = hOwnerWnd; //重置DSHOW接口 UninitDShow(); //打開(kāi)視頻文件,并對(duì)DSHOW接口做初始化工作 return InitDShow( strFileName, hOwnerWnd,hNotifyWnd); } /* *函數(shù)介紹:括放視頻 *入口參數(shù);(無(wú)) *出口參數(shù):(無(wú)) *返回值:TRUE:成功;FALSE:失敗 */ BOOL CEricMediaControl::VideoRun() //檢測(cè)IMediaControl控制接口有效性 if(m_pMC== NULL) { Return FALSE; } //播放視頻 HRESULT hResult = m_pMC->Run(); if(hResult !=S_OK) { Return FALSE; } Return TRUE; } /* *函數(shù)介紹;暫停視頻 *入口參數(shù):(無(wú)) *出口參數(shù):(無(wú)) *返回值: TRUE:成功:FALSE:失敗 */ BOOL CEricMediaControl::VideoPause() //檢測(cè)IMediaControl控制接口有效性 if (m_pMC==NULL) { return FALSE; } //暫停視頻 HRESULT hResult = m_pMC->Pause(); If(hResult i= S_OK) { Return FALSE; } Return TRUE; } /* *函數(shù)介紹:停止視頻 *入口參數(shù):(無(wú)) *出口參數(shù):(無(wú)) *返回值:TRUE:成功;FALSE:失敗 */ BOOL CEricMediaControl::VideoStop() { HRESULT hResult; //檢測(cè)IMediaControl控制接口有效性 if(m_pMC ==NULL) { Return FALSE; } //停止視頻 hReSult = m_pMC->Stop(); if (hResult != S_OK) { Return FALSE; } //將當(dāng)前播放位置置0 LONGLONG pos:0; hResult : m_pMS->SetPositions(&pos, AM_SEEKIFIG_AbsolutePositionin9 NULL, AM SEEKING NoPositioning); 6)為類(lèi)CEricMediaControl類(lèi)添加7個(gè)共有方法,用于設(shè)置媒體播放屬性以及得到媒體播放屬性。這7個(gè)方法分別為FitVideoWindow(設(shè)置視頻顯示比例)、FuIIScreen(全屏顯示)、GetFuIIScreenStatus(得到是否是全屏顯示)、GetMediaEvent(得到DShow播放事件)、SetPositions(設(shè)置播放進(jìn)度)、GetCurrentPos(得到當(dāng)前播放進(jìn)度)、GelDuration(得到媒體文件時(shí)間長(zhǎng)度)。具體定義如下: public: //設(shè)置視頻顯示比例 BOOL FitVideoWindow(FLOAT fScale); //全屏顯示 BOOL FullScreen(); //得到是否是全屏顯示 BOOL GetFullScreenStatus(); //得到DShow播放事件 BOOL GetMediaEvent(long *lEventCode); //設(shè)置播放進(jìn)度,單位: 秒 BOOL SetPositions(DWORD dwPos /*設(shè)置當(dāng)前播放進(jìn)度*/); //得到視頻播放當(dāng)前的位置,單位:秒 BOOL GetCurrentPos(DWORD &dwPos /*out 當(dāng)前播放進(jìn)度*/); //得到視頻文件時(shí)間長(zhǎng)度,單位:秒 BOOL GetDuration(DWORD &dwLength); 這7個(gè)函數(shù)的實(shí)現(xiàn)如下所示。 /* *函數(shù)介紹: 設(shè)置視頻顯示比例 *入口參數(shù): fScale : 顯示比例, <= 1.0 *出口參數(shù): (無(wú)) *返回值:TRUE:成功;FALSE:失敗,此處有點(diǎn)問(wèn)題 */ 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; } //設(shè)置拉伸后的尺寸 lWidth = lWidth * fScale; lHeight = lHeight * fScale; //得到視頻播放窗口的尺寸 GetClientRect(m_hOwnerWnd, &clientRect); lDeflateX = (clientRect.Width() - clientRect.Width() * fScale) / 2; lDeflateY = (clientRect.Height() - clientRect.Height() * fScale) / 2; //重新設(shè)置客戶(hù)區(qū)域 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)); } } //設(shè)置視頻播放位置 m_pVW->SetWindowPosition(dstRect.left,dstRect.top,dstRect.Width(),dstRect.Height()); return TRUE; } /* *函數(shù)介紹: 全屏顯示 *入口參數(shù): (無(wú)) *出口參數(shù): (無(wú)) *返回值:TRUE:成功;FALSE:失敗 */ BOOL CEricMediaControl::FullScreen() { LONG lMode = 0; static HWND hDrain=0; if (m_pBV == NULL) { return FALSE; } //得到全屏狀態(tài) 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 ); //設(shè)置全屏幕 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; } /* *函數(shù)介紹: 得到是否全屏幕播放 *入口參數(shù): (無(wú)) *出口參數(shù): (無(wú)) *返回值: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; } } /* *函數(shù)介紹: 得到媒體事件 *入口參數(shù): (無(wú)) *出口參數(shù): 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; } /* *函數(shù)介紹: 設(shè)置播放進(jìn)度 *入口參數(shù): dwPos :播放進(jìn)度,單位秒 *出口參數(shù): (無(wú)) *返回值:TRUE:成功;FALSE:失敗 */ BOOL CEricMediaControl::SetPositions(DWORD dwPos /*設(shè)置當(dāng)前播放進(jìn)度*/) { //設(shè)置絕對(duì)位置,轉(zhuǎn)化成納秒為單位 LONGLONG llPos = dwPos * 10000 * 1000; if (m_pMS == NULL) { return FALSE; } //設(shè)置視頻播放當(dāng)前位置 HRESULT hr = m_pMS->SetPositions(&llPos,AM_SEEKING_AbsolutePositioning , NULL, AM_SEEKING_NoPositioning); if (hr == S_OK) { return TRUE; } else { return FALSE; } } /* *函數(shù)介紹: 得到視頻文件播放長(zhǎng)度,單位秒 *入口參數(shù): dwLength :視頻文件時(shí)間長(zhǎng)度 *出口參數(shù): dwLength :視頻文件時(shí)間長(zhǎng)度 *返回值:TRUE:成功;FALSE:失敗 */ BOOL CEricMediaControl::GetDuration(DWORD &dwLength) { dwLength = 0; if (m_pMS == NULL) { return FALSE; } //得到視頻總時(shí)間長(zhǎng)度 LONGLONG llDuration; HRESULT hResult = m_pMS->GetDuration(&llDuration); if (hResult != S_OK) { return FALSE; } //轉(zhuǎn)換成以秒為單位 llDuration = llDuration / 10000 ; llDuration = llDuration / 1000; dwLength = (DWORD)llDuration; return TRUE; } /* *函數(shù)介紹: 得到媒體當(dāng)前播放進(jìn)度位置,單位秒 *入口參數(shù): dwPos :當(dāng)前播放進(jìn)度,單位秒 *出口參數(shù): dwPos :當(dāng)前播放進(jìn)度,單位秒 *返回值:TRUE:成功;FALSE:失敗 */ BOOL CEricMediaControl::GetCurrentPos(DWORD &dwPos) { dwPos = 0; LONGLONG llPos = 0; if (m_pMS== NULL) { return FALSE; } //得到當(dāng)前視頻播放位置 HRESULT hResult = m_pMS->GetCurrentPosition(&llPos); if (hResult != S_OK) { return FALSE; } //轉(zhuǎn)換成以秒為單位 llPos = llPos / 10000 ; llPos = llPos / 1000; dwPos = (DWORD)llPos; return TRUE; } 至此,CEricMediaContorl類(lèi)就編寫(xiě)完成了。下面將基于CEricMediaContorl類(lèi)演示如何使用它實(shí)現(xiàn)播放視頻、音頻文件的功能。若此時(shí)編譯工程,將會(huì)發(fā)現(xiàn)有一堆連接錯(cuò)誤提示。那是因?yàn)闆](méi)有鏈接strmiids.lib靜態(tài)庫(kù)。 選擇“EricMediaPlayer屬性頁(yè)”一“鏈接囂” 一“輸入”一“附加依賴(lài)項(xiàng)”,輸入strmiids.lib,即可無(wú)誤地編譯本工程。 (3)設(shè)計(jì)媒體播放器主窗口。 1) include“CEricMedia Control.h” 2)為CEricMeidaPlayerDlg類(lèi)添加mVideoControl成員變量,類(lèi)型為之前創(chuàng)建的CEricMediaControl類(lèi)。定義如下。 privrate: CEricMediaControl m_VideoControl;//媒體播放對(duì)象 備注:由于引用了CEricMediaControl類(lèi),因此需要在EricMediaPJayerDlg.h文件中添加對(duì)CEricMediaControl類(lèi)定義文件的引用。
3)實(shí)現(xiàn)“打開(kāi)”按鈕單擊事件。單擊“打開(kāi)”按鈕,將可以選擇要播放的媒體文件。“打開(kāi)”按鈕單擊事件的實(shí)現(xiàn)如下所示。 //打開(kāi)視頻文件 void CEricMeidaPlayerDlg::OnBnClickedBtnOpen() { DWORD dwMax = 0; //得到媒體播放窗口 CWnd *pVideoWnd = GetDlgItem(IDC_WND_VIDEO); //得到進(jìn)度條窗口 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); //打開(kāi)文件選擇對(duì)話(huà)框 if( fileDlg.DoModal () !=IDOK ) { return; } //得到要播放的視頻文件名 strFileName = fileDlg.GetPathName(); //打開(kāi)視頻文件 if (m_VideoControl.OpenFile(strFileName,pVideoWnd->m_hWnd,m_hWnd)) { //設(shè)置視頻尺寸 m_VideoControl.FitVideoWindow(1.0); //得到視頻文件時(shí)間長(zhǎng)度 m_VideoControl.GetDuration(dwMax); //設(shè)置進(jìn)度條范圍 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)實(shí)現(xiàn)“播放”、“暫?!?、“停止”和“全屏”按鈕單擊事件,具體實(shí)現(xiàn)代碼如下所示。 //播放視頻 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)實(shí)現(xiàn)視頻窗口(IDC_WND_VIDEO)單擊事件,用于從全屏狀態(tài)回到正常狀態(tài),如下所示。 //視頻窗口單擊事件 //用于從全屏狀態(tài)回到正常狀態(tài) void CEricMeidaPlayerDlg::OnStnClickedWndVideo() { if (m_VideoControl.GetFullScreenStatus()) { //切換到正常狀態(tài) m_VideoControl.FullScreen(); } } 6)實(shí)現(xiàn)媒體播放事件通知消息(WM ORAPHNOTIFY)函數(shù)。 首先為CEricMediaPlayerDlg類(lèi)添加自定義消息處理函數(shù)定義,定義如下: //媒體插放事件消息處理函數(shù) afx_msg LRESULT OnNotifyMedia(WPARAM wParam, LPARAM lParam); 注意,該定義應(yīng)添加在DECLARE_MESSAOE_MAP()語(yǔ)句之前。 OnNotifyMedia函數(shù)的實(shí)現(xiàn)如下所示。 //媒體播放事件通知 LRESULT CEricMeidaPlayerDlg::OnNotifyMedia(WPARAM WParam, LPARAM LParam) { long lEventCode; if (m_VideoControl.GetMediaEvent(&lEventCode)) { //收到播放結(jié)束事件 if (lEventCode == EC_COMPLETE) { if (m_VideoControl.VideoStop()) { // } } } return (LRESULT)0; } 最后,需要將OnNotifyMedia消息處理函數(shù)和消息WM GRAPHNOTIFY關(guān)聯(lián)起來(lái),即在 BEGIN_MESSAGE_MAP宏中添加如下代碼實(shí)現(xiàn)關(guān)聯(lián): ON_MESSAGE(WM_GRAPHNOTIFY, OnNotifyMedia) 7)添加1個(gè)定時(shí)器,用于更新媒體播放進(jìn)度。 在BOOL CEricMeidaPlayerDlg::OnInitDialog()函數(shù)中添加如下代碼,用于啟動(dòng)1個(gè)定時(shí)器。 //啟動(dòng)定時(shí)器,用于更新媒體播放進(jìn)度 SetTimer(1,1000,NULL); 定時(shí)器消息處理函數(shù)的實(shí)現(xiàn)如下所示。 //WM_TIMER,定時(shí)器處理函數(shù) void CEricMeidaPlayerDlg::OnTimer(UINT_PTR nIDEvent) { //得到進(jìn)度條窗口 CProgressCtrl *pPrgWnd = (CProgressCtrl*)GetDlgItem(IDC_PRG_VIDEO); DWORD dwPos = 0; //得到媒體當(dāng)前播放進(jìn)度 m_VideoControl.GetCurrentPos(dwPos); //設(shè)置進(jìn)度條位置 pPrgWnd->SetPos(dwPos); CDialog::OnTimer(nIDEvent); } 至此,媒體播放器示例就編寫(xiě)完成了。 打開(kāi)Windows Embedded Compact 7虛擬機(jī),將該程序復(fù)制到共享目錄, 執(zhí)行之,如圖10-7 圖10-7選擇程序并運(yùn)行 運(yùn)行效果如下圖10-8 圖10-8程序效果圖 單擊“打開(kāi)”按鈕,選擇一個(gè)電影,我們這里選擇鴻門(mén)宴的剪輯,然后單擊“播放”按鈕就可以觀看視頻了。 圖10-9播放電影圖 |
|
來(lái)自: BeautymengRoom > 《wince6.0》