1. 程式人生 > >DirectShow之介面實戰篇--視訊抓圖與播放控制

DirectShow之介面實戰篇--視訊抓圖與播放控制


-----------------------------------------------

    現今自己程式設計做一個多媒體播放工具是一件很令人開心愉悅的事情,但如果使用MediaPlay控制項開發則會受到很多限制,自己的很多好的創意想法都無法或者很難實現,如果利用微軟的DirectX介面開發則可以充分的將作者的獨特想法付諸於實現,何樂而不為呢!!不過關於DirectShow介面的開發說明文件實在是少之又少,僅有的一些不是英文的就是一些關於理論方面的,真正關於介面實戰程式設計而且是用Delphi開發工具實現的更是鳳毛麟角,使很多人都望而卻步。在這裡,我把我應用Directshow開發的心得以及我搜集到一些資料重新整理編輯出來公佈,希望對所有由此興趣的同仁有所幫助,就算達到了我的目的。廢話少說,進入正文。

    既然是介面實戰篇,就先把一些常用的介面列出來,讓大家有一些基本的認識,都是用來做什麼的,什麼時候我們會需要用到此介面。


IFilterGraph ----過濾通道介面

IFilterGraph2----增強的IFilterGraph

IGraphBuilder----最為重用的COM介面,用於手動或者自動構造過濾通道FilterGraph Manager

IMediaControl----用來控制流媒體,例如流的啟動和停止暫停等,播放控制介面

IMediaEvent  ----播放事件介面 ,該介面在Filter Graph發生一些事件時用來建立事件的標誌資訊並傳送給應用程式

IMediaEventEx----擴充套件播放事件介面

IMediaPosition----播放的位置和速度控制介面(控制播放位置只能為設定時間控制方式)

IMediaSeeking-----另一個播放的位置和播放速度控制介面,在位置選擇方面功能較強.

     設定播放格式,多種控制播放方式.常用的有:
    (1)TIME_FORMAT_MEDIA_TIME單位100納秒。
    (2)TIME_FORMAT_FRAME按幀播放

IBasicAudio------聲音控制介面

IBasicVideo------影象控制介面(串列傳輸速率,寬度,長度等資訊)

IVideoWindow------顯示視窗控制介面 (有關播放視窗的一切控制,包括caption顯示,視窗位置控制等)

ISampleGrabber-----捕獲影象介面(可用於抓圖控制)

IVideoFrameStep-----控制單幀播放的介面


好了,熟悉了應用DirectShow應用開發常用的介面後,我們就通過一個例項媒體播放器來熟悉掌握這些介面,例項的程式碼雖然簡單,但五臟俱全,功能強大,同時也瞭解一下應用DirectShow開發一般常用的步驟。


--------------------------------------------------------------------------------
DirectShow之介面實戰篇(二)

大體說來,一般使用DirectShow介面程式設計無非3個步驟,
初始化介面,
利用介面中的控制函式使用控制操作,
最後釋放介面。

(當然這裡假定你已經擁有了directshow.pas等必須單元,如果沒有的話請在網上查詢)
(注:以下變數沒有定義,需自己定義使用)

(1) 首先初始化部分介面,需要定義需要使用的介面
GraphBuilder: IGraphBuilder;
MediaControl: IMediaControl;
MediaSeeking: IMediaSeeking;
MediaPosition: IMediaPosition;
MediaEventEx: IMediaEvent;
BasicAudio: IBasicAudio;
BasicVideo: IBasicVideo;
VideoWindow: IVideoWindow;
SampleGrabber: ISampleGrabber;
VideoFrameStep: IVideoFrameStep;

(2)然後需要使用CoCreateInstance函式建立一個Filter Graph Manager 例項,
CoCreateInstance(TGUID(CLSID_FilterGraph),nil, CLSCTX_INPROC_SERVER,TGUID(IID_IGraphBuilder),GraphBuilder)
  
因為需要抓圖使用IsampleGrabber介面,需要建立SampleGrabber例項,
var Filter: IBaseFilter;
CoCreateInstance(CLSID_SampleGrabber, nil, CLSCTX_INPROC_SERVER,IID_IBaseFilter, Filter);

(3) 呼叫QueryInterface函式獲取來獲取指標,好以後操作控制

Filter.QueryInterface(IID_ISampleGrabber, SampleGrabber);
GraphBuilder.AddFilter(Filter, 'Grabber');

