### 智能媒体播放器

网上有许多优秀的媒体播放器，例如金山影霸、千千静听等。图 1 和图 2 分别显示了这两种播放器。

图 1 金山影霸

图 2 千千静听

就连 Windows 系统也自带了一个非常优秀的媒体播放器——MediaPlayer。如图 3 所示。

图 3 Media Player

下面我们也来设计一个媒体播放器。它能够保存根据音频或视频文件决定是否显示视频窗口，能够调整视频图像的颜色，能够调整声音大小等。图 4 显示了媒体播放器的主窗口。

图 4 主窗口

图 5 显示了视频窗口。

图 5 视频窗口

图 6 显示了黑白图像的视频窗口。

图 6 黑白图像

图 7 显示了视频设置窗口。

图 7 设置视频

图 8 显示了调整色调、饱和度、亮度和对比度信息的视频窗口。

图 8 视频图像颜色调整

在实现媒体播放器时，笔者采用的是 DirectShow 进行设计的。

对于 Visual C++的初学者来说，应用 DirectShow 进行多媒体应用程序开发有一定的难度，它需要理解 DirectShow 的开发思路，需要具有一定的 Com 知识。下面笔者介绍如何在 Visual C++中应用 DirectShow 进行程序开发。

l 如何使用 Direct Show 开发包

DirectX 是微软公司推出的一套基于 Windows 平台的图像、声音、输入输出和网络游戏的编程接口。DirectX 被定义为与设备无关性，即 DirectX 可以使用与设备无关的方法提供设备相关的高性能。DirectX 是一个大家族，其成员主要包括“Direct Input”、“Direct Play”、“Direct Setup”、“Direct Music”、“Direct Sound”、“DirectX Media Objects”、“DirectX Graphics”和“Direct Show”等。其中，Direct Show 主要是为 Windows 平台处理媒体文件播放、语音视频采集提供了完整的解决方案。

在使用 Direct Show 之前，需要安装 DirectX 开发包和 Direct Show 开发包。用户可以到微软的官方网站上下载。这里需要说明的是，在 DirectX8.1 SDK 版本中包含有 Direct Show SDK，在 DirectX9.0C SDK 的第一个版本 DirectX SDK Summer 2004 中也包含有 Direct Show SDK，之后，在 DirectX SDK 中没有包含 Direct Show SDK，Direct Show SDK 以 Extras 的形式单独发布。

本章程序采用的 DirectX SDK 为“Microsoft DirectX SDK (April 2006)”，Direct Show SDK 为“DirectX SDK Extras Frebruary 2005”。

在安装完 DirectX SDK 和 Direct Show SDK 之后，为了能够在程序中使用 Direct Show，首先需要将 Visual C++2005 目录下的“BaseTsd.h”文件复制到 Direct Show SDK 安装目录的 Include 目录下。否则，在 Visual C++6.0 中使用 Direct Show 将无法通过编译。然后在 Visual C++6.0 开发环境中设置文件查找路径。具体步骤如下：

（1）单击“Tools/Options”菜单项打开选项窗口，选择“Directories”选项卡，如图 9 所示。

图 9 选项窗口

（2）单击“New”按钮创建一个列表，设置目录为 DirectX SDK 安装目录下的 Include 目录，如图 10 所示。

图 10 置 DirectX SDK 目录

（3）单击“New”按钮创建一个列表项，分别设置目录为 Direct Show SDK 安装目录下的 Include 目录，如图 11 所示。

图 11 设置 Direct Show SDK 目录

（4）在“Show directores for”组合框中选择“Library files”选项，单击“New”按钮新建一个列表项，设置目录为 Direct Show SDK 安装目录下“\LIB\X86”目录，用于查找库文件 Strmiids.lib 和 quartz.lib。如图 12 所示。

图 12 设置库文件路径

（5）调整“Directories”列表中的选项顺序，如图 13 所示。

图 13 调整列表项顺序

在完成了上面的配置之后，还需要引用“dshow.h”头文件，链接“Strmiids”和“quartz”库文件就可以在程序中使用 Direct Show SDK 了。

#include "dshow.h"

#pragma comment (lib,"Strmiids")

#pragma comment (lib,"quartz")

l 使用 Direct Show 开发程序的方法

Direct Show 提供了一个很有用的工具——graphedt，在 Direct Show 安装目录的 Utilities 目录下。在使用 Direct Show 开发应用程序前，可以使用 graphedt 预先设置出过滤图表。以播放一个 DAT 文件为例，使用 graphedt 打开 DAT 文件，graphedt 将根据系统信息生成过滤图像，如图 14 所示。

图 14 过滤图表

如果需要使用 Direct Show 实现播放 DAT 文件的功能，只要按照图表中显示的过滤器一一创建过滤器，并按顺序连接过滤器就可以了。当然，另一种更简单的方法就是使用过滤图表对象的 RenderFile 方法，该方法将根据文件名自动生成过滤图表。下面给出播放媒体文件的关键代码。

IGraphBuilder *pGraph; //定义过滤图表接口

ICaptureGraphBuilder2 * pBuilder; //定义图表构建接口对象

IMediaControl *pMediaControl ; //定义媒体控制接口

CoCreateInstance(CLSID_CaptureGraphBuilder2,0,CLSCTX_INPROC_SERVER,

IID_ICaptureGraphBuilder2,(void**)&pBuilder); //创建图表构建的接口对象

CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,

IID_IFilterGraph2, (void **)&pGraph); //创建过滤图表接口对象

pBuilder->SetFiltergraph(pGraph); //设置过滤图表

pGraph->RenderFile(strFile.AllocSysString(),NULL); //通过媒体文件构建默认的过滤图表

pGraph->QueryInterface(IID_IMediaControl,(void**)&pMediaControl); //获取媒体控制对象

pMediaControl->Run(); //开始播放文件

l 确定媒体文件播放完成

