视频播放 via DirectShow

本文深入探讨了DirectShow,微软的流媒体处理框架,详细介绍了其视频播放原理和实现过程,包括FilterGraph的构建、播放控制及视频渲染等关键环节。

DirectShow 简介

DirectShow(有时缩写为 DS 或 DShow),开发代号 Quartz,是微软在 ActiveMovie 和 Video for Windows 的基础上推出的新一代基于 COM 的流媒体处理的开发包,与 DirectX 开发包一起发布。DShow 使用一种叫 Filter Graph 的模型来管理整个数据流的处理过程,有了 DShow,我们可以很方便地从支持 WDM 驱动模型的采集卡上捕获数据,并且进行相应的后期处理乃至存储到文件中。这样使在多媒体数据库管理系统(MDBMS)中多媒体数据的存取变得更加方便。它广泛地支持各种媒体格式,包括 asf、mpeg、avi、dv、mp3、wav 等,为多媒体流的捕捉和回放提供了强有力的支持。

DirectShow 播放视频

MSDN 上有基于本文所用代码的类似文章,全英文的,有兴趣的请走 这边

播放流程

dshow v play

播放代码

以下是整个 DShow 播放过程的概要代码,略去各个函数的具体实现和资源释放:

hr = CoInitialize(NULL);

MainWindow *pWin = new MainWindow();
hr = pWin->Create(hInstance);
hr = pWin->Show(nCmdShow);

m_pPlayer = new DShowPlayer(m_hwnd);

// Set the event notification window.
hr = m_pPlayer->SetEventWindow(m_hwnd, WM_GRAPH_EVENT);
hr = m_pPlayer->OpenFile(szFileName);

m_pPlayer->Play();
m_pPlayer->Pause();
m_pPlayer->SetPosition(ONE_MSEC * pInfo->position); // Seek
m_pPlayer->Stop();

delete m_pPlayer;
delete pWin;
CoUninitialize();

DShowPlayer::OpenFile 函数

打开媒体文件,创建并连接 filter graph。

HRESULT DShowPlayer::OpenFile(const WCHAR* sFileName)
{
    HRESULT hr = S_OK;
    IBaseFilter *pSource = NULL;
    // Create a new filter graph. (This also closes the old one, if any.)
    hr = InitializeGraph();
    
    // Add the source filter to the graph.
    hr = m_pGraph->AddSourceFilter(sFileName, NULL, &pSource);
    
    // Try to render the streams.
    hr = RenderStreams(pSource);
    
    // Get the seeking capabilities.
    hr = m_pSeek->GetCapabilities(&m_seekCaps);
    
    // Set the volume.
    hr = UpdateVolume();
    
    // Update our state.
    m_state = STATE_STOPPED;
    
    SAFE_RELEASE(pSource);
    return hr;
}
DShowPlayer::InitializeGraph 函数

创建 filter graph,并获得相应的控制接口。

HRESULT DShowPlayer::InitializeGraph()
{
    HRESULT hr = S_OK;
    TearDownGraph();

    // Create the Filter Graph Manager.
    hr = CoCreateInstance( CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, 
                           IID_IGraphBuilder, (void**)&m_pGraph );

    // Query for graph interfaces.
    hr = m_pGraph->QueryInterface(IID_IMediaControl, (void**)&m_pControl);

    hr = m_pGraph->QueryInterface(IID_IMediaEventEx, (void**)&m_pEvent);

    hr = m_pGraph->QueryInterface(IID_IMediaSeeking, (void**)&m_pSeek);

    hr = m_pGraph->QueryInterface(IID_IBasicAudio, (void**)&m_pAudio);

    // Set up event notification.
    hr = m_pEvent->SetNotifyWindow((OAHWND)m_hwndEvent, m_EventMsg, NULL);
    return hr;
}
DShowPlayer::RenderStreams 函数

连接各个 filter 和 render。