GraphBuilder.QueryInterface(IID_IMediaControl, MediaControl);
GraphBuilder.QueryInterface(IID_IMediaPosition, MediaPosition);
GraphBuilder.QueryInterface(IID_IMediaSeeking, MediaSeeking);
GraphBuilder.QueryInterface(IID_IMediaEventEx, MediaEventEx);
GraphBuilder.QueryInterface(IID_IVideoFrameStep, VideoFrameStep);
GraphBuilder.QueryInterface(IID_IBasicAudio, BasicAudio);
GraphBuilder.QueryInterface(IID_IBasicVideo, BasicVideo);
GraphBuilder.QueryInterface(IID_IVideoWindow, VideoWindow);

當然為了安全起見,可以對以上每個過程進行是否成功判斷,給出資訊,否則很有可能出現問題找不到頭緒。
好了,一切準備成功,就可以進入第4步,開始我們的控制操作了。

(4)通過介面提供的函式開始控制
哦,差點忘記一件重要的事情,在上面呼叫QueryInterface之前,還有兩件重要的事情要做,

第一,要建立一個Unicode(wide character)字串,儲存檔案名。
var _wfile: array[0..MAX_PATH - 1] of wchar;
MultiByteToWideChar(CP_ACP, 0, pChar(播放檔案名), -1, @_wfile, MAX_PATH);

然後需要成功RenderFile才可以控制操作
GraphBuilder.RenderFile(_wfile, nil);

當然在顯示的時候要把顯示錶單和控制項關連起來,這裡需要通過IvideoWindow介面方法,

VideoWindow. put_Owner(Edit1.Handle);
VideoWindow. put_WindowStyle(DSVIDEO_WINDOW_CHILD_STYLE);
VideoWindow.SetWindowPosition(0,0, Edit1.ClientWidth, Edit1.ClientHeight);

得到影象的一些必要資訊,使用IbasicVideo介面中的方法,一些變數自己定義,
BasicVideo.GetVideoSize(VideoWidth, VideoHeight);
BasicVideo.get_BitRate(VideoBitRate);
BasicVideo.get_AvgTimePerFrame(PerFrame);

得到當前檔的總時間以及播放時間,需要使用ImediaSeeking介面方法,
MediaSeeking.GetDuration(Duration);//得到總時間
MediaSeeking.GetCurrentPosition(CurrentPos);//得到當前播放時間

也可以通過IMediaSeeking::SetPositions方法設定開始和結束時間。

哦,這裡得到的單位好像是毫米級的,可以自己轉化為秒級的.

還有,如果想以後能夠單幀控制播放,在這裡也需要設定播放格式為按幀播放。
MediaSeeking.SetTimeFormat(Time_Format_Frame);

播放,停止,暫停等控制,
這些需要使用ImediaControl介面的方法,控制起來很簡單,分別為
MediaControl.Play;
MediaControl.Stop;
MediaControl.Pause;

--------------------------------------------------------------------------------

DirectShow之介面實戰篇(三)

播放速度的設定
需要使用ImediaPosition的方法。

MediaPosition.put_Rate(1);//正常
MediaPosition.put_Rate(0.25);//慢速
MediaPosition.put_Rate(2);//快速

單幀播放控制
需要使用IvideoFrameStep的方法
VideoFrameStep.Step(1, nil);

音量控制
需要使用IbasicAudio的方法
增加音量:
BasicAudio.get_Volume (&volume);//得到音量
volume:= volume +volumestep;
BasicAudio.put_Volume (volume);//增加一定的音量的分貝
減小音量:
BasicAudio.get_Volume (&volume); //得到音量
volume:= volume -volumestep;
BasicAudio.putVolume (volume); //減小一定音量的分貝

顯示放大縮小控制
只需改變Edit1的大小,然後使用IvideoWindow介面方法即可
VideoWindow.SetWindowPosition(0, 0, Edit1.Width, Edit1.Height);

單幀捕獲,抓圖
其實很多介面都提供了此功能,但是我更傾向於使用IsampleGrabber介面來實現,相對來說,效率高些。
這個控制起來做的工作稍微多些,首先,在開啟檔案的時候
var MediaType: TAM_MEDIA_TYPE;
ZeroMemory(@MediaType, SizeOf(TAM_MEDIA_TYPE));
MediaType.majortype := MEDIATYPE_Video;//視訊流
MediaType.subtype := MEDIASUBTYPE_RGB24;//24點陣圖像
MediaType.formattype := FORMAT_VideoInfo;
SampleGrabber.SetMediaType(MediaType);//關聯介面
SampleGrabber.SetBufferSamples(True);
然後在抓圖按鈕事件中如下操作
var
MediaType: TAM_MEDIA_TYPE;
VideoInfoHeader: TVideoInfoHeader;
BitmapInfo: TBitmapInfo;
Bitmap: HBitmap;
Buffer: Pointer;
BufferSize: Integer;
begin
SampleGrabber.GetConnectedMediaType(MediaType);