在设计媒体播放器时，需要在媒体文件播放完成后得到通知。因为，在设计文件播放列表时，需要在一个媒体文件播放完成后播放下一个文件。在 Direct Show 中，可以通过媒体控制接口和一个单独的线程来实现。具体步骤如下：

（1）创建一个媒体控制接口对象。

IMediaEventEx *pEvent; //定义媒体事件接口对象

pGraph->QueryInterface(IID_IMediaEventEx, (void **)&pEvent); //获取媒体事件接口对象

（2）创建一个线程，执行线程函数，在线程函数中判断媒体文件是否播放完成，如果没有播放完成，发送 CM_POSCHANGE 自定义消息，如果播放完成，发送 CM_COMPLETE 自定义消息。

DWORD WINAPI ThreadProc(LPVOID lpParameter )

{

CDirectShowEventDlg* pWnd = (CDirectShowEventDlg*)lpParameter; //获取主窗口指针

HANDLE hEvent; //定义事件句柄

pWnd->pEvent->GetEventHandle( (OAEVENT*) &hEvent); //获取事件句柄

long code,p1,p2; //定义事件代码参数

BOOL done = FALSE;

while (!done) //开始执行循环

{

pWnd->SendMessage(CM_POSCHANGE); //发送 CM_POSCHANGE 消息

if (WaitForSingleObject(hEvent,80)==WAIT_OBJECT_0) //Direct Show 是否有事件产生

{

//获取事件代码

while (SUCCEEDED( pWnd->pEvent->GetEvent(&code,&p1,&p2,0)))

{

pWnd->pEvent->FreeEventParams(code,p1,p2); //释放事件参数

if (code==EC_COMPLETE) //是否为播放完成

{

pWnd->m_Completed = TRUE; //设置播放完成状态

pWnd->SendMessage(CM_COMPLETE); //发送 CM_COMPLETE 消息

done=true; //退出循环，结束线程

}

}

}

}

return 0 ;

}

l 使用 Direct Show 进行音量和播放进度的控制

对于媒体播放软件来说，实现音量调节和播放进度的控制是不可获取的功能。在 Direct Show 中可以使用 IBasicAudio 接口来实现音量的控制。该接口提供了 get_Volume 和 put_Volume 方法用于获取和设置音量。如果想设置为静音，可以将音量设置为“-10000L”。下面的代码演示了音量的控制。

IBasicAudio* pAudio = NULL; //定义 IBasicAudio 接口指针

if (pGraph != NULL)

{

pGraph->QueryInterface(IID_IBasicAudio,(void**)&pAudio); //获取 IBasicAudio 接口对象

if (pAudio != NULL) //如果有音频数据

{

pAudio->get_Volume(&m_lVolumn); //获取当前的音量

if (m_lVolumn <0) //当前不是最大音量

{

m_lVolumn += 200; //增加音量

pAudio->put_Volume(m_lVolumn); //设置音量

}

else

{

m_lVolumn = 0; //当前音量为最大音量

}

}

}

如果想要控制播放的进度，可以使用 IMediaPosition 接口来实现。该接口提供有 get_StopTime 方法获取文件播放结束时的停止事件，提供有 get_CurrentPosition 方法获取当前的播放时间，提供有 put_CurrentPosition 方法设置播放时间，也就是设置播放进度。有了这些方法，实现播放进度的控制就易如反掌了。相关代码如下：

IMediaPosition* pPosition = NULL; //定义 IMediaPosition 接口指针

if (pGraph != NULL)

{

pGraph->QueryInterface(IID_IMediaPosition,(void**)&pPosition); //获取 IMediaPosition 接口对象

if (pPosition != NULL)

{

REFTIME curTime,endTime;

pPosition->get_StopTime(&endTime); //获取播放的停止时间

pPosition->get_CurrentPosition(&curTime); //获取但前的播放时间

curTime += 5; //设置快进

if (curTime <=endTime)

{

pPosition->put_CurrentPosition(curTime); //设置当前播放的时间

}

else

{

pPosition->put_CurrentPosition(endTime); //设置当前播放时间为停止时间

}

}

}

l 使用 Direct Show 实现亮度、饱和度和对比度调节

在媒体播放器模块中，笔者实现了对视频图像的亮度、饱和度和对比度的设置。主要方法是使用 VideoMixingRenderer9 过滤器支持的 IVMRMixerControl9 接口实现的，该接口提供有 SetProcAmpControl 方法用于设置图像的亮度、饱和度和对比度等信息。主要代码如下：

void CDirectShowEventDlg::SetViewInfo(int nFlag, float fValue)

{

IVMRMixerControl9 * pControl = NULL; //定义 IVMRMixerControl9 接口指针

if (pRender != NULL)

{

//获取 IVMRMixerControl9 接口对象

pRender->QueryInterface( IID_IVMRMixerControl9,(void**)&pControl);

if (pControl != NULL)

{

VMR9ProcAmpControl vmrParam; //定义参数

memset(&vmrParam,0,sizeof(VMR9ProcAmpControl)); //初始化参数

vmrParam.dwSize = sizeof(VMR9ProcAmpControl); //设置参数大小

vmrParam.dwFlags = nFlag; //设置标记

vmrParam.Brightness = fValue; //设置亮度值

vmrParam.Contrast = fValue; //设置对比度值

vmrParam.Hue = fValue; //设置色调值

vmrParam.Saturation = fValue; //设置饱和度值

pControl->SetProcAmpControl(0,&vmrParam); //设置视频图像颜色信息

}

}

}

下面我们来介绍媒体播放器的设计过程。

（1）创建一个基于对话框的工程，工程名称为“MediaPlayer”。

（2）创建一个对话框类，类名为“CDirectShowEventDlg”。

（3）向对话框中添加按钮、静态文本和滑块控件，设置主要控件属性，如表 1 所示。

表 1 媒体播放器主窗口控件属性设置

