视频播放 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 播放视频
MSDN 上有基于本文所用代码的类似文章,全英文的,有兴趣的请走 这边。
播放流程
播放代码
以下是整个 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 。
其他框架下的播放
请参考对应的文章。
– EOF –