HRESULT DShowPlayer::RenderStreams(IBaseFilter *pSource)
{
    BOOL bRenderedAnyPin = FALSE;
    IFilterGraph2 *pGraph2 = NULL;
    IEnumPins *pEnum = NULL;
    IBaseFilter *pAudioRenderer = NULL;
    
    hr = m_pGraph->QueryInterface(IID_IFilterGraph2, (void**)&pGraph2);
    RETURN_IF_FAILED(hr);
    
    hr = CreateVideoRenderer();
    RETURN_IF_FAILED(hr);
    
    hr = AddFilterByCLSID(m_pGraph, CLSID_DSoundRender, &pAudioRenderer, L"Audio Renderer");
    RETURN_IF_FAILED(hr);
    
    hr = pSource->EnumPins(&pEnum);
    IPin *pPin = NULL;
    while (S_OK == pEnum->Next(1, &pPin, NULL)) {
        HRESULT hr2 = pGraph2->RenderEx(pPin, AM_RENDEREX_RENDERTOEXISTINGRENDERERS, NULL);
        pPin->Release();
        if (SUCCEEDED(hr2))
            bRenderedAnyPin = TRUE;
    }
    
    // Remove un-used renderers.
    hr = m_pVideo->FinalizeGraph(m_pGraph);
    
    BOOL bAudRemoved = FALSE;
    hr = RemoveUnconnectedRenderer(m_pGraph, pAudioRenderer, &bAudRemoved);
    m_bAudioStream = !bAudRemoved;
    
    if (!bRenderedAnyPin)
        hr = VFW_E_CANNOT_RENDER;
    return hr;
}
DShowPlayer::CreateVideoRenderer 函数

关于 EVR,VMR7 和 VMR9 的区别请看 MSDN

HRESULT DShowPlayer::CreateVideoRenderer()
{
    HRESULT hr = E_FAIL;
    enum { Try_EVR, Try_VMR9, Try_VMR7 };
    
    for (DWORD i = 0; i <= Try_VMR7; i++) {
        switch (i) {
        case Try_EVR:
            m_pVideo = new EVR();
            break;
            
        case Try_VMR9:
            m_pVideo = new VMR9();
            break;
            
        case Try_VMR7:
            m_pVideo = new VMR7();
            break;
        }
        
        hr = m_pVideo->AddToGraph(m_pGraph, m_hwndVideo);
        if (SUCCEEDED(hr))
            break;
        SAFE_DELETE(m_pVideo);
    }
    
    return hr;
}

下面选择 EVR 继续。

EVR::AddToGraph 函数

把 EVR 加到 filter graph 中并初始化。

HRESULT EVR::AddToGraph(IGraphBuilder *pGraph, HWND hwnd)
{
    HRESULT hr = S_OK;
    IBaseFilter *pEVR = NULL;
    
    hr = AddFilterByCLSID(pGraph, CLSID_EnhancedVideoRenderer, &pEVR, L"EVR");
    RETURN_IF_FAILED(hr);
    
    hr = InitializeEVR(pEVR, hwnd, &m_pVideoDisplay);
    RETURN_IF_FAILED(hr);
    
    m_pEVR = pEVR;
    m_pEVR->AddRef();
    SAFE_RELEASE(pEVR);
    return hr;
}
EVR::InitializeEVR 函数

绑定 EVR 和 窗口句柄,设置视频显示宽高比为原始比例(通常会使得视频窗口边上部分涂黑)。

HRESULT InitializeEVR( IBaseFilter *pEVR, HWND hwnd, IMFVideoDisplayControl** ppDisplayControl ) 
{ 
    HRESULT hr = S_OK;
    IMFGetService *pService = NULL;
    IMFVideoDisplayControl *pDisplay = NULL;
    
    hr = pEVR->QueryInterface(__uuidof(IMFGetService), (void**)&pService); 
    hr = pService->GetService(MR_VIDEO_RENDER_SERVICE, __uuidof(IMFVideoDisplayControl), (void**)&pDisplay);
    hr = pDisplay->SetVideoWindow(hwnd);
    
    // Preserve aspect ratio by letter-boxing
    hr = pDisplay->SetAspectRatioMode(MFVideoARMode_PreservePicture);
    *ppDisplayControl = pDisplay;
    (*ppDisplayControl)->AddRef();
    
    SAFE_RELEASE(pService);
    SAFE_RELEASE(pDisplay);
    return hr; 
}

DShowPlayer::Play & Stop 函数

顾名思义。

HRESULT DShowPlayer::Play()
{
    if (m_state != STATE_PAUSED && m_state != STATE_STOPPED)
        return VFW_E_WRONG_STATE;

    HRESULT hr = m_pControl->Run();
    if (SUCCEEDED(hr))
        m_state = STATE_RUNNING;

    return hr;
}

