DirectShow Filter 開發典型例子分析 ——字幕疊加 (FilterTitleOverlay)1
本文分析一下《DirectShow開發指南》中的一個典型的Transform Filter的例子:字幕疊加(FilterTitleOverlay)。通過分析該例子,我們可以學習到DirectShow Transform Filter 開發的方式。
直接打開項目工程(我這里是VC2010),看到項目的結構如下圖所示:
先看一下運行的結果:
注意,DirectShow的Filter是不可以直接運行進行調試的。一般情況下需要借助於Graphedit.exe這個程序進行調試。當然這不是絕對的,也可以用graph-studio-next這樣的開源程序。
選擇右鍵點擊工程->屬性->調試->命令。在欄中輸入Graphedit.exe的路徑,如圖所示
這樣就可以調試Filter了。
拖入一個文件"五月天 咸魚.mp4",然后插入本工程的Filter,如圖所示。
播放視頻,效果如圖,可見左上角顯示出 "Hello, DirectShow!" 的字樣。
看完了結果,就要開始分析代碼了~
回顧一下工程結構圖:
先看一下CFilterTitleOverlay.h(已經在重要的地方加了注釋):
// // CFilterTitleOverlay.h // #ifndef __H_CFilterTitleOverlay__ #define __H_CFilterTitleOverlay__ #include "ITitleOverlay.h" #include "COverlayController.h" #include "OverlayDefs.h" class CFilterTitleOverlay : public CTransInPlaceFilter , public ISpecifyPropertyPages , public ITitleOverlay { private: OVERLAY_TYPE mOverlayType; COverlayController * mOverlayController; CCritSec mITitleOverlaySync; BOOL mNeedEstimateFrameRate; private: CFilterTitleOverlay(TCHAR *tszName, LPUNKNOWN punk, HRESULT *phr); ~CFilterTitleOverlay(); HRESULT SetInputVideoInfoToController(void); void ReleaseOverlayController(void); void SideEffectOverlayTypeChanged(void); public: static CUnknown * WINAPI CreateInstance(LPUNKNOWN punk, HRESULT *phr); //說明必須重寫NonDelegatingQueryInterface DECLARE_IUNKNOWN; // Basic COM - used here to reveal our own interfaces //暴露接口,使外部程序可以QueryInterface,關鍵! STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void ** ppv); // check if you can support mtIn virtual HRESULT CheckInputType(const CMediaType* mtIn); // PURE //必須重寫的核心函數 virtual HRESULT Transform(IMediaSample *pSample); // PURE // Delegating methods virtual HRESULT CompleteConnect(PIN_DIRECTION direction, IPin *pReceivePin); virtual HRESULT StartStreaming(); virtual HRESULT StopStreaming(); // --- ISpecifyPropertyPages --- STDMETHODIMP GetPages(CAUUID *pPages); // --- ITitleOverlay methods --- //都是接口函數 STDMETHODIMP put_TitleOverlayType(long inOverlayType); STDMETHODIMP get_TitleOverlayType(long * outOverlayType); STDMETHODIMP put_TitleOverlayStyle(int inUsingCover); STDMETHODIMP get_TitleOverlayStyle(int * outUsingCover); STDMETHODIMP put_Title(const char * inTitle, int inLength); STDMETHODIMP get_Title(char * outBuffer, int * outLength); STDMETHODIMP put_TitleColor(BYTE inR, BYTE inG, BYTE inB); STDMETHODIMP get_TitleColor(BYTE * outR, BYTE * outG, BYTE * outB); STDMETHODIMP put_TitleStartPosition(POINT inStartPos); STDMETHODIMP get_TitleStartPosition(POINT * outStartPos); STDMETHODIMP put_TitleFont(LOGFONT inFont); STDMETHODIMP get_TitleFont(LOGFONT * outFont); STDMETHODIMP put_TitleDuration(double inStart, double inEnd); STDMETHODIMP get_TitleDuration(double * outStart, double * outEnd); }; #endif // __H_CFilterTitleOverlay__
CFilterTitleOverlay繼承了CTransInPlaceFilter,意味着Transform()函數輸入和輸出的數據位於同一塊內存中。
以下幾個函數是必須有的:
CreateInstance():創建Filter
NonDelegatingQueryInterface():暴露接口,使外部程序可以QueryInterface
CheckInputType():檢查輸入類型
Transform():核心處理函數(字幕疊加)
另外還包含了ITitleOverlay中的函數put_TitleOverlayType()等等一大堆。
下面看一下CFilterTitleOverlay.cpp吧,先列出注冊信息部分:
//唯一標識符 // {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); // // setup data // //注冊時候的信息 const AMOVIESETUP_MEDIATYPE sudPinTypes = { &MEDIATYPE_NULL, // Major type &MEDIASUBTYPE_NULL // Minor type }; //注冊時候的信息 const AMOVIESETUP_PIN psudPins[] = { { L"Input", // String pin name FALSE, // Is it rendered FALSE, // Is it an output FALSE, // Allowed none FALSE, // Allowed many &CLSID_NULL, // Connects to filter L"Output", // Connects to pin 1, // Number of types &sudPinTypes }, // The pin details { L"Output", // String pin name FALSE, // Is it rendered TRUE, // Is it an output FALSE, // Allowed none FALSE, // Allowed many &CLSID_NULL, // Connects to filter L"Input", // Connects to pin 1, // Number of types &sudPinTypes // The pin details } }; //注冊時候的信息 const AMOVIESETUP_FILTER sudFilter = { &CLSID_HQTitleOverlay, // Filter CLSID L"HQ Title Overlay Std.", // Filter name MERIT_DO_NOT_USE, // Its merit 2, // Number of pins psudPins // 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 //注意g_Templates名稱是固定的 CFactoryTemplate g_Templates[] = { { L"HQ Title Overlay Std.", &CLSID_HQTitleOverlay, CFilterTitleOverlay::CreateInstance, NULL, &sudFilter }, { L"HQ Title Overlay Property Page", &CLSID_HQTitleOverlayProp, CTitleOverlayProp::CreateInstance } }; int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]);
這一部分並不屬於CFilterTitleOverlay這個類。主要是DirectShow Filter的一些注冊信息。其結構是非常固定的。
再來看看CFilterTitleOverlay中函數實現部分(只列了幾個函數,不然內容太多= =):
CreateInstance():
// // CreateInstance // // Override CClassFactory method. // Provide the way for COM to create a CNullInPlace object // //創建 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 // 檢查調用該Filter的程序 // 一開始調試不了,就卡在這了 = = if (::strstr(szCreatorName, "graphedit") == NULL && ::strstr(szCreatorName, "ourapp") == NULL) { *phr = E_FAIL; return NULL; } #endif //通過New對象的方法 CFilterTitleOverlay *pNewObject = new CFilterTitleOverlay(NAME("TitleOverlay"), punk, phr); if (pNewObject == NULL) { *phr = E_OUTOFMEMORY; } return pNewObject; }
NonDelegatingQueryInterface():
// // Basic COM - used here to reveal our own interfaces //暴露接口,使外部程序可以QueryInterface,關鍵! STDMETHODIMP CFilterTitleOverlay::NonDelegatingQueryInterface(REFIID riid, void ** ppv) { CheckPointer(ppv, E_POINTER); //根據不同的REFIID,獲得不同的接口指針 if (riid == IID_ISpecifyPropertyPages) { return GetInterface((ISpecifyPropertyPages *) this, ppv); } else if (riid == IID_ITitleOverlay) { return GetInterface((ITitleOverlay *) this, ppv); } else { //不是以上的REFIID的話,調用父類的 return CTransInPlaceFilter::NonDelegatingQueryInterface(riid, ppv); } } // NonDelegatingQueryInterface
CheckInputType():
// Only RGB 32/24/565/555 supported HRESULT CFilterTitleOverlay::CheckInputType(const CMediaType* mtIn) { // Dynamic format change will never be allowed! if (IsStopped() && *mtIn->Type() == MEDIATYPE_Video) { if (*mtIn->Subtype() == MEDIASUBTYPE_RGB32 || *mtIn->Subtype() == MEDIASUBTYPE_RGB24 || *mtIn->Subtype() == MEDIASUBTYPE_RGB555 || *mtIn->Subtype() == MEDIASUBTYPE_RGB565) { return NOERROR; } } return E_INVALIDARG; }
Transform():
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 (mNeedEstimateFrameRate) { mNeedEstimateFrameRate = FALSE; REFERENCE_TIME startTime = 0; REFERENCE_TIME endTime = 0; double estimated = 25; if (SUCCEEDED(pSample->GetTime(&startTime, &endTime))) { estimated = 1.0 * UNITS / (endTime - startTime); } mOverlayController->SetEstimatedFrameRate(estimated); } if (mOverlayType != OT_NONE) { //PBYTE是unsigned char PBYTE pData = NULL; //獲取IMediaSample中的數據 pSample->GetPointer(&pData); //疊加 mOverlayController->DoTitleOverlay(pData); } return NOERROR; }
下面列出實現ITitleOverlay接口的函數的實現,就列了一個。
STDMETHODIMP CFilterTitleOverlay::get_Title(char * outBuffer, int * outLength) { CAutoLock lockit(&mITitleOverlaySync); *outLength = mOverlayController->GetTitle(outBuffer); return NOERROR; }
暫且分析到這里。
書上提供的代碼有誤,這是經過修改后,添加了注釋的代碼:
http://download.csdn.net/detail/leixiaohua1020/6371819
一個簡單的基於 DirectShow 的播放器 1(封裝類)
DirectShow最主要的功能就是播放視頻,在這里介紹一個簡單的基於DirectShow的播放器的例子,是用MFC做的,今后有機會可以基於該播放器開發更復雜的播放器軟件。
注:該例子取自於《DirectShow開發指南》
首先看一眼最終結果,如圖所示,播放器包含了:打開,播放,暫停,停止等功能。該圖顯示正在播放周傑倫的《聽媽媽的話》。
迅速進入主題,看一看工程是由哪些文件組成的,如下圖所示
從上圖可以看出,該工程最重要的cpp文件有兩個:SimplePlayerDlg.cpp和CDXGraph.cpp。前者是視頻播放器對話框對應的類,而后者是對DirectShow功能進行封裝的類。尤其是后面那個類,寫的很好,可以說做到了“可復用”,可以移植到其他DirectShow項目中。
本文首先分析CDXGraph這個類,SimplePlayerDlg在下篇文章中再進行分析。
首先看看它的頭文件:
CDXGraph.h
/* 雷霄驊 * 中國傳媒大學/數字電視技術 * leixiaohua1020@126.com * */ // CDXGraph.h #ifndef __H_CDXGraph__ #define __H_CDXGraph__ // Filter graph notification to the specified window #define WM_GRAPHNOTIFY (WM_USER+20) class CDXGraph { private: //各種DirectShow接口 IGraphBuilder * mGraph; IMediaControl * mMediaControl; IMediaEventEx * mEvent; IBasicVideo * mBasicVideo; IBasicAudio * mBasicAudio; IVideoWindow * mVideoWindow; IMediaSeeking * mSeeking; DWORD mObjectTableEntry; public: CDXGraph(); virtual ~CDXGraph(); public: //創建IGraphBuilder,使用CoCreateInstance virtual bool Create(void); //釋放 virtual void Release(void); virtual bool Attach(IGraphBuilder * inGraphBuilder); IGraphBuilder * GetGraph(void); // Not outstanding reference count IMediaEventEx * GetEventHandle(void); bool ConnectFilters(IPin * inOutputPin, IPin * inInputPin, const AM_MEDIA_TYPE * inMediaType = 0); void DisconnectFilters(IPin * inOutputPin); bool SetDisplayWindow(HWND inWindow); bool SetNotifyWindow(HWND inWindow); bool ResizeVideoWindow(long inLeft, long inTop, long inWidth, long inHeight); void HandleEvent(WPARAM inWParam, LPARAM inLParam); //各種操作 bool Run(void); // Control filter graph bool Stop(void); bool Pause(void); bool IsRunning(void); // Filter graph status bool IsStopped(void); bool IsPaused(void); bool SetFullScreen(BOOL inEnabled); bool GetFullScreen(void); // IMediaSeeking bool GetCurrentPosition(double * outPosition); bool GetStopPosition(double * outPosition); bool SetCurrentPosition(double inPosition); bool SetStartStopPosition(double inStart, double inStop); bool GetDuration(double * outDuration); bool SetPlaybackRate(double inRate); // Attention: range from -10000 to 0, and 0 is FULL_VOLUME. bool SetAudioVolume(long inVolume); long GetAudioVolume(void); // Attention: range from -10000(left) to 10000(right), and 0 is both. bool SetAudioBalance(long inBalance); long GetAudioBalance(void); bool RenderFile(const char * inFile); bool SnapshotBitmap(const char * outFile); private: void AddToObjectTable(void) ; void RemoveFromObjectTable(void); //各種QueryInterface,初始各種接口 bool QueryInterfaces(void); }; #endif // __H_CDXGraph__
該頭文件定義了CDXGraph類封裝的各種DirectShow接口,以及提供的各種方法。在這里因為方法種類特別多,所以只能選擇最關鍵的方法進行分析。下面打開CDXGraph.cpp看看如下幾個方法吧:
Create():用於創建IGraphBuilder
//創建IGraphBuilder,使用CoCreateInstance bool CDXGraph::Create(void) { if (!mGraph) { if (SUCCEEDED(CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void **)&mGraph))) { AddToObjectTable(); return QueryInterfaces(); } mGraph = 0; } return false; }
需要注意的是,Create()調用了QueryInterfaces()
QueryInterfaces():用於初始化各種接口
//各種QueryInterface,初始各種接口 bool CDXGraph::QueryInterfaces(void) { if (mGraph) { HRESULT hr = NOERROR; hr |= mGraph->QueryInterface(IID_IMediaControl, (void **)&mMediaControl); hr |= mGraph->QueryInterface(IID_IMediaEventEx, (void **)&mEvent); hr |= mGraph->QueryInterface(IID_IBasicVideo, (void **)&mBasicVideo); hr |= mGraph->QueryInterface(IID_IBasicAudio, (void **)&mBasicAudio); hr |= mGraph->QueryInterface(IID_IVideoWindow, (void **)&mVideoWindow); hr |= mGraph->QueryInterface(IID_IMediaSeeking, (void **)&mSeeking); if (mSeeking) { mSeeking->SetTimeFormat(&TIME_FORMAT_MEDIA_TIME); } return SUCCEEDED(hr); } return false; }
Release():釋放各種接口
//釋放 void CDXGraph::Release(void) { if (mSeeking) { mSeeking->Release(); mSeeking = NULL; } if (mMediaControl) { mMediaControl->Release(); mMediaControl = NULL; } if (mEvent) { mEvent->Release(); mEvent = NULL; } if (mBasicVideo) { mBasicVideo->Release(); mBasicVideo = NULL; } if (mBasicAudio) { mBasicAudio->Release(); mBasicAudio = NULL; } if (mVideoWindow) { mVideoWindow->put_Visible(OAFALSE); mVideoWindow->put_MessageDrain((OAHWND)NULL); mVideoWindow->put_Owner(OAHWND(0)); mVideoWindow->Release(); mVideoWindow = NULL; } RemoveFromObjectTable(); if (mGraph) { mGraph->Release(); mGraph = NULL; } }
Run():播放
bool CDXGraph::Run(void) { if (mGraph && mMediaControl) { if (!IsRunning()) { if (SUCCEEDED(mMediaControl->Run())) { return true; } } else { return true; } } return false; }
Stop():停止
bool CDXGraph::Stop(void) { if (mGraph && mMediaControl) { if (!IsStopped()) { if (SUCCEEDED(mMediaControl->Stop())) { return true; } } else { return true; } } return false; }
Pause():暫停
bool CDXGraph::Pause(void) { if (mGraph && mMediaControl) { if (!IsPaused()) { if (SUCCEEDED(mMediaControl->Pause())) { return true; } } else { return true; } } return false; }
SetFullScreen():設置全屏
bool CDXGraph::SetFullScreen(BOOL inEnabled) { if (mVideoWindow) { HRESULT hr = mVideoWindow->put_FullScreenMode(inEnabled ? OATRUE : OAFALSE); return SUCCEEDED(hr); } return false; }
GetDuration():獲得視頻時長
bool CDXGraph::GetDuration(double * outDuration) { if (mSeeking) { __int64 length = 0; if (SUCCEEDED(mSeeking->GetDuration(&length))) { *outDuration = ((double)length) / 10000000.; return true; } } return false; }
SetAudioVolume():設置音量
bool CDXGraph::SetAudioVolume(long inVolume) { if (mBasicAudio) { HRESULT hr = mBasicAudio->put_Volume(inVolume); return SUCCEEDED(hr); } return false; }
RenderFile():關鍵!
bool CDXGraph::RenderFile(const char * inFile) { if (mGraph) { WCHAR szFilePath[MAX_PATH]; MultiByteToWideChar(CP_ACP, 0, inFile, -1, szFilePath, MAX_PATH); if (SUCCEEDED(mGraph->RenderFile(szFilePath, NULL))) { return true; } } return false; }
一個簡單的基於 DirectShow 的播放器 2(對話框類)
上篇文章分析了一個封裝DirectShow各種接口的封裝類(CDXGraph):一個簡單的基於 DirectShow 的播放器 1(封裝類)
本文繼續上篇文章,分析一下調用這個封裝類(CDXGraph)的對話框類(CSimplePlayerDlg),看看在MFC中如何使用這個類(CDXGraph)。
首先來看看CSimplePlayerDlg這個類的定義,瞧瞧SimplePlayerDlg.h這個頭文件。
/* 雷霄驊 * 中國傳媒大學/數字電視技術 * leixiaohua1020@126.com * */ // SimplePlayerDlg.h : header file // #if !defined(AFX_SIMPLEPLAYERDLG_H__3599FE35_3322_4CC7_B30B_6D6050C2EDFF__INCLUDED_) #define AFX_SIMPLEPLAYERDLG_H__3599FE35_3322_4CC7_B30B_6D6050C2EDFF__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 ///////////////////////////////////////////////////////////////////////////// // CSimplePlayerDlg dialog #include <streams.h> #include "CDXGraph.h" #define SLIDER_TIMER 100 class CSimplePlayerDlg : public CDialog { // Construction public: CSimplePlayerDlg(CWnd* pParent = NULL); // standard constructor ~CSimplePlayerDlg(); // Dialog Data //{{AFX_DATA(CSimplePlayerDlg) enum { IDD = IDD_SIMPLEPLAYER_DIALOG }; CSliderCtrl mSliderGraph; CStatic mVideoWindow; //}}AFX_DATA // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CSimplePlayerDlg) public: virtual BOOL PreTranslateMessage(MSG* pMsg); virtual BOOL DestroyWindow(); protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support //}}AFX_VIRTUAL // Implementation protected: HICON m_hIcon; CDXGraph * mFilterGraph; // Filter Graph封裝 CString mSourceFile; // 源文件 UINT mSliderTimer; // 定時器ID //創建Graph void CreateGraph(void); // 創建Filter Graph void DestroyGraph(void); // 析構Filter Graph void RestoreFromFullScreen(void); // Just for testing... HRESULT FindFilterByInterface(REFIID riid, IBaseFilter** ppFilter); void ShowVRPropertyPage(void); // Generated message map functions //{{AFX_MSG(CSimplePlayerDlg) virtual BOOL OnInitDialog(); afx_msg void OnPaint(); afx_msg HCURSOR OnQueryDragIcon(); //打開 afx_msg void OnButtonOpen(); //播放 afx_msg void OnButtonPlay(); //暫停 afx_msg void OnButtonPause(); //停止 afx_msg void OnButtonStop(); afx_msg void OnButtonGrab(); afx_msg void OnButtonFullscreen(); afx_msg BOOL OnEraseBkgnd(CDC* pDC); afx_msg void OnTimer(UINT nIDEvent); afx_msg void OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar); afx_msg void OnButtonTest(); //}}AFX_MSG afx_msg LRESULT OnGraphNotify(WPARAM inWParam, LPARAM inLParam); DECLARE_MESSAGE_MAP() }; //{{AFX_INSERT_LOCATION}} // Microsoft Visual C++ will insert additional declarations immediately before the previous line. #endif // !defined(AFX_SIMPLEPLAYERDLG_H__3599FE35_3322_4CC7_B30B_6D6050C2EDFF__INCLUDED_)
從頭文件來看,和普通的MFC對話框類並沒有什么不同,無非是一些消息響應函數,或者MFC控件對應的類。需要注意一下,有一個變量:
CDXGraph * mFilterGraph
接下來看看CSimplePlayerDlg函數的實現部分吧。
OnButtonOpen():打開媒體文件按鈕的響應函數
//打開 void CSimplePlayerDlg::OnButtonOpen() { // TODO: Add your control notification handler code here CString strFilter = "AVI File (*.avi)|*.avi|"; strFilter += "MPEG File (*.mpg;*.mpeg)|*.mpg;*.mpeg|"; strFilter += "Mp3 File (*.mp3)|*.mp3|"; strFilter += "Wave File (*.wav)|*.wav|"; strFilter += "All Files (*.*)|*.*|"; CFileDialog dlgOpen(TRUE, NULL, NULL, OFN_PATHMUSTEXIST | OFN_HIDEREADONLY, strFilter, this); if (IDOK == dlgOpen.DoModal()) { mSourceFile = dlgOpen.GetPathName(); // Rebuild the file playback filter graph //創建Graph CreateGraph(); } }
其中CreateGraph()函數如下所示:
//創建Graph void CSimplePlayerDlg::CreateGraph(void) { //(如果有)銷毀Graph DestroyGraph(); //新建一個核心類 mFilterGraph = new CDXGraph(); if (mFilterGraph->Create()) { // Render the source clip mFilterGraph->RenderFile(mSourceFile); // Set video window and notification window mFilterGraph->SetDisplayWindow(mVideoWindow.GetSafeHwnd()); mFilterGraph->SetNotifyWindow(this->GetSafeHwnd()); // Show the first frame mFilterGraph->Pause(); } }
與CreateGraph()相反的還有一個DestroyGraph()
//(如果有)銷毀Graph void CSimplePlayerDlg::DestroyGraph(void) { if (mFilterGraph) { // Stop the filter graph first mFilterGraph->Stop(); mFilterGraph->SetNotifyWindow(NULL); delete mFilterGraph; mFilterGraph = NULL; } }
OnButtonPlay():播放按鈕的響應函數
//播放 void CSimplePlayerDlg::OnButtonPlay() { if (mFilterGraph) { mFilterGraph->Run(); // Start a timer if (mSliderTimer == 0) { mSliderTimer = SetTimer(SLIDER_TIMER, 100, NULL); } } }
OnButtonPause():暫停按鈕的響應函數
void CSimplePlayerDlg::OnButtonPause() { if (mFilterGraph) { mFilterGraph->Pause(); // Start a timer if (mSliderTimer == 0) { mSliderTimer = SetTimer(SLIDER_TIMER, 100, NULL); } } }
OnButtonStop():停止按鈕的響應函數
void CSimplePlayerDlg::OnButtonStop() { if (mFilterGraph) { mFilterGraph->SetCurrentPosition(0); mFilterGraph->Stop(); // Stop the timer if (mSliderTimer) { KillTimer(mSliderTimer); mSliderTimer = 0; } } }
其他的函數不再一一列舉,但意思都是一樣的。