控件 ID | 控件属性 | 关联变量
IDC_CTLLIST | Caption：控制列表 Border：FALSE | CCustomGroup：m_CtlList
IDC_GRAY | Caption：黑白图像 | CButton：m_GrayBtn
IDC_PROCESSCTRL | 默认 | CCustomSlider：m_ProgressCtrl
IDC_CURPOS | Caption：空 Border：FALSE | CNumLabel：m_CurPos

（4）引用 Direct Show 相关头文件和库文件。

#include "dshow.h"

#include "D3d9.h"

#include "vmr9.h"

#include "Objbase.h"

#pragma comment (lib,"Strmiids")

#pragma comment (lib,"quartz")

（5）在应用程序初始化时初始化 Com 库，因为 Direct Show 操作是基于 Com 技术实现的。

CoInitialize(NULL); //初始化 Com 库

（6）向对话框中添加 FindPin 方法，用于查找某一过滤器的输入、输出引脚。

//查找引脚

IPin* CDirectShowEventDlg::FindPin(IBaseFilter *pFilter, PIN_DIRECTION dir)

{

IEnumPins* pEnumPins; //定义 IEnumPins 接口指针

IPin* pOutpin; //定义 IPin 接口指针

PIN_DIRECTION pDir; //定义引脚方向

pFilter->EnumPins(&pEnumPins); //列举过滤器引脚

while (pEnumPins->Next(1,&pOutpin,NULL)==S_OK)

{

pOutpin->QueryDirection(&pDir); //获取引脚方向(输入或输出引脚)

if (pDir==dir) //判断引脚方向

{

return pOutpin;

}

}

}

（7）向对话框中添加 Done 方法，当用户播放一个媒体文件时，将创建一个线程，用于检测媒体文件是否播放完成，如果播放完成将向主窗口发送自定义消息 CM_COMPLETE，该消息关联 Done 方法。Done 方法的作用是在媒体文件播放完成之后，终止线程，隐藏 Direct Show 的视频显示窗口，释放媒体控制接口对象指针，释放过滤图标，恢复对话框的成员变量为初始状态，修改视频显示窗口，使得在媒体文件播放完成后不可以调整窗口大小。

void CDirectShowEventDlg::Done(WPARAM wParam, LPARAM lParam)

{

if (m_hThread) //判断线程是否运行

{

TerminateThread(m_hThread,0); //终止线程

m_hThread = NULL;

}

if (pMediaControl != NULL) //是否正在播放

{

pMediaControl->Stop(); //停止播放

m_bStop = TRUE;

if (pViewWnd != NULL) //是否显示视频图像

{

pViewWnd->put_Visible(FALSE); //隐藏视频窗口

}

}

if (pMediaControl) //判断媒体控制接口指针是否为空

{

pMediaControl->Release(); //释放媒体控制接口指针

pMediaControl = NULL;

}

if (pGraph) //判断过滤图表接口指针是否为空

{

pGraph->Release(); //释放过滤图表接口指针

pGraph = NULL;

}

if (pEvent)

{

pEvent->Release(); //释放媒体控制对象

pEvent = NULL;

}

m_bFullScreen = FALSE; //将成员变量恢复为原来的状态

pViewWnd = NULL;

pBaseVideo = NULL;

pBase = NULL;

m_bViewPlay = FALSE;

m_lVolumn = 0;

m_bMute = FALSE;

m_bSpeed = FALSE;

m_bBack= FALSE;

m_bGrayImage = FALSE;

m_fSaturation = m_fBright = m_fContrast = m_fHue = 1;

pRender = NULL;

m_bStop = m_bPause = FALSE;

m_hThread = NULL;

m_Stop.SetWindowText("停止"); //设置停止按钮的文本

m_GrayBtn.SetWindowText("黑白图像");

m_Pause.SetWindowText("暂停");

m_Progress.SetText("00:00:00");

m_CurPos.SetText("00:00:00");

m_ProgressCtrl.SetPos(0);

//播放完成

m_Previewed = FALSE;

m_Completed = TRUE;

m_DisplayWnd.ModifyStyle(WS_SIZEBOX,0); //修改视频显示窗口的风格

m_DisplayWnd.m_Panel.ModifyStyle(SS_BLACKRECT,SS_BITMAP);

m_DisplayWnd.m_Panel.SetBitmap(m_DisplayWnd.bmp); //设置默认的位图

m_DisplayWnd.SetWindowPos(NULL,0,0,m_OriginRC.Width(),

m_OriginRC.Height(),SWP_NOMOVE); //恢复视频窗口大小

}

（8）向对话框中添加 OnPosChange 方法，用于时时显示当前的播放时间，当播放一个媒体文件时，将创建一个线程判断媒体文件是否播放完成，如果没有播放完成，则发送自定义消息 CM_POSCHANGE，该消息关联 OnPosChange 方法。

void CDirectShowEventDlg::OnPosChange()

{

if (pGraph != NULL)

{

IMediaPosition* pPosition = NULL; //定义 IMediaPosition 接口指针

//获取 IMediaPosition 接口对象

pGraph->QueryInterface(IID_IMediaPosition,(void**)&pPosition);

if (pPosition != NULL)

{

REFTIME endTime;

pPosition->get_CurrentPosition(&endTime); //获取当前的播放事件

m_ProgressCtrl.SetPos(endTime); //设置进度条的显示位置

int nHour = endTime / 3600; //将秒转换为小时:分:秒的形式

int nMinute = (endTime - nHour*3600)/60;

int nSecond = (int)endTime % 60;

CString csTime,csSpace;

csSpace = "";

if (nHour<10)

csSpace += "0%d:";

else

csSpace += "%d:";

if (nMinute<10)

csSpace += "0%d:";

else

csSpace += "%d:";

if (nSecond<10)

csSpace += "0%d";

else

csSpace += "%d";

csTime.Format(csSpace,nHour,nMinute,nSecond); //格式化字符串

m_CurPos.SetText(csTime); //显示播放事件

}

}

}

