DirectShow学习之三媒体播放过程分析

2011年12月04日 分类:学习笔记C++

上一篇做了一个简单的媒体文件播放器,它到底与其它程序有什么不同呢?很大的一个关键就在于我们使用了CDXGraph.h和CDXGraph.cpp这两个文件,只有这两个东西不是我们自己的,现在就把它变成我们自己的。

    先看看我们做的程序,要播放媒体,就两个步骤,打开文件,播放。跟踪代码的执行过程,无非就是这样:
通过“打开对话框”得到文件:mSourceFile = dlgOpen.GetPathName();
然后CreateGraph(),看:
void CMyPlayerDlg::CreateGraph(void)
{
    DestroyGraph();//老套路了,先破坏,确保mFilterGraph是NULL,先看DestroyGraph()
    mFilterGraph = new CDXGraph();
    if (mFilterGraph->Create())
    {
        mFilterGraph->RenderFile(mSourceFile);
        mFilterGraph->SetDisplayWindow(mVideoWindow.GetSafeHwnd());
        mFilterGraph->SetNotifyWindow(this->GetSafeHwnd());
        mFilterGraph->Pause();
    }
}
看DestroyGraph():
void CMyPlayerDlg::DestroyGraph(void)
{//这个函数的作用就是:如果“打开”了就“关闭”,否则什么也不干,
//就是运行了这个函数,一定是“关闭”的
    if (mFilterGraph)//CDXGraph *   mFilterGraph,是CDXGraph指针
    {
        mFilterGraph->Stop();
        mFilterGraph->SetNotifyWindow(NULL);
        delete mFilterGraph;
        mFilterGraph = NULL;
    }
}
CreateGraph函数我们从new CDXGraph()开始“研究”,就是看其构造函数,什么都没做,就是初始化,往下看mFilterGraph->Create():
bool CDXGraph::Create(void)
{
    if (!mGraph)
    {
        if (SUCCEEDED(CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,IID_IGraphBuilder, (void **)&mGraph)))
        {
            //AddToObjectTable();这个是用来做GraphEdit调试的,暂且去掉
            return QueryInterfaces();
        }
        mGraph = 0;
    }
    return false;
}
第一个不认识的是mGraph,mGraph是什么?是IGraphBuilder *     mGraph;
    DirectShow是基于模块化的,使用一个Filter Graph Manager来管理整个数据流的处理过程,参与处理的各个功能模块叫做Filter(国内目前的翻译有滤波器、过滤器、滤镜、筛选器等等乱七八糟的), 各个Filter在Filter Graph中按一定的顺序连接成一条流水线协调工作,大致分为3类:Source Filters(负责获取数据)、Transform Filters(负责数据的格式转换,例如数据流的分离/合成、解码/编码)、Rendering Filters(负责数据的去向,显卡、声卡、文件等)。
    DirectShow建立在COM组件技术基础上,DirectShow与COM紧密相连,它 所有的部件和功能都由COM接口来构造和实现,其中几个重要的接口经常需要用到的:IGraphBuilder接口,用来创建Filter Graph Manager;IMediaControl接口,用来控制流媒体在Filter Graph中的流动,例如流媒体的启动和停止;IMediaEvent接口,该接口在Filter Graph发生一些事件时用来创建事件的标志信息并传送给应用程序(参考附2);IVideoWindow: 用于设置多媒体播放窗口的属性。(更多接口请参考附1)
    所以,在CDXGraph类中有如下一些定义:
    IGraphBuilder *     mGraph;
    IMediaControl *     mMediaControl;
    IMediaEventEx *     mEvent;
