音频播放 via DirectShow

2019-04-15 14:49发布

音频播放 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