音频播放 via DirectShow

DirectShow 简介

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

DirectShow 播放音频

播放流程

dshow audio play

播放代码

以下是整个 DirectShow 播放过程的概要代码,略去错误处理:

hr = CoInitialize(NULL);
CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void**)&m_pGraph);

hr = m_pGraph->QueryInterface(IID_IMediaControl, (void**)&m_pControl);
hr = m_pGraph->QueryInterface(IID_IBasicAudio, (void**)&m_pAudio);
hr = m_pGraph->AddSourceFilter(szFilePath, NULL, &pSource);
hr = AddFilterByCLSID(m_pGraph, CLSID_DSoundRender, &pAudioRenderer, L"Audio Renderer");

// render the source filter to audio renderer   
pSource->EnumPins(&pEnum);
while (S_OK == pEnum->Next(1, &pPin, NULL)) {
    // Try to render this pin. It's OK if we fail some pins, if at least one pin renders.
    hr = pGraph2->RenderEx(pPin, AM_RENDEREX_RENDERTOEXISTINGRENDERERS, NULL);
    pPin->Release();
    if (SUCCEEDED(hr))
        bRenderedAnyPin = TRUE;
}

hr = m_pAudio->put_Volume(m_lVolume);
hr = m_pControl->Run();

// playing ...

hr = m_pControl->Stop();
CoUninitialize();

DSAudioPlayer::openFile 函数

这里完成了整个 filter graph 的创建和连接工作,并启动一个线程进行播放。

HRESULT openFile(LPCTSTR szFilePath, PF_ON_COMPLETE pfOnComplete)
{
    HRESULT hr = S_OK;
    CComPtr<IBaseFilter> pSource = NULL;
    
    // Create a new filter graph. (This also closes the old one, if any.)
    hr = _initGraph();
    RETURN_IF_FAILED(hr);
    
    hr = m_pGraph->AddSourceFilter(szFilePath, NULL, &pSource);
    RETURN_IF_FAILED(hr);
    
    hr = _renderStreams(pSource);
    RETURN_IF_FAILED(hr);
    
    hr = _updateVolume();
    RETURN_IF_FAILED(hr);
    
    m_pOnCompleteCallback = pfOnComplete;
    
    DWORD threadID = 0;
    HANDLE hThread = CreateThread(NULL, 0, _handleEvent, this, 0, &threadID);
    RETURN_IF_NULL(hThread);
    
    m_hThread = hThread;
    m_state = STATE_STOPPED;
    return hr;
}
DSAudioPlayer::_initGraph 函数

创建 filter graph 并获取相关的接口:

  • IMediaControl:通过 filter graph 控制数据流,包括运行,暂停和停止 filter graph 的方法
  • IBasicAudio:允许访问音量和平衡(balance)功能
  • IMediaEventEx:支持来自 filter graph 和 filters 的事件通知到应用程序,允许注册窗口以接收消息。
HRESULT _initGraph()
{
    HRESULT hr = S_OK;
    _tearDownGraph();

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

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

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

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

    return hr;
}
DSAudioPlayer::_renderStreams 函数

遍历 source filter 的每一个 pin,尝试找到一个到 audio renderer 的成功连接,这个过程是 DShow 自动完成的,在 source 和 renderer 中间可能会添加一些 filter 如 demuxer、decoder 和 resampler 等等。

HRESULT _renderStreams(IBaseFilter *pSource)
{
    HRESULT hr = S_OK;
    BOOL bRenderedAnyPin = FALSE;
    CComPtr<IFilterGraph2> pGraph2 = NULL;
    CComPtr<IEnumPins> pEnum = NULL;
    CComPtr<IBaseFilter> pAudioRenderer = NULL;
    
    hr = m_pGraph->QueryInterface(IID_IFilterGraph2, (void**)&pGraph2);
    RETURN_IF_FAILED(hr);
    
    hr = AddFilterByCLSID(m_pGraph, CLSID_DSoundRender, &pAudioRenderer, _T("Audio Renderer"));
    RETURN_IF_FAILED(hr);
    
    hr = pSource->EnumPins(&pEnum);
    RETURN_IF_FAILED(hr);
    
    IPin *pPin = NULL;
    while (S_OK == pEnum->Next(1, &pPin, NULL)) {
        // Try to render this pin. It's OK if we fail some pins, if at least one pin renders.
        hr = pGraph2->RenderEx(pPin, AM_RENDEREX_RENDERTOEXISTINGRENDERERS, NULL);
        pPin->Release();
        if (SUCCEEDED(hr))
            bRenderedAnyPin = TRUE;
    }
    
    RETURN_IF_FALSE_EX(bRenderedAnyPin, VFW_E_CANNOT_RENDER);
    return S_OK;
}
DSAudioPlayer::_updateVolume 函数

Very simple,在播放过程中也可以调节。

HRESULT _updateVolume()
{
    HRESULT hr = E_FAIL;
    if (m_bMute) // If the audio is muted, set the minimum volume. 
        hr = m_pAudio->put_Volume(MIN_VOLUME);
    else // Restore previous volume setting
        hr = m_pAudio->put_Volume(m_lVolume);
    RETURN_IF_FAILED(hr);

    return hr;
}

DSAudioPlayer::play 函数

Just run!

HRESULT play()
{
    RETURN_IF_FALSE_EX((m_state == STATE_PAUSED) || (m_state == STATE_STOPPED), VFW_E_WRONG_STATE);
    RETURN_IF_NULL(m_pGraph);

    HRESULT hr = m_pControl->Run();
    RETURN_IF_FAILED(hr);

    m_state = STATE_RUNNING;
    return hr;
}

handleDShowEvent 函数

其实 DShow 定义了很多 event,下面只处理了 EC_COMPLETE。常用的还有 EC_ERRORABORT:发生了严重错误,将强制终止。

typedef void (*PF_ON_COMPLETE)();

static HRESULT handleDShowEvent(IMediaEventEx* pEvent, PF_ON_COMPLETE pfOnCompleteCallback)
{
    RETURN_IF_NULL(pEvent);
    HANDLE hEvent = NULL;
    HRESULT hr = pEvent->GetEventHandle((OAEVENT*)&hEvent);
    RETURN_IF_FAILED(hr);
    
    bool bDone = false;
    while (!bDone) {
        DWORD ret = WaitForSingleObject(hEvent, INFINITE);
        RETURN_IF_FALSE(WAIT_OBJECT_0 == ret);
        
        long evCode = 0, param1 = 0, param2 = 0;
        while (SUCCEEDED(pEvent->GetEvent(&evCode, &param1, &param2, 0))) {
            hr = pEvent->FreeEventParams(evCode, param1, param2);
            if (EC_COMPLETE == evCode) {
                bDone = true;
                if (NULL != pfOnCompleteCallback)
                    pfOnCompleteCallback();
            }
        }
    }
    
    return S_OK;
}

DShow 播放音频的 Filter Graph

以下是播放一首 MP3 生成的 Filter Graph,包含四个模块:Source Filter, Splitter, Decoder 和 Render。
audio play graph

其他框架下的播放

请参考对应的文章。

Blueware
EOF

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值