IVideoWindow *     mVideoWindow;
因此,程序中if (!mGraph)判断还没有创建Filter Graph Manager,然后 SUCCEEDED(CoCreateInstance(CLSID_FilterGraph,NULL,CLSCTX_INPROC_SERVER,IID_IGraphBuilder, (void )&mGraph))创建Filter Graph Manager。
外面的SUCCEEDED是一个宏:
#define SUCCEEDED(Status)     ((HRESULT)(Status) >= 0)
CoCreateInstance是com的api函数。
接着QueryInterfaces()查询各接口,获取Filter Graph 和IMediaEvent等组件的指针;
bool CDXGraph::QueryInterfaces(void)
{
    if (mGraph)
    {
        HRESULT hr = NOERROR;
        hr |= mGraph->QueryInterface(IID_IMediaControl, (void )&mMediaControl);
        hr |= mGraph->QueryInterface(IID_IMediaEventEx, (void )&mEvent);
        hr |= mGraph->QueryInterface(IID_IBasicVideo, (void )&mBasicVideo);
        hr |= mGraph->QueryInterface(IID_IBasicAudio, (void )&mBasicAudio);
        hr |= mGraph->QueryInterface(IID_IVideoWindow, (void )&mVideoWindow);
        hr |= mGraph->QueryInterface(IID_IMediaSeeking, (void )&mSeeking);
        if (mSeeking)
        {
            mSeeking->SetTimeFormat(&TIME_FORMAT_MEDIA_TIME);
        }
        return SUCCEEDED(hr);
    }
    return false;
}
mFilterGraph->Create()成功创建Filter Graph Manager之后,就是
mFilterGraph->RenderFile(mSourceFile);
看如何实现:
bool CDXGraph::RenderFile(const char * inFile)
{
    if (mGraph)
    {
        WCHAR    szFilePath[MAX_PATH];
        MultiByteToWideChar(CP_ACP, 0, inFile, -1, szFilePath, MAX_PATH);
        if (SUCCEEDED(mGraph->RenderFile(szFilePath, NULL)))
        {
            return true;
        }
    }
    return false;
}
先对字符串作一个转换,关键在mGraph->RenderFile(szFilePath, NULL),即IGraphBuilder::RenderFile函数,内部实现暂且不管。
mFilterGraph->RenderFile(mSourceFile);后是
mFilterGraph->SetDisplayWindow(mVideoWindow.GetSafeHwnd());即:
bool CDXGraph::SetDisplayWindow(HWND inWindow)
{
    if (mVideoWindow)
    {
        mVideoWindow->put_Visible(OAFALSE);
        mVideoWindow->put_Owner((OAHWND)inWindow);
        RECT windowRect;
        ::GetClientRect(inWindow, &windowRect);
        mVideoWindow->put_Left(0);
        mVideoWindow->put_Top(0);
        mVideoWindow->put_Width(windowRect.right - windowRect.left);
        mVideoWindow->put_Height(windowRect.bottom - windowRect.top);
        mVideoWindow->put_WindowStyle(WS_CHILD|WS_CLIPCHILDREN|WS_CLIPSIBLINGS);
        mVideoWindow->put_MessageDrain((OAHWND) inWindow);
        if (inWindow != NULL)
        {
            mVideoWindow->put_Visible(OATRUE);
        }
        else
        {
            mVideoWindow->put_Visible(OAFALSE);
        }
        return true;
    }
    return false;
}
上面这个很好理解,无非就是播放窗口的设置,除了上面设置的,还有全屏啊什么的设置,不一一列出。
再往下就是事件通知处理mFilterGraph->SetNotifyWindow(this->GetSafeHwnd());,即:
bool CDXGraph::SetNotifyWindow(HWND inWindow)
{
    if (mEvent)
    {
        mEvent->SetNotifyWindow((OAHWND)inWindow, WM_GRAPHNOTIFY, 0);
        return true;
    }
    return false;
}
然后先暂停(代码不再详细列出)mMediaControl->Pause(),当然做这些操作之前应该先获得状态,例如:
bool CDXGraph::IsPaused(void)
{
    if (mGraph && mMediaControl)
    {
        OAFilterState state = State_Stopped;
        if (SUCCEEDED(mMediaControl->GetState(10, &state)))
        {
            return state == State_Paused;
        }
    }
    return false;
}
打开文件就做这么多,至于播放,就是一个:mMediaControl->Run()。
下面不使用CDXGraph类来创建我们自己的工程,用vc创建MFC对话框工程MyPlayer2,和MyPlayer一样做好各个设置,为对话框头文件增加#include <streams.h>,引进CDXGraph类的过程不要,设置好界面,打开按钮不要,只要一个播放按钮,为播放按钮添加函数,精简的代码(省略了一些细节)如下:
void CMyPlayer2Dlg::OnBnClickedButtonplay()
{
    // TODO: Add your control notification handler code here
    IGraphBuilder *     mGraph;
    IMediaControl *     mMediaControl;
    IVideoWindow *     mVWindow;
    CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void)&mGraph);
    if (mGraph)
    {
        mGraph->QueryInterface(IID_IMediaControl, (void )&mMediaControl);
        mGraph->QueryInterface(IID_IVideoWindow, (void )&mVWindow);
    }
    mGraph->RenderFile(L”g://cctv00.mpg”, NULL);
    HWND inWindow=mVideoWindow.GetSafeHwnd();
    if (mVWindow)
    {
        mVWindow->put_Visible(OAFALSE);
        mVWindow->put_Owner((OAHWND)inWindow);
        RECT windowRect;
        ::GetClientRect(inWindow, &windowRect);
        mVWindow->put_Left(0);
        mVWindow->put_Top(0);
        mVWindow->put_Width(windowRect.right - windowRect.left);
        mVWindow->put_Height(windowRect.bottom - windowRect.top);
        mVWindow->put_WindowStyle(WS_CHILD|WS_CLIPCHILDREN|WS_CLIPSIBLINGS);
        mVWindow->put_MessageDrain((OAHWND) inWindow);
        if (inWindow != NULL)
        {
            mVWindow->put_Visible(OATRUE);
        }
        else
        {
            mVWindow->put_Visible(OAFALSE);
        }
    }
    mMediaControl->Run();
}
其中大部分代码是窗口的设置,我们甚至连那个窗口也不要,仅仅在对话框中添加一个播放按钮:
void CMyPlayer2Dlg::OnBnClickedButtonplay()
{
    IGraphBuilder *     mGraph;
    IMediaControl *     mMediaControl;
    CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void)&mGraph);
    mGraph->QueryInterface(IID_IMediaControl, (void )&mMediaControl);
    mGraph->RenderFile(L”g://cctv00.mpg”, NULL);
    mMediaControl->Run();
}
上面六行代码告诉了我们播放一个媒体文件的必需步骤:
1,通过API函数CoCreateInstance()创建一个Filter Graph Manager 实例;