（9）向对话框中添加 PlayFile 方法，播放指定的媒体文件。该方法首先判断当前是否正在播放媒体文件，如果是则调用 Done 方法结束媒体文件的播放，然后构建适当的过滤图表，在构建完过滤图表之后，将创建一个线程，该线程的作用是修改过滤图表，默认情况下，Direct Show 使用 Video Renderer 过滤器来显示视频图像，该过滤器不能实现字幕叠加功能，不能够调节视频图像的亮度、饱和度和对比度，我们需要将该过滤器替换为 VideoMixingRenderer9 过滤器，线程函数的作用就是使用 VideoMixingRenderer9 过滤器替换 Video Renderer 过滤器。接着在 PlayFile 方法中将判断媒体文件是否包含视频显示，如果是则显示视频窗口，最后获取媒体文件的长度，创建一个单独的线程来检测媒体文件是否播放完成，

void CDirectShowEventDlg::PlayFile(LPCTSTR lpFileName)

{

if (pGraph != NULL) //之前已经播放文件或者正在播放文件

{

Done(0,0); //停止播放

}

m_bStop = FALSE; //初始化变量

pBuilder = NULL;

pGraph = NULL;

pMediaControl = NULL;

m_FileName = lpFileName;

CoCreateInstance(CLSID_CaptureGraphBuilder2,0,CLSCTX_INPROC_SERVER,

IID_ICaptureGraphBuilder2,(void**)&pBuilder); //创建图表构建接口对象

CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,

IID_IFilterGraph2, (void **)&pGraph); //创建过滤图表对象

pBuilder->SetFiltergraph(pGraph); //构造默认的过滤图表

m_bInvalidFile = FALSE;

pRender = NULL;

CoCreateInstance(CLSID_VideoMixingRenderer9, NULL, CLSCTX_ALL,

IID_IBaseFilter, (void **)&pRender); //创建 VideoMixingRenderer9 过滤器

m_ThreadStop = FALSE;

//创建一个单独的线程实现视频显示过滤器的替换

HANDLE hHandle = CreateThread(NULL,0,PlayVideoFile,(void*)this,0,NULL);

while (!m_ThreadStop) //等待线程函数执行完成

{

MSG msg;

::GetMessage(&msg,NULL,0,WM_USER); //在循环过程中响应界面操作

::TranslateMessage(&msg);

::DispatchMessage(&msg);

}

if (m_bInvalidFile) //文件非法

{

Done(0,0); //停止播放

return;

}

pViewWnd= NULL;

//获取预览窗口

pGraph->QueryInterface(IID_IVideoWindow,(void**)&pViewWnd);

if (pViewWnd)

{

//设置预览窗口的拥有者

pViewWnd->put_Owner((long)m_DisplayWnd.m_Panel.m_hWnd);

pViewWnd->put_Left(0);

pViewWnd->put_Top(0);

//获取预览窗口风格

long style;

pViewWnd->get_WindowStyle(&style);

style = style & ~WS_CAPTION;

style = style & ~WS_DLGFRAME;

style = style & WS_CHILD;

pViewWnd->put_WindowStyle(style);

//设置预览窗口宽度和高度

CRect rc;

m_DisplayWnd.m_Panel.GetClientRect(rc);

pViewWnd->put_Height(rc.Height());

pViewWnd->put_Width(rc.Width());

pViewWnd->put_MessageDrain((OAHWND)m_DisplayWnd.m_Panel. m_hWnd );

}

if (m_bViewPlay)

{

m_DisplayWnd.ShowWindow(SW_SHOW); //显示视频显示窗口

m_DisplayWnd.ModifyStyle(0,WS_SIZEBOX); //设置视频显示窗口风格

}

else

{

m_DisplayWnd.ShowWindow(SW_HIDE); //隐藏视频显示窗口

m_DisplayWnd.ModifyStyle(WS_SIZEBOX,0); //设置视频显示窗口风格

}

m_hThread = NULL;

//获取媒体控制接口对象

pGraph->QueryInterface(IID_IMediaControl,(void**)&pMediaControl);

pMediaControl->Run(); //开始播放文件

IMediaPosition* pPosition = NULL; //定义 IMediaPosition 接口指针

//获取 IMediaPosition 接口对象

pGraph->QueryInterface(IID_IMediaPosition,(void**)&pPosition);

if (pPosition != NULL)

{

REFTIME curTime,endTime;

pPosition->get_StopTime(&endTime); //获取文件播放的停止事件

pPosition->get_CurrentPosition(&curTime); //获取文件播放的当前事件

m_ProgressCtrl.SetRange(curTime,endTime); //设置进度条的范围

//将秒转换为小时:分:秒的形式

int nHour = endTime / 3600; //获取小时

int nMinute = (endTime - nHour*3600)/60; //获取分钟

int nSecond = (int)endTime % 60; //获取秒

CString csTime,csSpace;

csSpace = "";

if (nHour<10)

csSpace += "0%d:";

else

csSpace += "%d:";

if (nMinute<10)

csSpace += "0%d:";

else

csSpace += "%d:";

if (nSecond<10)

csSpace += "0%d";

else

csSpace += "%d";

csTime.Format(csSpace,nHour,nMinute,nSecond); //格式化字符串

m_Progress.SetText(csTime); //设置显示时间

}

pEvent = NULL;

//获取 IID_IMediaEventEx 接口对象

pGraph->QueryInterface(IID_IMediaEventEx, (void **)&pEvent);

m_Completed = FALSE;

DWORD threadID;

//开始一个线程，检测文件是否播放完成

m_hThread = CreateThread(NULL,0,ThreadProc,(void*)this,0,&threadID);

m_Previewed = TRUE;

}

（10）添加 PlayVideoFile 全局函数，该函数作为一个线程函数，当构建完媒体文件的过滤图表之后，将创建一个线程执行线程函数 PlayVideoFile，用于使用 VideoMixingRenderer9 过滤器替换 Video Renderer 过滤器。