HRESULT DShowPlayer::Stop()
{
    if (m_state != STATE_RUNNING && m_state != STATE_PAUSED)
        return VFW_E_WRONG_STATE;

    HRESULT hr = m_pControl->Stop();
    if (SUCCEEDED(hr))
        m_state = STATE_STOPPED;

    return hr;
}

DShowPlayer::SetPosition 函数

即 seek 功能。

HRESULT DShowPlayer::SetPosition(REFERENCE_TIME pos)
{
    if (m_pControl == NULL || m_pSeek == NULL)
        return E_UNEXPECTED;

    HRESULT hr = S_OK;

    hr = m_pSeek->SetPositions(&pos, AM_SEEKING_AbsolutePositioning,
        NULL, AM_SEEKING_NoPositioning);

    if (SUCCEEDED(hr)) {
        // If playback is stopped, we need to put the graph into the paused
        // state to update the video renderer with the new frame, and then stop 
        // the graph again. The IMediaControl::StopWhenReady does this.
        if (m_state == STATE_STOPPED)
            hr = m_pControl->StopWhenReady();
    }

    return hr;
}

DShow 播放视频的 Filter Graph

以下是播放一个 WMV 文件生成的 Filter Graph,包含五个模块:Source Filter, Audio Decoder, Audio Render, Video Decoder 和 Video Render 。
在这里插入图片描述

其他框架下的播放

请参考对应的文章。

Blueware
EOF

/---------------------------------------------------------------------\ * 书 名:《DirectShow开发指南》 * 作 者: 陆其明(著) 金邦飞(审校) * 内容提要: 本书以DirectX SDK 9.0版为蓝本,涉及的内容几乎涵盖了在Windows平台上使 用DirectShow进行C++编码的方方面面。全书共分4个部分。第1部分详细介绍了 DirectShow的基础知识。第2部分重点讨论了Filter的开发,以及DirectShow 应用程序的开发,包括目前非常流行的如音视频采集、数码摄像机的支持、非线性 编辑等应用。第3部深入分析了DirectShow SDK提供的部分典型源代码例子。第 4部分结合作者个人的一些开发实践,通过案例和开放源码分析,进一步介绍 DirectShow的实务应用。 本书完全忠实于DirectX SDK 9.0的帮助文档以及基类源代码,并结合作者多年 的实践,经过提炼而成。内容丰富,条理清晰,实用性强。适合广大的流媒体应用 开发人员,以及对Windows平台上多媒体处理感兴趣的编程爱好者、学生学习和参 考。 * 下载内容说明: readme.txt:本说明文件。 Chapter04:第4章用到的代码,其中AppIPTransform为MFC Filter的例子, DsDemo是一些演示代码,FilterTitleOverlay是字符叠加Filter的源代码。 Chapter05:第5章用到的代码,其中GraphBuilding为Filter Graph构建技术 的一些代码,SimplePlayer为一个简单的播放器例子。 Chapter07:第7章用到的代码,其中DsDemo是一些演示代码。 Chapter09:第9章用到的代码,其中DESCallback演示了控制DirectShow智能 连接的方法。 Chapter18:第18章用到的代码,其中MpegNetwork为“MPEG流的网络客户端播放” 实现的所有源代码(请打开Daisy.dsw浏览各项目)。 Chapter19:第19章用到的代码,其中DVD2AVI_1.77.3_SRC.zip为开放源码, FilterMpeg2VD为此开放源码基础上开发的MPEG-2 Video Decoder Filter例子。 除上述代码外,本书各章用到的其他代码均在DirectX SDK安装目录的Samples 子目录下可以找到。 * 备注: DirectX SDK 9.0以及DirectX 9.0运行时库可以到微软的网站上下载。 请访问微软的网站首页http://www.microsoft.com,然后输入DirectX进行搜 索;或者直接访问http://www.microsoft.com/directx下载运行时库,或者 http://www.microsoft.com/downloads/details.aspx?FamilyId=9216652F-51E0-402E-B7B5-FEB68D00F298&displaylang=en * 技术支持网站:http://hqtech.nease.net \---------------------------------------------------------------------/
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值