今天我们重点介绍一下Filte链的自动建立过程,前面在介绍RenderFile函数的时候大概讲解了一下,但是感觉不够详细,所以这里准备单独拿出来讲解一下。
我们先了看一下整个链建立过程中需要调用的函数:
- 需要先根据文件创建源Filter对象。
- 调用IGraphBuilder2扩展接口的方法:STDMETHODIMP ConnectFilter(IBaseFilter* pBF, IPin* pPinIn);
- 调用IGraphBuilder接口的方法: STDMETHODIMP Connect(IPin* pPinOut, IPin* pPinIn);
- 调用IGraphBuilder2接口的方法:STDMETHODIMP ConnectFilterDirect(IPin* pPinOut, IBaseFilter* pBF, const AM_MEDIA_TYPE* pmt);
- 调用IFilterGraph接口的方法:STDMETHODIMP ConnectDirect(IPin* pPinOut, IPin* pPinIn, const AM_MEDIA_TYPE* pmt);
- 如果ConnectFilterDirect方法调用成功,则获取到下一个Filter对象,调用第二步中的ConnectFilter继续连接过程。
接下来我们详细了解一下每一个函数。先来看看ConnectFilter函数:
STDMETHODIMP CFGManager::ConnectFilter(IBaseFilter* pBF, IPin* pPinIn)
{
CAutoLock cAutoLock(this);
CheckPointer(pBF, E_POINTER);
if (pPinIn && S_OK != IsPinDirection(pPinIn, PINDIR_INPUT)) {
return VFW_E_INVALID_DIRECTION;
}
int nTotal = 0, nRendered = 0;
const CAppSettings& s = AfxGetAppSettings();
BeginEnumPins(pBF, pEP, pPin) {
if (S_OK == IsPinDirection(pPin, PINDIR_OUTPUT)
&& S_OK != IsPinConnected(pPin)
&& !((s.iDSVideoRendererType != VIDRNDT_DS_EVR_CUSTOM && s.iDSVideoRendererType != VIDRNDT_DS_EVR && s.iDSVideoRendererType != VIDRNDT_DS_SYNC) && GetPinName(pPin)[0] == '~')) {
CLSID clsid;
pBF->GetClassID(&clsid);
// Disable DVD subtitle mixing in EVR (CP) and Sync Renderer for Microsoft DTV-DVD Video Decoder, it corrupts DVD playback.
if (clsid == CLSID_CMPEG2VidDecoderDS) {
if (s.iDSVideoRendererType == VIDRNDT_DS_EVR_CUSTOM || s.iDSVideoRendererType == VIDRNDT_DS_SYNC) {
if (GetPinName(pPin)[0] == '~') {
continue;
}
}
}
// No multiple pin for Internal MPEG2 Software Decoder, NVIDIA PureVideo Decoder, Sonic Cinemaster VideoDecoder
else if (clsid == CLSID_CMpeg2DecFilter
|| clsid == CLSID_NvidiaVideoDecoder
|| clsid == CLSID_SonicCinemasterVideoDecoder) {
if (GetPinName(pPin)[0] == '~') {
continue;
}
//TODO: enable multiple pins for the renderer, if the video decoder supports DXVA
}
m_streampath.Append(pBF, pPin);
HRESULT hr = Connect(pPin, pPinIn);
if (SUCCEEDED(hr)) {
for (ptrdiff_t i = m_deadends.GetCount() - 1; i >= 0; i--) {
if (m_deadends[i]->Compare(m_streampath)) {
m_deadends.RemoveAt(i);
}
}
nRendered++;
}
nTotal++;
m_streampath.RemoveTail();
if (SUCCEEDED(hr) && pPinIn) {
return S_OK;
}
}
}
EndEnumPins;
return
nRendered == nTotal ? (nRendered > 0 ? S_OK : S_FALSE) :
nRendered > 0 ? VFW_S_PARTIAL_RENDER :
VFW_E_CANNOT_RENDER;
}
这个函数的主要工作就2个:
- 遍历传进来的Filter的输出Pin.
- 根据每个输出Pin调用STDMETHODIMP Connect(IPin* pPinOut, IPin* pPinIn)方法向下连接。
按CFGManager的习惯,本来Connect这个函数应该转接到DirectShow 实现的Filter Graph Manager对象上,但是MPC-HC没有这样干,而是对这个函数重新实现了一遍。具体情况看源码:
HRESULT CFGManager::Connect(IPin* pPinOut, IPin* pPinIn, bool bContinueRender)
{
CAutoLock cAutoLock(this);
CheckPointer(pPinOut, E_POINTER);
HRESULT hr;
if (S_OK != IsPinDirection(pPinOut, PINDIR_OUTPUT)
|| pPinIn && S_OK != IsPinDirection(pPinIn, PINDIR_INPUT)) {
return VFW_E_INVALID_DIRECTION;
}
if (S_OK == IsPinConnected(pPinOut)
|| pPinIn && S_OK == IsPinConnected(pPinIn)) {
return VFW_E_ALREADY_CONNECTED;
}
bool fDeadEnd = true;
if (pPinIn) {
// 1. Try a direct connection between the filters, with no intermediate filters
if (SUCCEEDED(hr = ConnectDirect(pPinOut, pPinIn, nullptr))) {
return hr;
}
} else {
// 1. Use IStreamBuilder
if (CComQIPtr<IStreamBuilder> pSB = pPinOut) {
if (SUCCEEDED(hr = pSB->Render(pPinOut, this))) {
return hr;
}
pSB->Backout(pPinOut, this);
}
}
// 2. Try cached filters
if (CComQIPtr<IGraphConfig> pGC = (IGraphBuilder2*)this) {
BeginEnumCachedFilters(pGC, pEF, pBF) {
if (pPinIn && GetFilterFromPin(pPinIn) == pBF) {
continue;
}
hr = pGC->RemoveFilterFromCache(pBF);
// does RemoveFilterFromCache call AddFilter like AddFilterToCache calls RemoveFilter ?
if (SUCCEEDED(hr = ConnectFilterDirect(pPinOut, pBF, nullptr))) {
if (!IsStreamEnd(pBF)) {
fDeadEnd = false;
}
if (SUCCEEDED(hr = ConnectFilter(pBF, pPinIn))) {
return hr;
}
}
hr = pGC->AddFilterToCache(pBF);
}
EndEnumCachedFilters;
}
// 3. Try filters in the graph
{
CInterfaceList<IBaseFilter> pBFs;
BeginEnumFilters(this, pEF, pBF) {
if (pPinIn && GetFilterFromPin(pPinIn) == pBF
|| GetFilterFromPin(pPinOut) == pBF) {
continue;
}
// HACK: ffdshow - audio capture filter
if (GetCLSID(pPinOut) == GUIDFromCString(_T("{04FE9017-F873-410E-871E-AB91661A4EF7}"))
&& GetCLSID(pBF) == GUIDFromCString(_T("{E30629D2-27E5-11CE-875D-00608CB78066}"))) {
continue;
}
pBFs.AddTail(pBF);
}
EndEnumFilters;
POSITION pos = pBFs.GetHeadPosition();
while (pos) {
IBaseFilter* pBF = pBFs.GetNext(pos);
if (SUCCEEDED(hr = ConnectFilterDirect(pPinOut, pBF, nullptr))) {
if (!IsStreamEnd(pBF)) {
fDeadEnd = false;
}
if (SUCCEEDED(hr = ConnectFilter(pBF, pPinIn))) {
return hr;
}
}
EXECUTE_ASSERT(SUCCEEDED(Disconnect(pPinOut)));
}
}
// 4. Look up filters in the registry
{
CFGFilterList fl;
CAtlArray<GUID> types;
ExtractMediaTypes(pPinOut, types);
POSITION pos = m_transform.GetHeadPosition();
while (pos) {
CFGFilter* pFGF = m_transform.GetNext(pos);
if (pFGF->GetMerit() < MERIT64_DO_USE || pFGF->CheckTypes(types, false)) {
fl.Insert(pFGF, 0, pFGF->CheckTypes(types, true), false);
}
}
pos = m_override.GetHeadPosition();
while (pos) {
CFGFilter* pFGF = m_override.GetNext(pos);
if (pFGF->GetMerit() < MERIT64_DO_USE || pFGF->CheckTypes(types, false)) {
fl.Insert(pFGF, 0, pFGF->CheckTypes(types, true), false);
}
}
CComPtr<IEnumMoniker> pEM;
if (!types.IsEmpty()
&& SUCCEEDED(m_pFM->EnumMatchingFilters(
&pEM, 0, FALSE, MERIT_DO_NOT_USE + 1,
TRUE, (DWORD)types.GetCount() / 2, types.GetData(), nullptr, nullptr, FALSE,
!!pPinIn, 0, nullptr, nullptr, nullptr))) {
for (CComPtr<IMoniker> pMoniker; S_OK == pEM->Next(1, &pMoniker, nullptr); pMoniker = nullptr) {
CFGFilterRegistry* pFGF = DEBUG_NEW CFGFilterRegistry(pMoniker);
fl.Insert(pFGF, 0, pFGF->CheckTypes(types, true));
}
}
// let's check whether the madVR allocator presenter is in our list
// it should be if madVR is selected as the video renderer
CFGFilter* pMadVRAllocatorPresenter = nullptr;
pos = fl.GetHeadPosition();
while (pos) {
CFGFilter* pFGF = fl.GetNext(pos);
if (pFGF->GetCLSID() == CLSID_madVRAllocatorPresenter) {
// found it!
pMadVRAllocatorPresenter = pFGF;
break;
}
}
pos = fl.GetHeadPosition();
while (pos) {
CFGFilter* pFGF = fl.GetNext(pos);
// Checks if madVR is already in the graph to avoid two instances at the same time
CComPtr<IBaseFilter> pBFmadVR;
FindFilterByName(_T("madVR Renderer"), &pBFmadVR);
if (pBFmadVR && (pFGF->GetName() == _T("madVR Renderer"))) {
continue;
}
if (pMadVRAllocatorPresenter && (pFGF->GetCLSID() == CLSID_madVR)) {
// the pure madVR filter was selected (without the allocator presenter)
// subtitles, OSD etc don't work correctly without the allocator presenter
// so we prefer the allocator presenter over the pure filter
pFGF = pMadVRAllocatorPresenter;
}
TRACE(_T("FGM: Connecting '%s'\n"), pFGF->GetName().GetString());
CComPtr<IBaseFilter> pBF;
CInterfaceList<IUnknown, &IID_IUnknown> pUnks;
if (FAILED(pFGF->Create(&pBF, pUnks))) {
TRACE(_T(" --> Filter creation failed\n"));
continue;
}
if (FAILED(hr = AddFilter(pBF, pFGF->GetName()))) {
TRACE(_T(" --> Adding the filter failed\n"));
pUnks.RemoveAll();
pBF.Release();
continue;
}
hr = ConnectFilterDirect(pPinOut, pBF, nullptr);
/*
if (FAILED(hr))
{
if (types.GetCount() >= 2 && types[0] == MEDIATYPE_Stream && types[1] != GUID_NULL)
{
CMediaType mt;
mt.majortype = types[0];
mt.subtype = types[1];
mt.formattype = FORMAT_None;
if (FAILED(hr)) hr = ConnectFilterDirect(pPinOut, pBF, &mt);
mt.formattype = GUID_NULL;
if (FAILED(hr)) hr = ConnectFilterDirect(pPinOut, pBF, &mt);
}
}
*/
if (SUCCEEDED(hr)) {
if (!IsStreamEnd(pBF)) {
fDeadEnd = false;
}
if (bContinueRender) {
hr = ConnectFilter(pBF, pPinIn);
}
if (SUCCEEDED(hr)) {
m_pUnks.AddTailList(&pUnks);
// maybe the application should do this...
POSITION posInterface = pUnks.GetHeadPosition();
while (posInterface) {
if (CComQIPtr<IMixerPinConfig, &IID_IMixerPinConfig> pMPC = pUnks.GetNext(posInterface)) {
pMPC->SetAspectRatioMode(AM_ARMODE_STRETCHED);
}
}
if (CComQIPtr<IVMRAspectRatioControl> pARC = pBF) {
pARC->SetAspectRatioMode(VMR_ARMODE_NONE);
}
if (CComQIPtr<IVMRAspectRatioControl9> pARC = pBF) {
pARC->SetAspectRatioMode(VMR_ARMODE_NONE);
}
if (CComQIPtr<IVMRMixerControl9> pMC = pBF) {
m_pUnks.AddTail(pMC);
}
if (CComQIPtr<IVMRMixerBitmap9> pMB = pBF) {
m_pUnks.AddTail(pMB);
}
if (CComQIPtr<IMFGetService, &__uuidof(IMFGetService)> pMFGS = pBF) {
CComPtr<IMFVideoDisplayControl> pMFVDC;
CComPtr<IMFVideoMixerBitmap> pMFMB;
CComPtr<IMFVideoProcessor> pMFVP;
if (SUCCEEDED(pMFGS->GetService(MR_VIDEO_RENDER_SERVICE, IID_PPV_ARGS(&pMFVDC)))) {
m_pUnks.AddTail(pMFVDC);
}
if (SUCCEEDED(pMFGS->GetService(MR_VIDEO_MIXER_SERVICE, IID_PPV_ARGS(&pMFMB)))) {
m_pUnks.AddTail(pMFMB);
}
if (SUCCEEDED(pMFGS->GetService(MR_VIDEO_MIXER_SERVICE, IID_PPV_ARGS(&pMFVP)))) {
m_pUnks.AddTail(pMFVP);
}
//CComPtr<IMFWorkQueueServices> pMFWQS;
//pMFGS->GetService (MF_WORKQUEUE_SERVICES, IID_PPV_ARGS(&pMFWQS));
//pMFWQS->BeginRegisterPlatformWorkQueueWithMMCSS(
if (pMadVRAllocatorPresenter) {
// Hook DXVA to have status and logging.
CComPtr<IDirectXVideoDecoderService> pDecoderService;
CComPtr<IDirect3DDeviceManager9> pDeviceManager;
HANDLE hDevice = INVALID_HANDLE_VALUE;
if (SUCCEEDED(pMFGS->GetService(MR_VIDEO_ACCELERATION_SERVICE, IID_PPV_ARGS(&pDeviceManager)))
&& SUCCEEDED(pDeviceManager->OpenDeviceHandle(&hDevice))
&& SUCCEEDED(pDeviceManager->GetVideoService(hDevice, IID_PPV_ARGS(&pDecoderService)))) {
HookDirectXVideoDecoderService(pDecoderService);
pDeviceManager->CloseDeviceHandle(hDevice);
}
pDeviceManager.Release();
pDecoderService.Release();
}
}
return hr;
}
}
EXECUTE_ASSERT(SUCCEEDED(RemoveFilter(pBF)));
TRACE(_T(" --> Failed to connect\n"));
pUnks.RemoveAll();
pBF.Release();
}
}
if (fDeadEnd) {
CAutoPtr<CStreamDeadEnd> psde(DEBUG_NEW CStreamDeadEnd());
psde->AddTailList(&m_streampath);
int skip = 0;
BeginEnumMediaTypes(pPinOut, pEM, pmt) {
if (pmt->majortype == MEDIATYPE_Stream && pmt->subtype == MEDIASUBTYPE_NULL) {
skip++;
}
psde->mts.AddTail(CMediaType(*pmt));
}
EndEnumMediaTypes(pmt);
if (skip < (int)psde->mts.GetCount()) {
m_deadends.Add(psde);
}
}
return pPinIn ? VFW_E_CANNOT_CONNECT : VFW_E_CANNOT_RENDER;
}
这个函数代码有点长,但是重要执行代码就几行,截图如下:
这里就不详细介绍了,截图已经解释的很清楚了。从截图可知,连接的过程主要是调用了ConnectFilterDirect这个函数,下面我们看一下这个函数源码:
STDMETHODIMP CFGManager::ConnectFilterDirect(IPin* pPinOut, IBaseFilter* pBF, const AM_MEDIA_TYPE* pmt)
{
CAutoLock cAutoLock(this);
CheckPointer(pPinOut, E_POINTER);
CheckPointer(pBF, E_POINTER);
if (S_OK != IsPinDirection(pPinOut, PINDIR_OUTPUT)) {
return VFW_E_INVALID_DIRECTION;
}
const CAppSettings& s = AfxGetAppSettings();
BeginEnumPins(pBF, pEP, pPin) {
if (S_OK == IsPinDirection(pPin, PINDIR_INPUT)
&& S_OK != IsPinConnected(pPin)
&& !((s.iDSVideoRendererType != VIDRNDT_DS_EVR_CUSTOM && s.iDSVideoRendererType != VIDRNDT_DS_EVR && s.iDSVideoRendererType != VIDRNDT_DS_SYNC) && GetPinName(pPin)[0] == '~')) {
HRESULT hr = ConnectDirect(pPinOut, pPin, pmt);
if (SUCCEEDED(hr)) {
return hr;
}
}
}
EndEnumPins;
return VFW_E_CANNOT_CONNECT;
}
从源代码可知,这个函数也就干了2件事情:
- 遍历传入的Filter的输入Pin.
- 调用Conectdirect连接上一个Filter的输出Pin和步骤1 中遍历出的输入Pin.
所以这个函数将Pin和Filter的连接最终转成了PIN和PIN之间的连接。我们再来看看最终连接函数Conectdirect的源码:
STDMETHODIMP CFGManager::ConnectDirect(IPin* pPinOut, IPin* pPinIn, const AM_MEDIA_TYPE* pmt)
{
if (!m_pUnkInner) {
return E_UNEXPECTED;
}
CAutoLock cAutoLock(this);
CComPtr<IBaseFilter> pBF = GetFilterFromPin(pPinIn);
CLSID clsid = GetCLSID(pBF);
// TODO: GetUpStreamFilter goes up on the first input pin only
for (CComPtr<IBaseFilter> pBFUS = GetFilterFromPin(pPinOut); pBFUS; pBFUS = GetUpStreamFilter(pBFUS)) {
if (pBFUS == pBF) {
return VFW_E_CIRCULAR_GRAPH;
}
if (clsid != CLSID_Proxy && GetCLSID(pBFUS) == clsid) {
return VFW_E_CANNOT_CONNECT;
}
}
return CComQIPtr<IFilterGraph2>(m_pUnkInner)->ConnectDirect(pPinOut, pPinIn, pmt);
}
从源码可知,最终PIN和PIN之间的连接还是转接到DirectShow的Filter Graph Manager上了。
我们再回到Connect函数当中,看一下调用ConnectFilterDirect函数连接下一个Filter成功以后如何建立下一个Filter的连接。源码截图如下:
截图可知,如果上一个Filter连接成功,则继续往下连接下一个Filter。
总结:以上就是对Filter Graph Manager中Filter链自动建立过程的详细介绍。