DWORD WINAPI PlayVideoFile(LPVOID lpParameter )

{

//获取主窗口指针

CDirectShowEventDlg* pWnd = (CDirectShowEventDlg*)lpParameter;

CString strFile= pWnd->m_FileName; //获取播放的文件名

//构建过滤图标

HRESULT hRet = pWnd->pGraph->RenderFile(strFile.AllocSysString(),NULL);

if(hRet!=S_OK)

{

pWnd->m_bInvalidFile = TRUE; //非法的文件

return 1;

}

//获取 VideoRender filter

pWnd->pBase == NULL;

IBaseFilter *pRenderFiler = NULL;

//获取默认的视频显示过滤器

pWnd->pGraph->FindFilterByName(L"Video Renderer",(IBaseFilter**)&pRenderFiler);

if (pRenderFiler!=NULL) //包含视频信息

{

pWnd->m_bViewPlay = TRUE;

IPin* pVideoIn = NULL; //定义引脚接口指针

//查找输入引脚

pVideoIn = pWnd->FindPin(pRenderFiler,PINDIR_INPUT);

if (pVideoIn)

{

pVideoIn->Disconnect(); //与视频解码过滤器断开连接

}

pWnd->pGraph->RemoveFilter(pRenderFiler); //移除视频显示过滤器

//添加 VideoMixingRenderer9 过滤器

pWnd->pGraph->AddFilter(pWnd->pRender,L"Render");

//获取视频解码器

pWnd->pGraph->FindFilterByName(L"MPEG Video Decoder",(IBaseFilter**)&pWnd->pBase);

if (pWnd->pBase)

{

IPin* pOutPin = NULL;

//获取视频解码的输出引脚

pOutPin = pWnd->FindPin(pWnd->pBase,PINDIR_OUTPUT);

if (pOutPin != NULL)

{

IPin* pColorIn = NULL;

IPin* pColorOut = NULL;

//获取输入和输出引脚

IPin *pTextIn = NULL;

IPin *pTextOut = NULL;

IPin *pRenderIn = NULL;

pRenderIn = pWnd->FindPin(pWnd->pRender,PINDIR_INPUT);

HRESULT hRet = 0;

hRet = pOutPin->Disconnect(); //断开连接

//连接视频解码过滤器输出引脚与 VideoMixingRenderer9 过滤器输入引脚

hRet = pWnd->pGraph->ConnectDirect(pOutPin,pRenderIn,NULL);

}

}

}

else

{

pWnd->m_bViewPlay = FALSE; //没有视频信息

}

pWnd->m_ThreadStop = TRUE;

return 0 ;

}

（11）添加一个全局函数 ThreadProc，该函数将作为一个线程函数。当播放一个媒体文件时将创建一个线程来检测媒体文件是否播放完成，如果没有播放完成，则向主窗口发送 CM_POSCHANGE 消息执行 OnPosChange 方法显示当前的播放时间，否则向主窗口发送 CM_COMPLETE 消息执行 Done 方法结束媒体文件播放。

DWORD WINAPI ThreadProc(LPVOID lpParameter )

{

CDirectShowEventDlg* pWnd = (CDirectShowEventDlg*)lpParameter; //获取主窗口指针

HANDLE hEvent; //定义事件句柄

pWnd->pEvent->GetEventHandle( (OAEVENT*) &hEvent); //获取事件句柄

long code,p1,p2; //定义事件代码参数

BOOL done = FALSE;

while (!done) //开始执行循环

{

pWnd->SendMessage(CM_POSCHANGE); //发送 CM_POSCHANGE 消息

if (WaitForSingleObject(hEvent,80)==WAIT_OBJECT_0) //Direct Show 是否有事件产生

{

//获取事件代码

while (SUCCEEDED( pWnd->pEvent->GetEvent(&code,&p1,&p2,0)))

{

pWnd->pEvent->FreeEventParams(code,p1,p2); //释放事件参数

if (code==EC_COMPLETE) //是否为播放完成

{

pWnd->m_Completed = TRUE; //设置播放完成状态

pWnd->SendMessage(CM_COMPLETE); //发送 CM_COMPLETE 消息

done=true; //退出循环，结束线程

}

}

}

}

return 0 ;

}

（12）处理“打开文件”按钮的单击事件，利用文件打开对话框选择一个媒体文件，然后调用 PlayFile 方法播放媒体文件。

void CDirectShowEventDlg::OnSetFile()

{

CFileDialog fDlg(TRUE,NULL,NULL,OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,

"avi 文件|*.avi;*.dat;*.mp3;*.wav;*.mpeg|所有文件|*.*||",this); //定义文件打开对话框

if (fDlg.DoModal()==IDOK)

{

m_FileName = fDlg.GetPathName(); //获取文件名

m_Stop.SendMessage(WM_LBUTTONDOWN,0,0);

PlayFile(m_FileName); //开始播放媒体文件

}

}

（13）处理“抓图”按钮的单击事件，将抓取当前视频窗口的图像信息，将其保存到磁盘文件中。主要方法是使用 IbasicVideo 接口的 GetCurrentImage 方法来获得位图的信息后和位图数据。

void CDirectShowEventDlg::OnSnap()

