音频播放 via DirectShow
音频播放 via DirectShow
这里所说的播放是指通过读取声音文件数据然后经过解码输出到扬声器进行播放的过程。
Windows 上有如下几种常见的实现方式:
- Waveform API
- FFmpeg
- DirectShow
- Media Foundation I
- Media Foundation II
DirectShow 简介
DirectShow(有时缩写为 DS 或 DShow),开发代号 Quartz,是微软在 ActiveMovie 和 Video for Windows 的基础上推出的新一代基于 COM 的流媒体处理的开发包,与 DirectX 开发包一起发布。DShow 使用一种叫 Filter Graph 的模型来管理整个数据流的处理过程,有了 DShow,我们可以很方便地从支持 WDM 驱动模型的采集卡上捕获数据,并且进行相应的后期处理乃至存储到文件中。这样使在多媒体数据库管理系统(MDBMS)中多媒体数据的存取变得更加方便。它广泛地支持各种媒体格式,包括 asf、mpeg、avi、dv、mp3、wav 等,为多媒体流的捕捉和回放提供了强有力的支持。
DirectShow 播放音频
播放流程
播放代码
以下是整个 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, ¶m1, ¶m2, 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。
– EOF –