【视频处理工程】7、一个基于LavFilter的对话框视频播放器
在实现了利用控制台程序播放指定视频文件之后,接下来开始尝试编写一个带有界面的视频播放器,可以选择播放的视频,控制音量等更多的功能。为简单起见,界面的框架用MFC实现。
1、建立工程,生成默认界面
这一步很简单,打开Visual Studio 2010,选择MFC Application,选择基于对话框的工程然后一路next就完成了,没有任何需要更改的地方。之后可以编译运行,生成一个默认的对话框。对工程进行与DirectShow相关的设置,具体方法参考这里。
2、添加播放器内核类
将上文中的DirectShowApi.h+.cpp文件添加到新的工程中,代码如下。
//DirectShowApi.h
#pragma once
#include
#include HRESULT AddToRot(IUnknown *pUnkGraph, DWORD *pdwRegister);
void RemoveFromRot(DWORD pdwRegister);
HRESULT AddFilterByCLSID(IGraphBuilder *pGraph, const GUID& clsid, LPCWCHAR wszName, IBaseFilter **ppF);
HRESULT GetUnconectedPin(IBaseFilter *pFilter, PIN_DIRECTION PinDir, IPin **ppPin);
HRESULT ConnectFilters(IGraphBuilder *pGraph, IPin *pOut, IBaseFilter *pDest);
HRESULT ConnectFilters( IGraphBuilder *pGraph, IBaseFilter *pSrc, IBaseFilter *pDest);//DirectShowApi.cpp
#include "stdafx.h"
#include "DirectShowAPI.h"HRESULT AddToRot(IUnknown *pUnkGraph, DWORD *pdwRegister)
{IMoniker * pMoniker = NULL;IRunningObjectTable *pROT = NULL;if (FAILED(GetRunningObjectTable(0, &pROT))) {return E_FAIL;}const size_t STRING_LENGTH = 256;WCHAR wsz[STRING_LENGTH];StringCchPrintfW(wsz, STRING_LENGTH, L"FilterGraph %08x pid %08x", (DWORD_PTR)pUnkGraph, GetCurrentProcessId());HRESULT hr = CreateItemMoniker(L"!", wsz, &pMoniker);if (SUCCEEDED(hr)) {hr = pROT->Register(ROTFLAGS_REGISTRATIONKEEPSALIVE, pUnkGraph,pMoniker, pdwRegister);pMoniker->Release();}pROT->Release();return hr;
}void RemoveFromRot(DWORD pdwRegister)
{IRunningObjectTable *pROT = NULL;if(SUCCEEDED(GetRunningObjectTable(0,&pROT))){pROT->Revoke(pdwRegister);pROT->Release();}
}HRESULT AddFilterByCLSID( IGraphBuilder *pGraph, const GUID& clsid, LPCWCHAR wszName, IBaseFilter **ppF )
{if (!pGraph || !ppF)return E_POINTER;*ppF = 0;IBaseFilter *pF = 0;HRESULT hr = CoCreateInstance(clsid, 0, CLSCTX_INPROC_SERVER, IID_IBaseFilter, reinterpret_cast(&pF));if (SUCCEEDED(hr)){hr = pGraph->AddFilter(pF, wszName);if (SUCCEEDED(hr))*ppF = pF;elsepF->Release();}return hr;
}HRESULT GetUnconectedPin( IBaseFilter *pFilter, PIN_DIRECTION PinDir, IPin **ppPin )
{*ppPin = 0;IEnumPins *pEnum = 0;IPin *pPin = 0;HRESULT hr = pFilter->EnumPins(&pEnum);if (FAILED(hr)){return hr;}hr = pEnum->Reset();while (pEnum->Next(1,&pPin,NULL) == S_OK){PIN_DIRECTION ThisPinDirection;pPin->QueryDirection(&ThisPinDirection);if (ThisPinDirection == PinDir){IPin *pTemp = 0;hr = pPin->ConnectedTo(&pTemp);if (SUCCEEDED(hr)){//当前pin已经连接,无效;pTemp->Release();} else{pEnum->Release();*ppPin = pPin;return S_OK;} }pPin->Release();}pEnum->Release();return E_FAIL;
}HRESULT ConnectFilters( IGraphBuilder *pGraph, IPin *pOut, IBaseFilter *pDest )
{if ((pGraph == NULL)||(pOut == NULL)||(pDest == NULL))return E_POINTER;#ifdef _DEBUGPIN_DIRECTION PinDir;pOut->QueryDirection(&PinDir);assert(PinDir == PINDIR_OUTPUT);
#endif // _DEBUG//得到下级filter的输入pinIPin *pIn = 0;HRESULT hr = GetUnconectedPin(pDest,PINDIR_INPUT,&pIn);if (FAILED(hr))return hr;hr = pGraph->Connect(pOut,pIn);pIn->Release();return hr;
}HRESULT ConnectFilters( IGraphBuilder *pGraph, IBaseFilter *pSrc, IBaseFilter *pDest)
{if ((pGraph == NULL)||(pSrc == NULL)||(pDest == NULL))return E_POINTER;IPin *pOut = 0;HRESULT hr = GetUnconectedPin(pSrc,PINDIR_OUTPUT,&pOut);if (FAILED(hr))return hr;hr = ConnectFilters(pGraph,pOut,pDest);pOut->Release();return hr;
}
在工程中添加类CFilterGraph,并在定义类的头文件中添加跟Filter Graph相关的接口成员变量;随后定义创建和删除Filter Graph的方法,代码如下:
定义:
//Filter Graph.h
#pragma once
#include "DirectShowApi.h"class CFilterGraph
{
public:CFilterGraph(void);~CFilterGraph(void);public://DirectShow相关接口成员IGraphBuilder *pGraph; //滤波器链表管理器IMediaControl *pMediaControl; //媒体控制接口,如run、stop、pauseIMediaEventEx *pMediaEvent; //媒体事件接口IBasicVideo *pBasicVideo; //视频基本接口IBasicAudio *pBasicAudio; //音频基本接口IVideoWindow *pVideoWindow; //视频窗口接口DWORD m_dwGraphRegister;public://CFilterGraph APIvirtual bool Create(void); //生成滤波器链表管理器virtual void Release(void); //释放所有接口private://内部方法bool QueryInterfaces(void);
};
实现:
//Filter Graph.cpp
#include "StdAfx.h"
#include "FilterGraph.h"CFilterGraph::CFilterGraph(void)
{
}CFilterGraph::~CFilterGraph(void)
{
}bool CFilterGraph::Create( void )
{if (!pGraph){if (SUCCEEDED(CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void **)&pGraph))){::AddToRot(pGraph,&m_dwGraphRegister);return QueryInterfaces(); }}return false;
}bool CFilterGraph::QueryInterfaces( void )
{if (pGraph){HRESULT hr = NOERROR;hr |= pGraph->QueryInterface(IID_IMediaControl, (void **)&pMediaControl);hr |= pGraph->QueryInterface(IID_IMediaEventEx, (void **)&pMediaEvent);hr |= pGraph->QueryInterface(IID_IBasicVideo, (void **)&pBasicVideo);hr |= pGraph->QueryInterface(IID_IBasicAudio, (void **)&pBasicAudio);hr |= pGraph->QueryInterface(IID_IVideoWindow, (void **)&pVideoWindow);return SUCCEEDED(hr);}return false;
}void CFilterGraph::Release( void )
{if (pMediaControl){pMediaControl->Release();pMediaControl = NULL;}if (pMediaEvent){pMediaEvent->Release();pMediaEvent = NULL;}if (pBasicVideo){pBasicVideo->Release();pBasicVideo = NULL;}if (pBasicAudio){pBasicAudio->Release();pBasicAudio = NULL;}if (pVideoWindow){pVideoWindow->put_Visible(OAFALSE);pVideoWindow->put_MessageDrain((OAHWND)NULL);pVideoWindow->put_Owner(OAHWND(0));pVideoWindow->Release();pVideoWindow = NULL;}::RemoveFromRot(m_dwGraphRegister);if (pGraph) {pGraph->Release(); pGraph = NULL;}
}
现在这个类还不完整,稍后将会把这个类补全。
3、简要设计对话框的界面
在MFC默认的对话框界面中,添加一个按键用于选择视频文件,以及一个标签用于显示选定的文件名,如下图所示。
为按键添加相应函数:
void CLavPlayerDlg::OnBnClickedButtonSelectfile()
{// TODO: Add your control notification handler code hereCString strFormatFilter = _T("AVI file (*.avi)|*.avi|");strFormatFilter += _T("MPEG file (*.mpg;*.mpeg;*.mp4)|*.mpg;*.mpeg;*.mp4|");strFormatFilter += _T("HD file (*.mkv;*.ts)|*.mkv;*.ts|");strFormatFilter += _T("Audio file (*.mp3;*.aac)|*.mp3;*.aac|");strFormatFilter += _T("All file (*.*)|*.*|");CFileDialog dlg(TRUE,NULL,NULL,OFN_FILEMUSTEXIST | OFN_HIDEREADONLY,strFormatFilter,this);if (dlg.DoModal() == IDOK){m_VideoFilePath = dlg.GetPathName();m_VideoFileName = GetFileTitleFromFileName(m_VideoFilePath,1);CreateGraph();}
}CString CLavPlayerDlg::GetFileTitleFromFileName( CString FileName, bool Ext )
{int Where; Where = FileName.ReverseFind('\\'); if (Where == -1) Where = FileName.ReverseFind('/'); CString FileTitle = FileName.Right(FileName.GetLength() - 1 - Where); if (!Ext) { int Which = FileTitle.ReverseFind('.'); if (Which != -1) FileTitle = FileTitle.Left(Which); } return FileTitle;
}
其中的m_VideoFilePath和m_VideoFileName是定义在dlg类中的两个cstring类的成员,用于接收选定视频的文件路径和文件名。CreateGraph()用于创建完整的filter graph。
void CLavPlayerDlg::CreateGraph()
{DestroyGraph();m_pFilterGraph = new CFilterGraph();if (m_pFilterGraph->Create()){if (m_pFilterGraph->RenderFile(m_VideoFilePath)){AfxMessageBox(_T("无法播放文件,因为文件已损坏或缺少解码器!"));return;}
// m_pFilterGraph->SetDisplayWindow(m_videoWindow.GetSafeHwnd());m_pFilterGraph->SetNotifyWindow(this->GetSafeHwnd());m_pFilterGraph->Pause();}
}void CLavPlayerDlg::DestroyGraph()
{if (m_pFilterGraph != NULL){m_pFilterGraph->Stop();m_pFilterGraph->SetNotifyWindow(NULL);delete m_pFilterGraph;m_pFilterGraph = NULL;}
}
播放器核心类中添加了多个函数。如Stop、Pause、SetNotifyWindow、SetDisplayWindow、RenderFile等,分别用于停止视频播放、暂停视频播放、设置通知窗口、设置显示窗口、解析播放视频文件等。下面首先考虑实现RenderFile方法。
首先需要添加头文件定义lavfilter组件的clsid,并在播放器核心类中include这个头文件:
//CLSID.h
#pragma once
#include // {B98D13E7-55DB-4385-A33D-09FD1BA26338}
static const GUID CLSID_LavSplitter_Source =
{ 0xB98D13E7, 0x55DB, 0x4385, { 0xA3, 0x3D, 0x09, 0xFD, 0x1B, 0xA2, 0x63, 0x38 } };//{EE30215D-164F-4A92-A4EB-9D4C13390F9F}
static const GUID CLSID_LavVideoDecoder =
{ 0xEE30215D, 0x164F, 0x4A92, { 0xA4, 0xEB, 0x9D, 0x4C, 0x13, 0x39, 0x0F, 0x9F } };//{E8E73B6B-4CB3-44A4-BE99-4F7BCB96E491}
static const GUID CLSID_LavAudioDecoder =
{ 0xE8E73B6B, 0x4CB3, 0x44A4, { 0xBE, 0x99, 0x4F, 0x7B, 0xCB, 0x96, 0xE4, 0x91 } };
同时参考上篇文章中的控制台应用,在RenderFile函数中添加lavfilter的组件:
bool CFilterGraph::RenderFile( CString fileName )
{HRESULT hr = NOERROR;LPTSTR fileToPlay = fileName.GetBuffer();hr |= ::AddFilterByCLSID(pGraph,CLSID_LavSplitter_Source,L"Lav Splitter Source",&m_pLavSplitterSource);hr |= m_pLavSplitterSource->QueryInterface(IID_IFileSourceFilter,(void **)&m_pFileSourceFilter);hr |= m_pFileSourceFilter->Load(fileToPlay,NULL);fileName.ReleaseBuffer();hr |= AddFilterByCLSID(pGraph,CLSID_LavVideoDecoder,L"Lav Video Decoder",&m_pLavVideoDecoder);hr |= ConnectFilters(pGraph,m_pLavSplitterSource,m_pLavVideoDecoder);hr |= AddFilterByCLSID(pGraph,CLSID_LavAudioDecoder,L"Lav Audio Decoder",&m_pLavAudioDecoder);hr |= ConnectFilters(pGraph,m_pLavSplitterSource,m_pLavAudioDecoder);hr |= AddFilterByCLSID(pGraph,CLSID_VideoMixingRenderer9,L"Video Mixing Renderer-9",&m_pVideoRenderer); hr |= ConnectFilters(pGraph,m_pLavVideoDecoder,m_pVideoRenderer);hr |= AddFilterByCLSID(pGraph,CLSID_AudioRender,L"Audio render",&m_pAudioRender);hr |= ConnectFilters(pGraph,m_pLavAudioDecoder,m_pAudioRender);return SUCCEEDED(hr);
}
同时在界面上新增加一个按钮,命名为“播放”,初始显示设置为FALSE,在其响应函数中调用播放内核类的run方法,使Filter Graph Manager开始运行:
void CLavPlayerDlg::OnBnClickedBtnPlay()
{// TODO: Add your control notification handler code herem_pFilterGraph->Run();
}
bool CFilterGraph::Run( void )
{if (pGraph && pMediaControl){if (!IsRunning()){if (SUCCEEDED(pMediaControl->Run())){return true;}}else{return true;}}return false;
}bool CFilterGraph::IsRunning( void )
{if (pGraph && pMediaControl){OAFilterState state = State_Stopped;if (SUCCEEDED(pMediaControl->GetState(10, &state))){return state == State_Running;}}return false;
}
除此之外还需要在CreateGraph中添加Label显示文件名,以及显示播放按钮的功能,完成后效果如下:
void CLavPlayerDlg::CreateGraph()
{DestroyGraph();m_pFilterGraph = new CFilterGraph();if (m_pFilterGraph->Create()){if (m_pFilterGraph->RenderFile(m_VideoFilePath) == false){AfxMessageBox(_T("无法播放文件,因为文件已损坏或缺少解码器!"));return;}m_pFilterGraph->SetNotifyWindow(this->GetSafeHwnd());m_pFilterGraph->Pause();GetDlgItem(IDC_VIDEOFILE)->SetWindowText(m_VideoFileName);GetDlgItem(IDC_BTN_PLAY)->ShowWindow(TRUE);}
}
编译运行,初始界面如:
选择文件后,出现“播放”按钮:
播放之后的界面:
虽然视频的播放功能已经实现但是应用却还是十分的不完善(比如没有播放控制功能,没有音量调节等),还有很多漏洞,以后会在学习的过程中逐步改进。
本文DEMO下载请点击这里
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
