转载 自 : http://blog.csdn.net/wuanshi5/article/details/50392111
最主要的开发资料还是在 : http://blog.csdn.net/shaqoneal/article/category/1887469
总结: 不能播放mp4文件,还是因为缺少相应的库
主要用graphedit 打开视频文件,就能看到所用到的filters,如果打不开可以使用 lavfilter / ffdshow 等解码器和解复用器
1、初始化 com, 调用CoInitialize(NULL)
2、初始化GraphBuilder, 调用CoCreateInstance。 GraphBuilder是DirectShow中视频的管理类。 非常重要。
3、枚举需要的接口,调用QueryInterface, 常用的有IID_IMediaControl、IID_IVideoWindow等。
4、创建自己需要的Filter(若是播放AVI,则可以略过这一步), 这一步需要分别创建:
分离器: 从磁盘中读取文件,并将视频文件内容分为音频流和视频流。
视频解码器: 将视频流解码为一帧一帧的画面,之后传给视频渲染器。
音频解码器: 将音频流解码为音频设备可播放的形式,之后传给音频播放设备。
视频渲染器: 负责渲染视频画面。
音频渲染器:负责播放声音。
然后,需要将各filter的pin链接起来。形成一个通畅的视频流。之后,输入文件后,DirectShow就会按照事先设定好的线路运转。
5、枚举IID_IFileSourceFilter接口,读取文件。 之后使用MediaControl对视频的播放进行控制。
这个过程需要,预先安装好对应的解码器和分离器。 渲染器系统提供了。 lav filter包含同时分离器和解码器。
我用的是,lav的分离器 lav splitter filter 和 ffdshow的解码器。
同时,如果希望直观的了解这个通路的建立过程,可以使用graphstudionext。
不过,如果希望看到程序的播放通路。需要将graphBuilder加入到ROT(对象活动表)中,具体过程参照MSDN。
参考博客: http://blog.csdn.net/shaqoneal/article/category/1887469
这个博客,详细的介绍了上述的过程。
//===================================================================================
6、使用Lav Filter手动建立Filter Graph并播放视频文件
在前面的这篇博文中,我们开始利用的手动建立Filter Graph,已经完成了Splitter Source Filter和Audio/Video Decoder的建立和链接。接下来需要做的是渲染解码完成的音视频数据,分别由Audio Render和Video Render完成。音频渲染器较为简单也很少会出问题,我们主要讨论视频渲染器的问题。
DirectShow所提供的视频渲染器种类有多种,如Video Renderer(已不常用)、VMR-7、VMR-9、EVR等。MSDN的文档讲了各种视频渲染器的相关知识。其中提到了“在vista之后的windows版本中,如果硬件支持应使用EVR”,既然已经是win 7了,那自然是首选EVR了。这部分的代码如下:
- int _tmain(int argc, _TCHAR* argv[])
- {
-
- hr = AddFilterByCLSID(pGraph,CLSID_LavSplitter_Source,L"Lav Splitter Source",&pLavSplitterSource);
- hr = pLavSplitterSource->QueryInterface(IID_IFileSourceFilter,(void **)&pFileSourceFilter);
- hr = pFileSourceFilter->Load(fileName,NULL);
-
-
- hr = AddFilterByCLSID(pGraph,CLSID_LavVideoDecoder,L"Lav Video Decoder",&pLavVideoDecoder);
- hr = ConnectFilters(pGraph,pLavSplitterSource,pLavVideoDecoder);
-
-
- hr = AddFilterByCLSID(pGraph,CLSID_LavAudioDecoder,L"Lav Audio Decoder",&pLavAudioDecoder);
- hr = ConnectFilters(pGraph,pLavSplitterSource,pLavAudioDecoder);
- hr = AddFilterByCLSID(pGraph,CLSID_EnhancedVideoRenderer,L"Enhanced Video Renderer",&pRenderer);
- hr = ConnectFilters(pGraph,pLavVideoDecoder,pRenderer);
-
-
- hr = AddFilterByCLSID(pGraph,CLSID_AudioRender,L"Audio render",&pAudioRender);
- hr = ConnectFilters(pGraph,pLavAudioDecoder,pAudioRender);
-
- }
Build工程后运行,使用GraphStudioNext查看生成的Filter Graph,可以看到音视频渲染器都已经成功加入了,如下图所示:
但是我们发现一个问题,电影已经正常播放,但是只有声音却没有图像,这是为何?
通过搜索得知,DirectShow的渲染器定义了两种模式:窗口模式和非窗口模式。窗口模式下,视频渲染器会自动生成一个视频播放窗口,渲染完成的画面会显示在这个视频窗口中;当然我们可以获取这个窗口的句柄进行下一步操作比如讲该窗口置于我们自定义的应用窗口下加以控制。非窗口模式下,视频渲染器需要指定一个视频输出窗口,将数据渲染在该窗口中。在这两种模式中,Video Renderer只支持窗口模式,VMR-7/9支持两种模式,默认为窗口模式。而在MSDN文档中指出,EVR并不支持窗口模式,显示窗口必须由应用程序自己创建。而我们的demo只是一个控制台程序,不存在窗口,自然也就无法渲染视频了。
既然不支持,那我们就换掉,改为使用VMR-9进行绘制。方法很简单,在添加EVR组件的语句中将CLSID_EnhancedVideoRenderer改为CLSID_VideoMixingRenderer9就可以了。
编译链接运行,查看Filter Graph,如下图:
播放画面:
至此,我们已经成功地把一部主流格式的电影文件通过手动建立Filter Graph的方式播放出来了,也对DirectShow的基本概念有了一定的了解。接下来还将继续进一步深入研究与其相关的更多内容。
下载该demo点这里
//=====================================================
一个基于LavFilter的对话框视频播放器
在实现了利用控制台程序播放指定视频文件之后,接下来开始尝试编写一个带有界面的视频播放器,可以选择播放的视频,控制音量等更多的功能。为简单起见,界面的框架用MFC实现。
1、建立工程,生成默认界面
这一步很简单,打开Visual Studio 2010,选择MFC Application,选择基于对话框的工程然后一路next就完成了,没有任何需要更改的地方。之后可以编译运行,生成一个默认的对话框。对工程进行与DirectShow相关的设置,具体方法参考这里。
2、添加播放器内核类
将上文中的DirectShowApi.h+.cpp文件添加到新的工程中,代码如下。
- "font-size: 14px;">"font-family:SimSun;">
- #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);
-
-
- #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<void**>(&pF));
- if (SUCCEEDED(hr))
- {
- hr = pGraph->AddFilter(pF, wszName);
- if (SUCCEEDED(hr))
- *ppF = pF;
- else
- pF->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))
- {
-
- 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 _DEBUG
- PIN_DIRECTION PinDir;
- pOut->QueryDirection(&PinDir);
- assert(PinDir == PINDIR_OUTPUT);
- #endif // _DEBUG
-
-
- IPin *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的方法,代码如下:
定义:
- "font-size: 14px;">"font-family:SimSun;">
- #pragma once
- #include "DirectShowApi.h"
-
- class CFilterGraph
- {
- public:
- CFilterGraph(void);
- ~CFilterGraph(void);
-
- public:
-
-
- IGraphBuilder *pGraph;
- IMediaControl *pMediaControl;
- IMediaEventEx *pMediaEvent;
- IBasicVideo *pBasicVideo;
- IBasicAudio *pBasicAudio;
- IVideoWindow *pVideoWindow;
-
- DWORD m_dwGraphRegister;
-
- public:
-
- virtual bool Create(void);
- virtual void Release(void);
-
- private:
-
- bool QueryInterfaces(void);
- };
实现:
- "font-size: 14px;">"font-family:SimSun;">
- #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默认的对话框界面中,添加一个按键用于选择视频文件,以及一个标签用于显示选定的文件名,如下图所示。
为按键添加相应函数:
- "font-size: 14px;">"font-family:SimSun;">void CLavPlayerDlg::OnBnClickedButtonSelectfile()
- {
-
- CString 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。
- "font-size: 14px;">"font-family:SimSun;">void CLavPlayerDlg::CreateGraph()
- {
- DestroyGraph();
- m_pFilterGraph = new CFilterGraph();
- if (m_pFilterGraph->Create())
- {
- if (m_pFilterGraph->RenderFile(m_VideoFilePath))
- {
- AfxMessageBox(_T("无法播放文件,因为文件已损坏或缺少解码器!"));
- return;
- }
-
- 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这个头文件:
- "font-size: 14px;">"font-family:SimSun;">
- #pragma once
- #include
-
-
- static const GUID CLSID_LavSplitter_Source =
- { 0xB98D13E7, 0x55DB, 0x4385, { 0xA3, 0x3D, 0x09, 0xFD, 0x1B, 0xA2, 0x63, 0x38 } };
-
-
- static const GUID CLSID_LavVideoDecoder =
- { 0xEE30215D, 0x164F, 0x4A92, { 0xA4, 0xEB, 0x9D, 0x4C, 0x13, 0x39, 0x0F, 0x9F } };
-
-
- static const GUID CLSID_LavAudioDecoder =
- { 0xE8E73B6B, 0x4CB3, 0x44A4, { 0xBE, 0x99, 0x4F, 0x7B, 0xCB, 0x96, 0xE4, 0x91 } };
同时参考上篇文章中的控制台应用,在RenderFile函数中添加lavfilter的组件:
- "font-size: 14px;">"font-family:SimSun;">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开始运行:
- "font-family:SimSun;">void CLavPlayerDlg::OnBnClickedBtnPlay()
- {
-
- m_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显示文件名,以及显示播放按钮的功能,完成后效果如下:
- "font-family:SimSun;">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下载请点击这里