{

CFileDialog flDlg(FALSE,"","Snap.bmp"); //定义文件保存对话框

if (pGraph != NULL)

{

IBasicVideo * pBasicVideo = NULL; //定义 IBasicVideo 接口指针

//获取 IBasicVideo 接口对象

pGraph->QueryInterface(IID_IBasicVideo, (void **)&pBasicVideo);

if (pBasicVideo != NULL)

{

pMediaControl->Pause(); //暂停播放

if ( flDlg.DoModal()==IDOK)

{

CString csSaveName = flDlg.GetPathName();//获取文件名

//获取图像大小，包含位图信息头

long lBmpSize ;

if (SUCCEEDED(pBasicVideo->GetCurrentImage(&lBmpSize, 0)))

{

//定义图像数据缓冲区，获取图像数据

BYTE* pData = new BYTE[lBmpSize];

if (SUCCEEDED(pBasicVideo->GetCurrentImage(&lBmpSize, (long*)pData)))

{

BITMAPFILEHEADER bFile;

bFile.bfReserved1 = bFile.bfReserved2 = 0;

bFile.bfSize = sizeof(BITMAPFILEHEADER);

bFile.bfType = 0x4d42;

bFile.bfOffBits = sizeof(BITMAPFILEHEADER)+

sizeof(BITMAPINFOHEADER);

CFile file; //定义文件对象

//创建并打开文件

file.Open(csSaveName,CFile::modeCreate|CFile::modeReadWrite);

//写入文件数据

file.Write(&bFile,sizeof(BITMAPFILEHEADER));

file.WriteHuge(pData,lBmpSize);

file.Close(); //关闭文件

}

delete [] pData; //释放位图数据

pBasicVideo->Release(); //释放 IBasicVideo 接口指针

}

}

pMediaControl->Run(); //继续播放媒体文件

}

}

}

（14）处理“全屏”按钮的单击事件，调用视频窗口对象的 put_FullScreenMode 方法设置全屏模式。

void CDirectShowEventDlg::OnFullScreen()

{

if (pViewWnd!= NULL && m_bStop==FALSE)

{

pViewWnd->put_FullScreenMode(-1); //设置全屏显示

m_bFullScreen = TRUE;

}

}

（15）改写对话框的 PreTranslateMessage 方法，在全屏模式下按“Esc”键将取消全屏显示。

BOOL CDirectShowEventDlg::PreTranslateMessage(MSG* pMsg)

{

if (pMsg->message == WM_KEYDOWN) //是否为按键消息

{

if (pMsg->wParam == VK_ESCAPE) //是否为 Esc 键

{

if (pViewWnd!= NULL)

{

pMsg->wParam == 0;

pViewWnd->put_FullScreenMode(0); //取消全屏显示

m_bFullScreen = FALSE;

return TRUE;

}

}

}

return CDialog::PreTranslateMessage(pMsg);

}

（16）向对话框中添加 SetViewInfo 方法，用于设置视频图像的颜色，如亮度、饱和度、对比度的信息。

void CDirectShowEventDlg::SetViewInfo(int nFlag, float fValue)

{

IVMRMixerControl9 * pControl = NULL; //定义 IVMRMixerControl9 接口指针

if (pRender != NULL)

{

//获取 IVMRMixerControl9 接口对象

pRender->QueryInterface( IID_IVMRMixerControl9,(void**)&pControl);

if (pControl != NULL)

{

VMR9ProcAmpControl vmrParam; //定义参数

memset(&vmrParam,0,sizeof(VMR9ProcAmpControl)); //初始化参数

vmrParam.dwSize = sizeof(VMR9ProcAmpControl); //设置参数大小

vmrParam.dwFlags = nFlag; //设置标记

vmrParam.Brightness = fValue; //设置亮度值

vmrParam.Contrast = fValue; //设置对比度值

vmrParam.Hue = fValue; //设置色调值

vmrParam.Saturation = fValue; //设置饱和度值

pControl->SetProcAmpControl(0,&vmrParam); //设置视频图像颜色信息

}

}

}

（17）处理“快进”按钮的单击事件，利用 IMediaPosition 接口的 put_CurrentPosition 方法来设置当前播放的位置，以实现快进功能。

void CDirectShowEventDlg::OnAddSpeed()

{

IMediaPosition* pPosition = NULL; //定义 IMediaPosition 接口指针

if (pGraph != NULL)

{

pGraph->QueryInterface(IID_IMediaPosition,(void**)&pPosition); //获取 IMediaPosition 接口对象

if (pPosition != NULL)

{

REFTIME curTime,endTime;

pPosition->get_StopTime(&endTime); //获取播放的停止时间

pPosition->get_CurrentPosition(&curTime); //获取但前的播放时间

curTime += 5; //设置快进

if (curTime <=endTime)

{

pPosition->put_CurrentPosition(curTime); //设置当前播放的时间

}

else

{

pPosition->put_CurrentPosition(endTime); //设置播放时间为停止时间

}

}

}

}

（18）处理“增大音量”按钮的单击事件，利用 IbasicAudi 接口的 put_Volume 方法来调节音量。

void CDirectShowEventDlg::OnVolumnmax()

{

IBasicAudio* pAudio = NULL; //定义 IBasicAudio 接口指针

if (pGraph != NULL)

{

pGraph->QueryInterface(IID_IBasicAudio,(void**)&pAudio); //获取 IBasicAudio 接口对象

if (pAudio != NULL) //如果有音频数据

{

pAudio->get_Volume(&m_lVolumn); //获取当前的音量

if (m_lVolumn <0) //当前不是最大音量

{

m_lVolumn += 200; //增加音量

pAudio->put_Volume(m_lVolumn); //设置音量

}

else

{

m_lVolumn = 0; //当前音量为最大音量

}

}

}

}

（19）处理“黑白图像”按钮的单击事件，调节视频图像的饱和度为最小值，这样就实现了黑白图像的效果。

void CDirectShowEventDlg::OnGray()

