基于directShow,打造全能播放器系列之一
分类: VC++ directShow
2012-09-14 12:47
151人阅读
收藏
举报
总前言:我打算写一个能实现全能播放的播放器,功能比较简单,也算是抛砖引玉吧,因为内容较多,所以打算写三篇,这是开篇,欢迎大家吐槽
简易播放器的实现
本文的编写环境:visual studio 2008 ,基于MFC based DLG 的应用程序
前言:我写这个系列博客的目的,是想让大家知道,播放器的实现,其实没有想像的那么难,只是掌握了一点的方法,自己完全可以实现,当然出于容易讲解的目的,我会将代码写的尽量简洁,当然,在每个博客的最后都将贴出源代码地址,以供大家,研究学习。
前提:本文并不是假设你从零基础开始就能完全实现的,如果你根本没有了解过directShow,那还是请你从头开始吧,慢慢地了解个个函数的功能,然后再到这里来,因为我会从如何配置VS2005开始一步步的教到你完全写出这个播放器为止,但我并不会每句代码都会讲的很详细,如果有不理解的地方,你可以查看MSDN,directX SDK,找度娘,找谷歌,都是不错的选择。
注意:这篇博客与《DirectShow开发指南》第五章的例子,师从同路,高手可以不看
下面就开始播放器开发的旅程了,准备好了吧,那我们开始了
第一步:配置VS播放器,首先你得先安装directX 9.0开发包,安装好之后,记得编译dx9sdk\Samples\C++\DirectShow\BaseClasses这个目录下的baseclasses工程,然后就是将所需要的文件包含在VS2005配置中,为节省篇幅,这里就不再缀述,可以参看《DirectShow开发指南》P66-P67(开发环境的配置)
第二步:应用程序创建、界面及程序设置
创建一个MFC应用程序,based DLG,命名为Player
(一)配置
1、选择“项目”-》“player属性”打开属性页;
2、选择先在DEBUG模式下,选择“配置属性”-》“链接器”-》“输入”-》“附加依赖项”处添加:
strmbasd.lib uuid.lib winmm.lib Quartz.lib Strmiids.lib
如图:
3、然后将“配置”选项改为“Release”,重复上一操作,即添加相同的依赖项
4、在PlayDlg.cpp和PlayDlg.h的顶部加入directShow所需要的头文件#include<streams.h>
(二)界面设置
界面如下:
说明:
1、其中红框处,是添加的一个Picture Ctrl控件,ID号设置为:IDC_VIDEOWND,将其Type属性改为“Rectangle”
2、添加了一个Slider Ctrl,其ID设置为IDC_PROGRESS
3、添加三个按钮,“打开”按钮的ID号为:IDC_BTN_OPEN,“播放”按钮的ID号设置为IDC_BTN_PLAY,“暂停”按钮的ID号设置为“IDC_BTN_PAUSE”,“停止”按钮的ID号设置为:IDC_BTN_STOP
(三)关联变量
1、对Picture Ctrl控件关联CStatic型变量m_VideoWindowPlay;
2、对Slider Ctrl控件关联CSliderCtrl型变量m_Slider;
注意:由于MFC本身的CSliderCtrl会存在很多问题,比如定位不准确,等,所以我们一般不使用这个类,而改成我们自己的类,请到下面的地下下载CNiceSlider类
http://download.csdn.net/detail/harvic880925/4554013
然后将下载后的文件加载到工程中,并在PlayerDlg.h的文件中增加#include"NiceSlider.h"
然后将m_VideoWindow前的CStatic改为CNiceSliderCtrl,即
(四)初始化COM组件
因为DirectShow是COM组件,所以我们在使用前要先对其初始化,用完之后,也要手动解除
在CPlayerApp类中的InitInstance()函数中,添加初始化代码:
- <span style="font-size:14px;">HRESULT hr=CoInitialize(NULL);
- if(FAILED(hr))
- {
- printf("ERROR-Could not initialize COM libray");
- return -1;
- }
- </span>
位置如图:
然后添加在CPlayerApp类中添加ExitInstance()函数,在其中添加::CoUninitialize();以解除使用
第三步:实战
(一)变量定义、初始化及实例化
1、变量定义:
- <span style="font-size:14px;"> IGraphBuilder * m_Graph;
- IMediaControl * m_MediaControl;
- IMediaEventEx * m_Event;
- IBasicVideo * m_BasicVideo;
- IBasicAudio * m_BasicAudio;
- IVideoWindow * m_VideoWindow;
- IMediaSeeking * m_Seeking;
以上只是对各变量功能作了下简单的讲解,如果想具体了解,可以查看SDK或MSDN
2、初始化
CPlayerDlg::OnInitDialog()在添加初始化信息:
- <span style="font-size:14px;"> m_Graph=NULL;
- m_MediaControl=NULL;
- m_Event=NULL;
- m_BasicVideo=NULL;
- m_BasicAudio=NULL;
- m_VideoWindow=NULL;
- m_Seeking=NULL;
- this->m_Slider.SetRange(0,1000);
- this->m_Slider.SetPos(0);</span>
3、实例化
在CPlayerDlg类中,添加一个函数Create();专门用来实例化各个变量
- <span style="font-size:14px;">bool CPlayerDlg::Create()
- {
- if(!m_Graph)
- {
- HRESULT hr=S_OK;
- if(SUCCEEDED(::CoCreateInstance(CLSID_FilterGraph,NULL,CLSCTX_INPROC_SERVER,IID_IGraphBuilder,(void * *)&m_Graph)))
- {
- hr |=this->m_Graph->QueryInterface(IID_IMediaControl,(void **)&this->m_MediaControl);
- hr |=this->m_Graph->QueryInterface(IID_IBasicAudio,(void **)&this->m_BasicAudio);
- hr |=this->m_Graph->QueryInterface(IID_IMediaEventEx,(void **)&this->m_Event);
- hr |=this->m_Graph->QueryInterface(IID_IBasicVideo,(void **)&this->m_BasicVideo);
- hr |=this->m_Graph->QueryInterface(IID_IVideoWindow,(void **)&this->m_VideoWindow);
- hr |=this->m_Graph->QueryInterface(IID_IMediaSeeking,(void **)&this->m_Seeking);
-
- if(this->m_Seeking)
- {
- m_Seeking->SetTimeFormat(&TIME_FORMAT_MEDIA_TIME);
- }
- return SUCCEEDED(hr);
- }
- m_Graph=0;
- }
- return false;
- }</span>
(二)针对更接口函数的封装
1、针对IMediaControl类的函数封装,封装Run(),Stop,Pause()函数,及IsRun(),IsStop(),IsPause()函数的实现,代码简单,不再详述
- <span style="font-size:14px;">bool CPlayerDlg::IsRunning()
- {
- if(m_Graph&&this->m_MediaControl)
- {
- OAFilterState FilterState=State_Stopped;
- HRESULT hr=this->m_MediaControl->GetState(10,&FilterState);
- if(SUCCEEDED(hr))
- {
- if(FilterState==State_Running)
- {return true;}
- }
- }
- return false;
- }
- bool CPlayerDlg::Run()
- {
- if(m_Graph&&this->m_MediaControl)
- {
- if(!IsRunning())
- {
- if(SUCCEEDED(this->m_MediaControl->Run()))
- {
- return true;
- }
- }else
- {
- return true;
- }
- }
- return false;
- }
-
- bool CPlayerDlg::IsStopped()
- {
- if(m_Graph&&this->m_MediaControl)
- {
- OAFilterState FilterState=State_Stopped;
- if(SUCCEEDED(this->m_MediaControl->GetState(10,&FilterState)))
- {
- if(FilterState==State_Stopped)
- {
- return true;
- }
- }
- }
- return false;
- }
- bool CPlayerDlg::Stop()
- {
- if(m_Graph&&this->m_MediaControl)
- {
- if(!this->IsStopped())
- {
- if(SUCCEEDED(this->m_MediaControl->Stop()))
- {
- return true;
- }
- }else
- {
- return true;
- }
- }
- return false;
- }
- bool CPlayerDlg::IsPaused()
- {
- if(m_Graph&&this->m_MediaControl)
- {
- OAFilterState FilterState=State_Stopped;
- if(SUCCEEDED(this->m_MediaControl->GetState(10,&FilterState)))
- {
- if(FilterState==State_Paused)
- {
- return true;
- }
- }
- }
- return false;
- }
- bool CPlayerDlg::Pause()
- {
- if(m_Graph&&this->m_MediaControl)
- {
- if(!this->IsPaused())
- {
- if(SUCCEEDED(this->m_MediaControl->Pause()))
- {
- return true;
- }
- }else
- {
- return true;
- }
- }
- return false;
- }</span>
2、针对IMediaSeeking类的函数封装,主要是针对GetDuration、GetCurrentPosition、SetPositions函数的封装,主要是用来计算当前S lider控件中托动点的位置用的,这里我只是封装了几个我们播放用的函数,其实IMediaSeeking还有其它的一些函数,也是很好的,大家可以查看下SDK,比如设置播放速率什么的,实现慢放、快放等,这些就靠大家去研究吧
- <span style="font-size:14px;">bool CPlayerDlg::GetDuration(double * outDuration)
- {
- if (m_Seeking)
- {
- LONGLONG length = 0;
- if (SUCCEEDED(m_Seeking->GetDuration(&length)))
- {
- *outDuration = ((double)length) / 10000000.;
- return true;
- }
- }
- return false;
- }
-
-
- bool CPlayerDlg::GetCurrentPosition(double *outPosition)
- {
- if(m_Graph&&this->m_Seeking)
- {
- LONGLONG position=0;
- if(SUCCEEDED(this->m_Seeking->GetCurrentPosition(&position)))
- {
- *outPosition=((double)position) / 10000000.;
- return true;
- }
- }
- return false;
- }
-
- bool CPlayerDlg::SetCurrentPosition(double Position)
- {
- if(m_Graph&&this->m_Seeking)
- {
- LONGLONG pos=10000000*Position;
- HRESULT hr=this->m_Seeking->SetPositions(&pos,AM_SEEKING_AbsolutePositioning|AM_SEEKING_SeekToKeyFrame,0,AM_SEEKING_NoPositioning);
-
- if(SUCCEEDED(hr))
- {
- return true;
- }
- }
- return false;
- }
-
3、针对IVideoWindow类的函数封装,这个就稍微有点难度了,不再是仅仅的对一个函数的封装了,这里是真正的自己实现,我们实现的函数有SetDisplayWindow(用于设置播放窗口),SetFullScreen(用于将视频设置为全屏)、GetFullScreen(获取当前全屏状态),具体实现代码如下:
- <span style="font-size:14px;">bool CPlayerDlg::SetFullScreen(bool inEnabled)
- {
- if(m_Graph&&this->m_VideoWindow)
- {
- if(SUCCEEDED(this->m_VideoWindow->put_FullScreenMode(inEnabled ? OATRUE : OAFALSE)))
- {
- return true;
- }
- }
- return false;
- }
-
- bool CPlayerDlg::GetFullScreen(void)
- {
- if (m_VideoWindow)
- {
- long fullScreenMode = OAFALSE;
- m_VideoWindow->get_FullScreenMode(&fullScreenMode);
- return (fullScreenMode == OATRUE);
- }
- return false;
- }
-
- bool CPlayerDlg::SetDisplayWindow(HWND HWindow)
- {
- if(this->m_VideoWindow)
- {
- this->m_VideoWindow->put_Visible(OAFALSE);
-
- if(HWindow)
- {
- this->m_VideoWindow->put_Owner(OAHWND(HWindow));
-
- CRect wndRect;
- ::GetClientRect(HWindow,&wndRect);
- this->m_VideoWindow->put_Left(0);
- this->m_VideoWindow->put_Top(0);
- this->m_VideoWindow->put_Height((long)wndRect.Height());
- this->m_VideoWindow->put_Width((long)wndRect.Width());
-
- this->m_VideoWindow->put_WindowStyle(WS_CHILD|WS_CLIPCHILDREN|WS_CLIPSIBLINGS);
-
- this->m_VideoWindow->put_MessageDrain((OAHWND)HWindow);
-
- this->m_VideoWindow->put_Visible(OATRUE);
-
- return true;
- }
- }
- return false;
- }
-
-
-
4、针对IMediaEventEx类的函数封装,实现对SetNotifyWindow的封装
- <span style="font-size:14px;">bool CPlayerDlg::SetNotifyWindow(HWND HWindow)
- {
- if(this->m_Event&&HWindow)
- {
- HRESULT hr=this->m_Event->SetNotifyWindow((OAHWND)HWindow,WM_GRAPHNOTIFY,0);
- if(SUCCEEDED(hr))
- {
- return true;
- }
- }
- return false;
- }</span>
//定义消息接收的窗口,在其中定义的消息为WM_GRAPHNOTIFY,WM_GRAPHNOTIFY是我们自定义的消息,,
1、在PlayerDlg.h文件中,在顶部添加#define WM_GRAPHNOTIFY (WM_USER+20)
2、在PlayerDlg.h文件中,并且在头文件中DECLARE_MESSAGE_MAP上面添加afx_msg LRESULT OnGraphNotify(WPARAM inWParam,LPARAM inLParam);位置如图:
3、在PlayerDlg.cpp中如图所示位置在//}}AFX_MSG_MAP下面,添加ON_MESSAGE(WM_GRAPHNOTIFY,OnGraphNotify)
4、最后在PlayerDlg.cpp中增加对响应函数的实现,这里可以先加一个框架,在文章的最后,会贴出具体实现,这里可以先写成
- <span style="font-size:14px;">LRESULT CPlayerDlg::OnGraphNotify(WPARAM inWParam, LPARAM inLParam)
- {
- return true;
- }</span>
5,针对RenderFile()的封装
- <span style="font-size:14px;">bool CPlayerDlg::RenderFile(const char * inFile)
- {
- if(m_Graph)
- {
- WCHAR szFilePath[MAX_PATH];
- MultiByteToWideChar(CP_ACP, 0, inFile, -1, szFilePath, MAX_PATH);
-
- if(SUCCEEDED(this->m_Graph->RenderFile(szFilePath,NULL)))
- {
- return true;
- }
- }
- return false;
- }</span>
(三)对添加对按钮的响应
一、添加对“打开”按钮的响应
在其响应函数中添加如下代码
- void CPlayerDlg::OnBnClickedBtnOpen()
- {
-
- CString strFilter = _T("AVI File (*.avi)|*.avi|");
- strFilter +=_T( "MPEG File (*.mpg;*.mpeg)|*.mpg;*.mpeg|");
- strFilter +=_T("Mp3 File (*.mp3)|*.mp3|");
- strFilter +=_T( "Wave File (*.wav)|*.wav|");
- strFilter +=_T( "All Files (*.*)|*.*|");
- CFileDialog dlgOpen(TRUE, NULL, NULL, OFN_PATHMUSTEXIST | OFN_HIDEREADONLY,
- strFilter, this);
- if (IDOK == dlgOpen.DoModal())
- {
- m_SourceFile = dlgOpen.GetPathName();
- this->CreateGraph();
- }
- }
-
说明:1、m_SourceFile是定义的一个CStringA 对象,主要是用来保存打开文件的路径,大家自己添加定义和初始化一下吧。
2、CreateGraph()是新封装的一个函数,代码及讲解如下
- bool CPlayerDlg::CreateGraph()
- {
- this->DestroyGraph();
-
- if(this->Create())
- {
- this->RenderFile(this->m_SourceFile);
-
- this->SetDisplayWindow(this->m_VideoWindowPlay.GetSafeHwnd());
- this->SetNotifyWindow(this->GetSafeHwnd());
-
- this->Pause();
- return true;
-
- }
- return false;
- }
//这里主要是先用this->Create()创建Graph中的各个变量及初始化操作,然后this->RenderFile(this->m_SourceFile);来渲染文件,也即采用智能链接模式,自动为我们构建播放链路,然后用SetDisplayWindow和SetNotifyWindow来设置播放窗口和消息接收窗口,最后this->Pause();先将视频暂停,等我们按下“播放”按钮时再播放视频。这里有个函数是我们新添加的,即this->DestroyGraph();,它主要实现的功能是释放变量,实现代码如下:
- bool CPlayerDlg::DestroyGraph()
- {
- if(this->m_BasicAudio)
- {
- this->m_BasicAudio->Release();
- this->m_BasicAudio=NULL;
- }
- if(this->m_BasicVideo)
- {
- this->m_BasicVideo->Release();
- this->m_BasicVideo=NULL;
- }
- if(this->m_Event)
- {
- this->m_Event->Release();
- this->m_Event=NULL;
- }
-
- if(this->m_MediaControl)
- {
- this->m_MediaControl->Release();
- this->m_MediaControl=NULL;
- }
- if(this->m_Seeking)
- {
- this->m_Seeking->Release();
- this->m_Seeking=NULL;
- }
- if(this->m_VideoWindow)
- {
- this->m_VideoWindow->put_Visible(OAFALSE);
- this->m_VideoWindow->put_MessageDrain((OAHWND)NULL);
- this->m_VideoWindow->put_Owner((OAHWND)NULL);
- this->m_VideoWindow->Release();
- this->m_VideoWindow=NULL;
- }
-
- if(this->m_Graph)
- {
- this->m_Graph->Release();
- this->m_Graph=NULL;
- }
- return true;
- }
二、对“播放”按钮的响应
实现代码如下:
- void CPlayerDlg::OnBnClickedBtnPlay()
- {
-
- if(this->m_Graph)
- {
- this->Run();
- }
- }
三、对“暂停”按钮的响应
- void CPlayerDlg::OnBnClickedBtnPause()
- {
-
- if(this->m_Graph)
- {
- this->Pause();
- }
- }
四,对“停止”按钮的响应
- void CPlayerDlg::OnBnClickedBtnStop()
- {
-
- if(this->m_Graph)
- {
- this->Stop();
- this->SetCurrentPosition(0);
- }
- }
(四)添加进度条推进与拖曳响应
1、首先要增加一个定时器,所以在OnBnClickedBtnOpen()、OnBnClickedBtnPause()、OnBnClickedBtnStop()函数中,添加如下代码:
- if(this->m_SliderTimer==0)
- {
- m_SliderTimer=this->SetTimer(IDC_PROGRESS,100,NULL);
- }
//表示如果在点击这个按钮时,如果还没有创建定时器,就创建一个定时器,我们不指定接收消息函数,默认让OnTimer()来处理就可以了,这里有个新变量m_SliderTimer,是UINT 类型的变量,大家自己添加一下定义,记得初始化为0哦,用来保存创建Timer的ID号的。
2、对DLG添加WM_TIMER消息响应
- void CPlayerDlg::OnTimer(UINT_PTR nIDEvent)
- {
-
- if(nIDEvent==this->m_SliderTimer&&this->m_Graph)
- {
- double pos=0,duration=1;
- this->GetCurrentPosition(&pos);
- this->GetDuration(&duration);
-
- int newPos = int(pos * 1000 / duration);
- if(this->m_Slider.GetPos()!=newPos)
- {
- this->m_Slider.SetPos(newPos);
- }
- }
- CDialog::OnTimer(nIDEvent);
- }
-
3、实现对鼠标拉动进度条时的消息响应
对DLG添加对WM_HSCROLL消息的响应OnHScroll()
代码如下:
- void CPlayerDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
- {
-
- if(pScrollBar->GetSafeHwnd()==this->m_Slider.GetSafeHwnd())
- {
- if(this->m_Slider)
- {
- double duration=1;
- double pos=0;
- pos=this->m_Slider.GetPos();
- this->GetDuration(&duration);
- double newPos=duration*pos/1000;
- this->SetCurrentPosition(newPos);
- }
- }else
- {
- CDialog::OnHScroll(nSBCode, nPos, pScrollBar);
- }
- }
-
最后贴出消息响应函数的实现代码:
- LRESULT CPlayerDlg::OnGraphNotify(WPARAM inWParam, LPARAM inLParam)
- {
- if(this->m_Graph&&this->m_Event)
- {
- long eventCode=0,eventParam1=0,eventParam2=0;
- while(SUCCEEDED(this->m_Event->GetEvent(&eventCode,&eventParam1,&eventParam2,0)))
- {
- m_Event->FreeEventParams(eventCode,eventParam1,eventParam2);
-
- switch(eventCode)
- {
- case EC_COMPLETE:
- OnBnClickedBtnPause();
- this->SetCurrentPosition(0);
- break;
- case EC_USERABORT:
- case EC_ERRORABORT:
- OnBnClickedBtnStop();
- break;
- default:
- break;
- }
- }
- }
- return 0;
- }
-
实现图如下:
写上面的内容实在是太累了,暂且写到这吧,下篇将会对播放器进行功能的稍微补充,解码器的安装配置与GraphEdit.exe的使用,而在最后一篇我打算讲解对于手动连接FILTER,以解决有些格式播放有晃动不清的问题,写的不好,还请大家批评指正谢谢大家的观摩。