大学的小学期做了一个基于directshow的播放器,主要算法来源于前人的著作,而csdn中也有很详细的注解:http://blog.csdn.net/harvic880925/article/details/7978566
之后说一下自己的附加部分。基于windowSDK中directshow和MFC,使用vs2013作为开发工具。
一、DirectShow简介
DirectShow是微软公司提供的一套在Windows平台上进行流媒体处理的开发包,9.0之前与DirectX开发包一起发布,之后包含在windows SDK中。
运用DirectShow,我们可以很方便地从支持WDM驱动模型的采集卡上捕获数据,并且进行相应的后期处理乃至存储到文件中。它广泛地支持各种媒体格式,包括Asf、Mpeg、avi、dv、Mp3、Wave等等,使得多媒体数据的回放变得轻而易举。另外,DirectShow还集成了DirectX其它部分(比如DirectDraw、DirectSound)的技术,直接支持DVD的播放,视频的非线性编辑,以及与数字摄像机的数据交换。
二、多媒体播放器功能分析
(1)本地播放
打开本地文件,安装解码器后可以支持绝大部分媒体格式。打开通过获取文件地址,赋给m_sourcefile,之后调用底层函数进行播放。
bool CPlayerDlg::Run()
{
if(m_Graph&&this->m_MediaControl)
{
if(!IsRunning())
{
if(SUCCEEDED(this->m_MediaControl->Run()))
{
return true;
}
}else
{
return true;
}
}
return false;
}
(2)暂停与停止
通过调用pause()和stop()函数实现暂停和停止功能。
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;
}
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;
}
(3)快进与快退
首先调用duration()函数获取总的时间,之后在这一基础上加上几秒或减去几秒。再从新的位置播放。
void CPlayerDlg::OnBnClickedquickback()
{
//TODO: 在此添加控件通知处理程序代码
double duration = 1;
double pos = 0;
pos = this->m_Slider.GetPos();
this->GetDuration(&duration);
double newPos = (duration*pos - 5000) / 1000;
if (newPos >= 0 && (newPos <= duration))
this->SetCurrentPosition(newPos);
else
{
this->SetCurrentPosition(0);
}
}
(4)变速率播放
获取控件的值,可以调用IMediaSeeking类中的方法setrate(),改变媒体播放速率。
void CPlayerDlg::SetRate()
{
int nIndex = m_cbExamble.GetCurSel();
if (nIndex==0)
{
m_Seeking->SetRate(0.5);
}
else if (nIndex == 1)
{
m_Seeking->SetRate(1);
}
else if (nIndex==2)
{
m_Seeking->SetRate(1.5);
}
else
{
m_Seeking->SetRate(1);
}
}
(5)音量调节
音量调节的原理即获取滑动条的进度值,之后计算相应音量的值,即可实现音量控制。因为这也属于横向滑动条的响应函数,即加入到控制进度的滑动条消息中。
else if (pScrollBar->GetSafeHwnd() == m_volume.GetSafeHwnd())
{
if (this->m_volume)
{
int pos= mxc.Bounds.dwMaximum - m_volume.GetPos();
volStruct.lValue = pos; //想要设置的值
mixerSetControlDetails((HMIXEROBJ)m_hmx,&mxcd,
MIXER_SETCONTROLDETAILSF_VALUE);
}
(6)进度控制
通过duration函数获取总的时间,之后乘以从进度条读取的进度百分比,即可计算出现在的时间,从而获取现在的播放位置。
if(pScrollBar->GetSafeHwnd()==this->m_Slider.GetSafeHwnd())
{
if(this->m_Slider)
{
doubleduration=1;
doublepos=0;
pos=this->m_Slider.GetPos();
this->GetDuration(&duration);
doublenewPos=duration*pos/1000;
this->SetCurrentPosition(newPos);
}
}
(7)播放列表与历史记录
播放列表为一个listbox,在点击打开文件后,可使用addstring()方法将名字加入到listbox中,同时将其写入文件中作为历史记录。
在打开软件时从文件读入信息,从而将地址和名字赋给结构数组pathinformation,方便之后查找。
当双击listbox时触发响应,从而获取该行的字符串,与pathinformation中的数据进行比对即可得到URL地址。
void CPlayerDlg::writefile(URLinf &URLinformation)
{
CString a1=URLinformation.name;
CString a2 = URLinformation.URL;
CStdioFile file(_T("infname.txt"),CFile::modeCreate |CFile::modeNoTruncate |CFile::modeReadWrite);
file.SeekToEnd();
char* old_locale = _strdup(setlocale(LC_CTYPE,NULL));
setlocale(LC_CTYPE,"chs");//设定
file.WriteString(a1+"\n");//正常写入
setlocale(LC_CTYPE,old_locale);
free(old_locale);//还原区域设定
file.Close();
CStdioFile file1(_T("infpath.txt"),CFile::modeCreate |CFile::modeNoTruncate |CFile::modeReadWrite);
file1.SeekToEnd();
char* old_locale1 = _strdup(setlocale(LC_CTYPE,NULL));
setlocale(LC_CTYPE,"chs");//设定
file1.WriteString(a2+"\n");//正常写入
setlocale(LC_CTYPE,old_locale1);
free(old_locale1);//还原区域设定
file1.Close();
}
void CPlayerDlg::readfile()
{
int i = 0;
int j = 0;
CStdioFile File;
CStdioFile File1;
CString a1;
CString a2;
TCHAR buf[1000];
TCHAR buf1[1000];
if (File.Open(_T("infname.txt"),CFile::modeNoTruncate|CFile::modeRead))
{
char*old_locale = _strdup(setlocale(LC_CTYPE,NULL));
setlocale(LC_CTYPE,"chs");//设定
while(File.ReadString(buf, 1000))
{
m_list.AddString(buf);
pathInformation[i].name = buf;
pathInformation[i].name.Trim(_T("\n"));
i++;
}
setlocale(LC_CTYPE,old_locale);
free(old_locale);//还原区域设定
}
File.Close(); /*关闭文件*/
if (File1.Open(_T("infpath.txt"),CFile::modeNoTruncate |CFile::modeRead))
{
char*old_locale1 = _strdup(setlocale(LC_CTYPE,NULL));
setlocale(LC_CTYPE,"chs");//设定
while(File1.ReadString(buf1, 1000))
{
pathInformation[j].URL = buf1;
pathInformation[j].URL.Trim(_T("\n"));
j++;
}
setlocale(LC_CTYPE,old_locale1);
free(old_locale1);//还原区域设定
}
File1.Close();
}
void CPlayerDlg::insertfile(URLinf &URLinformation)
{
int change = 0;
int cc = 0;
int length = m_list.GetCount();
for (int i = 0; i <=length; i++)
{
if (URLinformation.name==pathInformation[i].name)
{
change = 0;
}
else
{
change = 1;
cc = i;
}
}
if (change == 1)
{
writefile(URLinformation);
m_list.AddString(URLinformation.name);
pathInformation[cc].name = URLinformation.name;
pathInformation[cc].URL = URLinformation.URL;
}
}
void CPlayerDlg::OnLbnDblclkList2()
{
//TODO: 在此添加控件通知处理程序代码
int record=0;
int judge = m_list.GetCurSel();
int length = m_list.GetCount();
CString temp="";
m_list.GetText(judge,temp);
temp.Trim(_T("\n"));
for (int i = 0; i < length;i++)
if (pathInformation[i].name==temp)
{
record = i;
}
else
{
;
}
m_SourceFile = pathInformation[record].URL;
this->CreateGraph();
}
(8)全屏
首先记录各个控件的位置,方便之后还原。然后重新画窗体,设置各个控件的位置和大小,同时监测如果有鼠标双击则恢复初始状态。
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(HWNDHWindow)
{
if(this->m_VideoWindow)
{
this->m_VideoWindow->put_Visible(OAFALSE);//首先隐藏窗口
if(HWindow)
{
this->m_VideoWindow->put_Owner(OAHWND(HWindow));
CRectwndRect;
::GetClientRect(HWindow,&wndRect);//一定要注意要用WND坐标,而不能桌面坐标
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);//WS_CLIPSIBLINGS表示重绘时,只重绘这一个窗口,其它窗口不发生重绘。
//WS_CLIPCHILDREN表示此窗口不允许被其它窗口覆盖
this->m_VideoWindow->put_MessageDrain((OAHWND)HWindow);//设置接收鼠标键盘的窗口
this->m_VideoWindow->put_Visible(OATRUE);
return true;
}
}
return false;
}
bool CPlayerDlg::SetFullScreen2()
{
if(m_bFullScreen)
{
ModifyStyle(WS_POPUP,WS_CAPTION);
//首先还原框架的大小及位置
SetWindowPlacement(&m_OldWndPlacement);
//将播放窗口还原到原来的位置
this->m_VideoWindowPlay.SetWindowPos(&wndTop,m_OldPlayWndRect.left,m_OldPlayWndRect.top,m_OldPlayWndRect.Width(),m_OldPlayWndRect.Height(),SWP_NOACTIVATE);
//还原视频显示大小
this->SetDisplayWindow(m_VideoWindowPlay.GetSafeHwnd());
//还原其它控件的位置
this->GetDlgItem(IDC_PROGRESS)->MoveWindow(m_OldProgressRect.left,m_OldProgressRect.top,m_OldProgressRect.Width(),m_OldProgressRect.Height(),true);
this->GetDlgItem(IDC_BTN_OPEN)->MoveWindow(m_OldOpenBtnRect.left,m_OldOpenBtnRect.top,m_OldOpenBtnRect.Width(),m_OldOpenBtnRect.Height(),true);
this->GetDlgItem(IDC_BTN_PLAY)->MoveWindow(m_OldPlayBtnRect.left,m_OldPlayBtnRect.top,m_OldPlayBtnRect.Width(),m_OldPlayBtnRect.Height(),true);
this->GetDlgItem(IDC_BTN_PAUSE)->MoveWindow(m_OldPauseBtnRect.left,m_OldPauseBtnRect.top,m_OldPauseBtnRect.Width(),m_OldPauseBtnRect.Height(),true);
this->GetDlgItem(IDC_BTN_STOP)->MoveWindow(m_OldStopBtnRect.left,m_OldStopBtnRect.top,m_OldStopBtnRect.Width(),m_OldStopBtnRect.Height(),true);
this->GetDlgItem(IDC_quickForward)->MoveWindow(m_OldquickForward.left,m_OldquickForward.top, m_OldquickForward.Width(), m_OldquickForward.Height(),true);
this->GetDlgItem(ID_quickBack)->MoveWindow(m_OldquickBack.left,m_OldquickBack.top, m_OldquickBack.Width(), m_OldquickBack.Height(),true );
this->GetDlgItem(IDC_STATIC2)->MoveWindow(m_OldSTATIC2.left,m_OldSTATIC2.top, m_OldSTATIC2.Width(), m_OldSTATIC2.Height(),true);
this->GetDlgItem(IDC_STATIC3)->MoveWindow(m_OldSTATIC3.left,m_OldSTATIC3.top, m_OldSTATIC3.Width(), m_OldSTATIC3.Height(),true);
this->GetDlgItem(IDC_STATIC4)->MoveWindow(m_OldSTATIC4.left,m_OldSTATIC4.top, m_OldSTATIC4.Width(), m_OldSTATIC4.Height(),true);
this->GetDlgItem(IDC_SLIDER1)->MoveWindow(m_OldSLIDER1.left,m_OldSLIDER1.top, m_OldSLIDER1.Width(), m_OldSLIDER1.Height(),true);
this->GetDlgItem(IDC_controlrate)->MoveWindow(m_Oldcontrolrate.left,m_Oldcontrolrate.top, m_Oldcontrolrate.Width(), m_Oldcontrolrate.Height(),true);
this->GetDlgItem(IDC_LIST2)->MoveWindow(m_OldLIST2.left,m_OldLIST2.top, m_OldLIST2.Width(), m_OldLIST2.Height(),true);
m_bFullScreen=false;
return true;
}else
{
//先放大总框架
GetWindowPlacement(&m_OldWndPlacement);
CRectm_RectOfCurrentWindow,m_RectOfClient;
GetWindowRect(&m_RectOfCurrentWindow);
RepositionBars(0,0xffff,AFX_IDW_PANE_FIRST,reposQuery,&m_RectOfClient);
ClientToScreen(&m_RectOfClient);
intnFullWidth = GetSystemMetrics(SM_CXSCREEN);
intnFullHeight = GetSystemMetrics(SM_CYSCREEN);
CRectm_FSRect;
m_FSRect.left =m_RectOfCurrentWindow.left-m_RectOfClient.left;
m_FSRect.top = m_RectOfCurrentWindow.top- m_RectOfClient.top;
m_FSRect.right =m_RectOfCurrentWindow.right - m_RectOfClient.right+nFullWidth;
m_FSRect.bottom =m_RectOfCurrentWindow.bottom - m_RectOfClient.bottom + nFullHeight;
MoveWindow(&m_FSRect,TRUE);
//先保存播放窗口及各控件原来的位置,以便还原
this->m_VideoWindowPlay.GetWindowRect(this->m_OldPlayWndRect);
this->GetDlgItem(IDC_PROGRESS)->GetWindowRect(m_OldProgressRect);
this->GetDlgItem(IDC_BTN_OPEN)->GetWindowRect(m_OldOpenBtnRect);
this->GetDlgItem(IDC_BTN_PLAY)->GetWindowRect(m_OldPlayBtnRect);
this->GetDlgItem(IDC_BTN_PAUSE)->GetWindowRect(m_OldPauseBtnRect);
this->GetDlgItem(IDC_BTN_STOP)->GetWindowRect(m_OldStopBtnRect);
this->GetDlgItem(IDC_quickForward)->GetWindowRect(m_OldquickForward);
this->GetDlgItem(ID_quickBack)->GetWindowRect(m_OldquickBack);
this->GetDlgItem(IDC_STATIC2)->GetWindowRect(m_OldSTATIC2);
this->GetDlgItem(IDC_STATIC3)->GetWindowRect(m_OldSTATIC3);
this->GetDlgItem(IDC_STATIC4)->GetWindowRect(m_OldSTATIC4);
this->GetDlgItem(IDC_SLIDER1)->GetWindowRect(m_OldSLIDER1);
this->GetDlgItem(IDC_controlrate)->GetWindowRect(m_Oldcontrolrate);
this->GetDlgItem(IDC_LIST2)->GetWindowRect(m_OldLIST2);
CRectrect;
::GetWindowRect(::GetDesktopWindow(),rect);
//设置作为播放窗口的图片控件的位置
this->m_VideoWindowPlay.SetWindowPos(&wndTop,rect.left,rect.top,rect.Width(),rect.Height()-100,SWP_NOACTIVATE);
//设置视频图像显示输出的大小
this->m_VideoWindow->put_Left(rect.left);
this->m_VideoWindow->put_Top(rect.top);
this->m_VideoWindow->put_Width(rect.right - rect.left);
this->m_VideoWindow->put_Height(rect.bottom - rect.top);//播放窗口最大化
//设置其它控件新的位置
this->GetDlgItem(IDC_PROGRESS)->MoveWindow(rect.left,rect.bottom-90,rect.Width(),20,true);
CRectrectTemp;
this->GetDlgItem(IDC_BTN_OPEN)->GetWindowRect(rectTemp);
this->GetDlgItem(IDC_BTN_OPEN)->MoveWindow(rect.left+rect.Width()/2-600,rect.bottom-50,rectTemp.Width(),rectTemp.Height(),true);
this->GetDlgItem(IDC_BTN_PLAY)->MoveWindow(rect.left+rect.Width()/2-500,rect.bottom-50,rectTemp.Width(),rectTemp.Height(),true);
this->GetDlgItem(IDC_BTN_PAUSE)->MoveWindow(rect.left+rect.Width()/2-400,rect.bottom-50,rectTemp.Width(),rectTemp.Height(),true);
this->GetDlgItem(IDC_BTN_STOP)->MoveWindow(rect.left+rect.Width()/2-300,rect.bottom-50,rectTemp.Width(),rectTemp.Height(),true);
this->GetDlgItem(IDC_quickForward)->MoveWindow(rect.left+ rect.Width() / 2 -200, rect.bottom - 50, rectTemp.Width(), rectTemp.Height(),true);
this->GetDlgItem(ID_quickBack)->MoveWindow(rect.left+ rect.Width() / 2 -100, rect.bottom - 50, rectTemp.Width(), rectTemp.Height(),true);
this->GetDlgItem(IDC_STATIC2)->MoveWindow(rect.left+ rect.Width() / 2 +100, rect.bottom - 50, rectTemp.Width(), rectTemp.Height(),true);
this->GetDlgItem(IDC_STATIC3)->MoveWindow(rect.left+ rect.Width() / 2 +300, rect.bottom - 50, rectTemp.Width(), rectTemp.Height(),true);
this->GetDlgItem(IDC_STATIC4)->MoveWindow(rect.left+ rect.Width() / 2 +500, rect.bottom - 50, rectTemp.Width(), rectTemp.Height(),true);
this->GetDlgItem(IDC_SLIDER1)->MoveWindow(rect.left+ rect.Width() / 2 +150, rect.bottom - 50, rectTemp.Width(), rectTemp.Height(),true);
this->GetDlgItem(IDC_controlrate)->MoveWindow(rect.left+ rect.Width() / 2 +350, rect.bottom -50, rectTemp.Width(), rectTemp.Height(),true);
this->GetDlgItem(IDC_LIST2)->MoveWindow(rect.left+ rect.Width() / 2 +600, rect.bottom - 50, rectTemp.Width(), rectTemp.Height(),true);
ShowCaret();//显示鼠标
m_bFullScreen=true;
return true;
}
return false;
}
三、问题及解决方法
(1)安装配置开发环境,由于directshow已经集成到windows SDK 中,安装的时候往往无法正确安装,解决方法:安装子文件夹中的任意msi文件,之后再安装setup即可。配置开发环境时先要编译baseclasses,得到debug文件夹。之后在工程中需要包含路径,也出现了一些失误。
(2)在读写文件时不熟悉cstuiofile类,导致未能正确使用属性和文件读写出现许多失误,包括不能读取中文等。后来认真学习此类,又在网上找了许多资料完成了这一工作。
(3)全屏和恢复是一项繁琐的工作,如果不细心往往会出现差错。
代码可以下载:http://download.csdn.net/detail/u012483487/8463807