ZeroMemory(@VideoInfoHeader, SizeOf(TVideoInfoHeader));
CopyMemory(@VideoInfoHeader, MediaType.pbFormat, SizeOf(VideoInfoHeader));

ZeroMemory(@BitmapInfo, SizeOf(TBitmapInfo));
CopyMemory(@BitmapInfo, @VideoInfoHeader.bmiHeader, SizeOf(VideoInfoHeader.bmiHeader));

Bitmap:=CreateDIBSection(0, BitmapInfo, DIB_RGB_COLORS, Buffer, 0, 0);
SampleGrabber.GetCurrentBuffer(BufferSize, Buffer);

Image1.Picture.Bitmap.Handle:=Bitmap
end;
即可。

在這裡,先總結這麼多,希望對大家有所幫助,這些只是DirectX的一個皮毛,它可以實現的功能十分強大,我也只是把我在實際中的遇到的問題總結出來供大家參考,後面的工作還很多,我想我會逐步的更深入的總結這方面的經驗發表出來與大家分享,好了,DirectShow介面施展篇到這裡該完結了,如果大家有補充或者想法,請發表出來以便我總結整理,謝謝大家。


 
--------------------------------------------------
 
 DirectShow之介面實戰篇(四)
 
通過DirectShow驅動攝像頭,首先要建立一個ICaptureGraphBuilder2的COM物件,它的作用是裝置(攝像頭)與輸出畫面打交道的一個介面.
//建立一個ICaptureGraphBuilder2物件
var
  m_cBuilder2: ICaptureGraphBuilder2;
  hr: HResult;
........................
begin
  hr := CoCreateInstance(CLSID_CaptureGraphBuilder2, nil, CLSCTX_INPROC, ICaptureGraphBuilder2, m_cBuilder2);
  if FAILED(hr) then Exit;//失敗就返回
.........................
好有了介面就要準備輸出的畫面了,其實就是一個IGraphBuilder的COM物件!
在var下面新增 
m_gBuilder: IGraphBuilder;
//建立一個畫面
hr := CoCreateInstance(CLSID_FilterGraph, nil, CLSCTX_INPROC, IGraphBuilder, m_gBuilder);
if FAILED(hr) then Exit;

//OK!把它們關聯起來
m_cBuilder2.SetFiltergraph(m_gBuilder);

現在有了輸出畫面但還沒有裝置,別慌!下面就講怎麼得到裝置!
要想得到裝置就要請ICreateDevEnum介面和IEnumMoniker介面幫忙了!


首先建立ICreateDevEnum介面物件,在var下面新增 
de: ICreateDevEnum;
//建立ICreateDevEnum物件
hr := CoCreateInstance(CLSID_SystemDeviceEnum, nil, CLSCTX_INPROC, IID_ICreateDevEnum, de);
  if FAILED(hr) then Exit;
有了ICreateDevEnum我們就可以列舉裝置了,在var下面新增

em: IEnumMoniker;
//建立一個列舉
de.CreateClassEnumerator(CLSID_VideoInputDeviceCategory, em, 0);
  if em = nil then Exit;
//列舉裝置(攝像頭)
在var下面新增
pM: IMoniker;//找到的介面
pBf: IBaseFilter;//裝置的過濾器
dID: DWORD;

if em.Next(1, pM, @dID) = S_OK then

//得到裝置的過濾器
  hr := pM.BindToObject(nil, nil, IID_IBaseFilter, pBf);
    if FAILED(hr) then Exit;
好了我們有了裝置了!那就是pBf(過濾器)
現在我們要把它加入到輸出畫面去
//加入過濾器
hr := m_gBuilder.AddFilter(pBf, 'Video Capture');
    if FAILED(hr) then Exit;
當有輸出畫面了有了過濾器還不行!要用ICaptureGraphBuilder2把他們關聯起來
hr := m_cBuilder2.RenderStream(@PIN_CATEGORY_PREVIEW, @MEDIATYPE_Video, pBf, nil, nil);
    if FAILED(hr) then Exit;
//現在基本完成了,下面就是要它們動起來!!
在var下面新增
m_mControl: IMediaControl;
  hr := m_gBuilder.QueryInterface(IID_IMediaControl, m_mControl);
  if FAILED(hr) then Exit;
這段程式碼是得到輸出畫面的控制介面,下面就要動了!!
//簡單的一句
m_mControl.Run;