2,通过调用QueryInterface ( )函数来获取组件的指针;

3,对Filter Graph进行控制和对事件作出响应。

附1:
转载:
DirectShow之接口实战篇(一)作者:pejaq(子墨书屋)
现今自己编程做一个多媒体播放工具是一件很令人开心愉悦的事情,但如果使用MediaPlay控件开发则会受到很多限制,自己的很 多好的创意想法都无法或者很难实现,如果利用微软的DirectX接口开发则可以充分的将作者的独特想法付诸于实现,何乐而不为呢!!不过关于 DirectShow接口的开发说明文档实在是少之又少,仅有的一些不是英文的就是一些关于理论方面的,真正关于接口实战编程而且是用Delphi开发工 具实现的更是凤毛麟角,使很多人都望而却步。在这里,我把我应用Directshow开发的心得以及我搜集到一些资料重新整理编辑出来公布,希望对所有由 此兴趣的同仁有所帮助,就算达到了我的目的。废话少说,进入正文。
       既然是接口实战篇,就先把一些常用的接口列出来,让大家有一些基本的认识,都是用来做什么的,什么时候我们会需要用到此接口。
IFilterGraph
过滤通道接口
IFilterGraph2
增强的IFilterGraph
IGraphBuilder
最为重用的COM接口,用于手动或者自动构造过滤通道Filter Graph Manager
IMediaControl
用来控制流媒体,例如流的启动和停止暂停等,播放控制接口
IMediaEvent
播放事件接口 ,该接口在Filter Graph发生一些事件时用来创建事件的标志信息并传送给应用程序
IMediaEventEx
扩展播放事件接口
IMediaPosition
播放的位置和速度控制接口(控制播放位置只能为设置时间控制方式)
IMediaSeeking
另一个播放的位置和播放速度控制接口,在位置选择方面功能较强.设置播放格式,多种控制播放方式.常用的有:(1)TIME_FORMAT_MEDIA_TIME单位100纳秒。(2)TIME_FORMAT_FRAME按帧播放
IBasicAudio
声音控制接口
IBasicVideo
图像控制接口(波特率,宽度,长度等信息)
IVideoWindow
显示窗口控制接口 (有关播放窗口的一切控制,包括caption显示,窗口位置控制等)
ISampleGrabber
捕获图象接口(可用于抓图控制)
IVideoFrameStep
控制单帧播放的接口
好了,熟悉了应用DirectShow应用开发常用的接口后,我们就通过一个实例媒体播放器来熟悉掌握这些接口,实例的代码虽然简单,但五脏俱全,功能强大,同时也了解一下应用DirectShow开发一般常用的步骤。
附2:
EC_ACTIVATE 视频窗口被激活或者转为非激活状态
EC_BUFFERING_DATA 过滤图形包含缓冲数据
EC_CLOCK_CHANGED 参考时钟被改变 EC_CLOCK_UNSET 时钟提供者被断开 EC_COMPLETE 所有数据被渲染完毕 EC_DEVICE_LOST 一个即插即用设备被移除或者变为有效. EC_DISPLAY_CHANGED 显示模式被改变 EC_END_OF_SEGMENT 到达段的末尾. EC_ERROR_STILLPLAYING 一个异步命令失败 EC_ERRORABORT 一个操作被放弃 EC_EXTDEVICE_MODE_CHANGE 不支持 EC_FULLSCREEN_LOST 一个视频渲染窗口被切换出全屏模式. EC_GRAPH_CHANGED 过滤器图被改变 EC_LENGTH_CHANGED 源的长度被改变. EC_NEED_RESTART 过滤器请求过滤图重新开始. EC_NOTIFY_WINDOW 通报一个视频渲染窗口的过滤器 EC_OLE_EVENT 过滤器传递一个字符串给应用程序。. EC_OPENING_FILE 过滤图打开一个文件,或者已经完成了打开文件操作 EC_PALETTE_CHANGED 视频调色板被改变. EC_PAUSED 一个暂停请求被处理. EC_QUALITY_CHANGE 过滤图为了质量控制丢桢 EC_REPAINT 一个视频渲染器要求重绘. EC_SEGMENT_STARTED 一个新段开始 EC_SHUTTING_DOWN 过滤器图被关闭 EC_SNDDEV_IN_ERROR 一个音频设备的输入引脚错误. EC_SNDDEV_OUT_ERROR 一个音频设备的输出引脚错误. EC_STARVATION 过滤器没有得到足够的数据. EC_STATE_CHANGE 过滤器图状态改变 EC_STEP_COMPLETE 一个过滤器执行了单桢渐进 EC_STREAM_CONTROL_STARTED 流控制开始命令产生效果. EC_STREAM_CONTROL_STOPPED 一个流控制的停止命令产生效果 EC_STREAM_ERROR_STILLPLAYING 在流中产生了一个错误,但流还是在运行中. EC_STREAM_ERROR_STOPPED 一个流因错误而停止 EC_TIMECODE_AVAILABLE 不支持 EC_USERABORT 用户中断回放. EC_VIDEO_SIZE_CHANGED 本地视频尺寸改变. EC_WINDOW_DESTROYED 视频渲染器被销毁,或者从过滤器图中移除.

作者:wuyuan 本文来自Wuyuan's Blog 转载请注明,谢谢! 文章地址: https://wuyuans.com/2011/12/directshow-learning-media-player-analysis