{

IVMRMixerControl9 * pControl = NULL; //定义 IVMRMixerControl9 接口指针

if (pRender != NULL)

{

//获取 IVMRMixerControl9 接口对象

pRender->QueryInterface( IID_IVMRMixerControl9,(void**)&pControl);

if (pControl != NULL )

{

VMR9ProcAmpControl vmrParam; //定义参数

memset(&vmrParam,0,sizeof(VMR9ProcAmpControl)); //初始化参数

vmrParam.dwSize = sizeof(VMR9ProcAmpControl); //设置参数大小

vmrParam.dwFlags = ProcAmpControl9_Saturation; //设置参数标记

//获取饱和度的最小值

VMR9ProcAmpControlRange range;

range.dwSize = sizeof(VMR9ProcAmpControlRange);

range.dwProperty = ProcAmpControl9_Saturation;

pControl->GetProcAmpControlRange(0, &range);

//设置饱和度为最小值

if (m_bGrayImage==FALSE) //设置黑白图像

{

VMR9ProcAmpControl getParam; //定义参数

memset(&getParam,0,sizeof(VMR9ProcAmpControl)); //初始化参数

getParam.dwSize = sizeof(VMR9ProcAmpControl); //设置参数大小

getParam.dwFlags = ProcAmpControl9_Saturation; //设置参数标记

pControl->GetProcAmpControl(0,&getParam); //获取饱和度

m_fSaturation = range.MinValue;

vmrParam.Saturation = m_fSaturation; //设置饱和度值

m_bGrayImage = TRUE;

m_GrayBtn.SetWindowText("彩色图像");

}

else //设置彩色图像

{

m_fSaturation = 1;

m_GrayBtn.SetWindowText("黑白图像");

vmrParam.Saturation = m_fSaturation;

m_bGrayImage = FALSE;

}

//设置黑白图像或彩色图像

HRESULT hRet = pControl->SetProcAmpControl(0,&vmrParam);

}

}

}

（20）创建一个对话框类，类名为“CDisplayWnd”，该类将作为视频显示窗口，显示影片图像。

（21）向对话框中添加一个图片等控件，设置图片控件的类型为 Bitmap，设置 Image 属性为程序中到导入的位图资源 ID。

（22）处理 CDisplayWnd 类的 WM_SIZE 消息，在对话框大小改变时调整视频图像的大小，使其适应窗口的大小。

void CDisplayWnd::OnSize(UINT nType, int cx, int cy)

{

CDialog::OnSize(nType, cx, cy);

CDirectShowEventDlg *pDlg = (CDirectShowEventDlg *)AfxGetMainWnd();

if (pDlg->m_bViewPlay)

{

CRect ClientRC,rc;

GetClientRect(ClientRC);

m_Panel.MoveWindow(ClientRC);

m_Panel.ModifyStyle(SS_BITMAP,SS_BLACKRECT);

m_Panel.GetClientRect(rc);

pDlg->pViewWnd->put_Height(rc.Height());

pDlg->pViewWnd->put_Width(rc.Width());

}

}

（23）新建一个对话框类，类名为“CVideoSet”，用于设置视频图像的色彩信息，包括视频图像的亮度、饱和度和对比度等

（24）向对话框中添加按钮、静态文本、群组框、编辑框、滑块等控件，如图 15 所示。

图 15 视频设置窗口

（25）处理对话框的 WM_HSCROLL 消息，在用户拖动滑块时，将执行该消息处理函数，设置视频图像相应的信息。

void CVideoSet::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)

{

if (pScrollBar != NULL)

{

CDirectShowEventDlg *pMainDlg = NULL;

//获取对话框主窗口指针

pMainDlg = (CDirectShowEventDlg *)AfxGetMainWnd();

pMainDlg->m_bGrayImage = FALSE;

pMainDlg->m_GrayBtn.SetWindowText("黑白图像"); //设置按钮文本

pMainDlg->m_GrayBtn.Invalidate(); //更新按钮

if (pScrollBar->m_hWnd==m_Hue.m_hWnd) //色调滑块的滚动消息

{

if (nSBCode==SB_THUMBPOSITION) //拖动滚动块

{

m_Hue.SetPos(nPos); //设置滑块位置

}

int nCurPos = m_Hue.GetPos(); //获取滑块位置

CString csPos;

csPos.Format("%i",nCurPos);

m_HueNum.SetWindowText(csPos);

//调用主对话框的 SetViewInfo 方法设置视频图像的色调

pMainDlg->SetViewInfo(ProcAmpControl9_Hue,nCurPos);

pMainDlg->m_fHue = nCurPos;

}

else if (pScrollBar->m_hWnd==m_Saturation.m_hWnd) //饱和度滑块的滚动消息

{

if (nSBCode==SB_THUMBPOSITION) //拖动滚动块

{

m_Saturation.SetPos(nPos); //设置滑块位置

}

int nCurPos = m_Saturation.GetPos(); //获取滑块位置

CString csPos;

csPos.Format("%i",nCurPos);

m_SatNum.SetWindowText(csPos);

//调用主对话框的 SetViewInfo 方法设置视频图像的饱和度

pMainDlg->SetViewInfo(ProcAmpControl9_Saturation,nCurPos/100.0);

pMainDlg->m_fSaturation = nCurPos/100.0;

}

else if (pScrollBar->m_hWnd==m_Brightness.m_hWnd) //亮度滑块的滚动消息

{

if (nSBCode==SB_THUMBPOSITION) //拖动滚动块

{

m_Brightness.SetPos(nPos); //设置滑块位置

}

int nCurPos = m_Brightness.GetPos(); //获取滑块位置

CString csPos;

csPos.Format("%i",nCurPos);

m_BrightNum.SetWindowText(csPos);

//调用主对话框的 SetViewInfo 方法设置视频图像的亮度

pMainDlg->SetViewInfo(ProcAmpControl9_Brightness,nCurPos);

pMainDlg->m_fBright = nCurPos;

}

else if (pScrollBar->m_hWnd==m_Contrast.m_hWnd) //对比度滑块的滚动消息

{

if (nSBCode==SB_THUMBPOSITION) //拖动滚动块

{

m_Contrast.SetPos(nPos); //设置滑块位置

}

int nCurPos = m_Contrast.GetPos(); //获取滑块位置

CString csPos;

csPos.Format("%i",nCurPos);

m_ConNum.SetWindowText(csPos);

//调用主对话框的 SetViewInfo 方法设置视频图像的对比度

pMainDlg->SetViewInfo(ProcAmpControl9_Contrast,nCurPos/100.0);

pMainDlg->m_fContrast = nCurPos/100.0;

}

}

CDialog::OnHScroll(nSBCode, nPos, pScrollBar);

}

（26）在对话框初始化时获取当前视频图像各项信息的默认值，作为视频设置窗口的初始值。

BOOL CVideoSet::OnInitDialog()

{

CDialog::OnInitDialog();

CDirectShowEventDlg *pMainDlg = NULL;

//获取对话框主窗口指针

pMainDlg = (CDirectShowEventDlg *)AfxGetMainWnd();

if (pMainDlg)

{

//设置色调滑块的滚动范围

m_Hue.SetRange(pMainDlg->m_HueRange.MinValue,pMainDlg->m_HueRange.MaxValue);

WPARAM wParam;

MAKEWPARAM(SB_THUMBPOSITION,pMainDlg->m_fHue);

m_Hue.SetPos(100);

m_Hue.SetPos(pMainDlg->m_fHue);

//执行对话框的 WM_HSCROLL 消息处理函数

SendMessage(WM_HSCROLL,wParam,(LPARAM)m_Hue.m_hWnd);

//设置饱和度滑块的滚动范围

m_Saturation.SetRange(pMainDlg->m_SatRange.MinValue*100,

pMainDlg->m_SatRange.MaxValue*100);

MAKEWPARAM(SB_THUMBPOSITION,pMainDlg->m_fSaturation*100);

m_Saturation.SetPos(100);

m_Saturation.SetPos(pMainDlg->m_fSaturation*100);

//执行对话框的 WM_HSCROLL 消息处理函数

SendMessage(WM_HSCROLL,wParam,(LPARAM)m_Saturation.m_hWnd);

//设置亮度滑块的滚动范围

m_Brightness.SetRange(pMainDlg->m_BrightRange.MinValue,

pMainDlg->m_BrightRange.MaxValue);

MAKEWPARAM(SB_THUMBPOSITION,pMainDlg->m_fBright);

m_Brightness.SetPos(100);

m_Brightness.SetPos(pMainDlg->m_fBright);

//执行对话框的 WM_HSCROLL 消息处理函数

SendMessage(WM_HSCROLL,wParam,(LPARAM)m_Brightness.m_hWnd);

//设置对比度滑块的滚动范围

m_Contrast.SetRange(pMainDlg->m_ConRange.MinValue*100,

pMainDlg->m_ConRange.MaxValue*100);

MAKEWPARAM(SB_THUMBPOSITION,1*100);

m_Contrast.SetPos(100);

m_Contrast.SetPos(1*100);

//执行对话框的 WM_HSCROLL 消息处理函数

SendMessage(WM_HSCROLL,wParam,(LPARAM)m_Contrast.m_hWnd);

}

return TRUE;

}

（27）处理“默认”按钮的单击事件，恢复视频图像的默认效果。

void CVideoSet::OnDefault()

{

CDirectShowEventDlg *pMainDlg = NULL;

pMainDlg = (CDirectShowEventDlg *)AfxGetMainWnd(); //获取主对话框指针

if (pMainDlg)

{

//设置色调滑块的滚动范围

m_Hue.SetRange(pMainDlg->m_HueRange.MinValue,pMainDlg->m_HueRange.MaxValue); WPARAM wParam;

MAKEWPARAM(SB_THUMBPOSITION,1);

m_Hue.SetPos(100);

m_Hue.SetPos(1);

//执行对话框的 WM_HSCROLL 消息处理函数

SendMessage(WM_HSCROLL,wParam,(LPARAM)m_Hue.m_hWnd);

pMainDlg->m_fHue = 1;

//设置饱和度滑块的滚动范围

m_Saturation.SetRange(pMainDlg->m_SatRange.MinValue*100,

pMainDlg->m_SatRange.MaxValue*100);

MAKEWPARAM(SB_THUMBPOSITION,1*100);

m_Saturation.SetPos(100);

m_Saturation.SetPos(1*100);

//执行对话框的 WM_HSCROLL 消息处理函数

SendMessage(WM_HSCROLL,wParam,(LPARAM)m_Saturation.m_hWnd);

pMainDlg->m_fSaturation = 1;

//设置亮度滑块的滚动范围

m_Brightness.SetRange(pMainDlg->m_BrightRange.MinValue,

pMainDlg->m_BrightRange.MaxValue);

MAKEWPARAM(SB_THUMBPOSITION,1);

m_Brightness.SetPos(100);

m_Brightness.SetPos(1);

//执行对话框的 WM_HSCROLL 消息处理函数

SendMessage(WM_HSCROLL,wParam,(LPARAM)m_Brightness.m_hWnd);

pMainDlg->m_fBright = 1;

//设置对比度滑块的滚动范围

m_Contrast.SetRange(pMainDlg->m_ConRange.MinValue*100,

pMainDlg->m_ConRange.MaxValue*100);

MAKEWPARAM(SB_THUMBPOSITION,1*100);

m_Contrast.SetPos(100);

m_Contrast.SetPos(1*100);

//执行对话框的 WM_HSCROLL 消息处理函数

SendMessage(WM_HSCROLL,wParam,(LPARAM)m_Contrast.m_hWnd);

pMainDlg->m_fContrast = 1;

}

}

（28）处理“色调”编辑框的文本改变时的事件，当用户在编辑框中输入色调值时将设置滑块显示的位置，这样会触发对话框的 WM_HSCROLL 消息，最终在 WM_HSCROLL 消息处理函数中设置色调信息。

void CVideoSet::OnChangeHuenum()

{

CString csText;

m_HueNum.GetWindowText(csText); //获取色调值

if (!csText.IsEmpty())

{

m_Hue.SetPos(atoi(csText)); //设置色调滑块位置

}

}