Recently it was the time to sort out an issue with a video DMO, which outputs YV12 video and in the same time is capable of supporting extended video strides in order to efficiently make a direct connection to Video Mixing Renderer Filters.
From past experience, I already knew that some bugs are definitely involved but their effect was yet unexplored. For a testbed application, I took good old FrameRateSample02 application, which generates multiple video feeds and routes it to video renderers:
With new source video choices the application is capable of constructing filter graphs that use a private DMO (that is hosted inside the executable) wrapped with DMO Wrapper Filter, with a graph topology shown below:
The DMO does not do any processing, except support for extended strides while copying original data into buffer streamed to video renderer. Extended strides for a planar YUV format is a bit tricky. Are they supported at all? Yes, they are and video renderer might still request an extended stride and in which case origial MxN video needs to be copied into (M+P)xN buffer. Unlike packed pixel format, unused space is distributed in a bit different way but still very much similarly: output buffer is still a regular (M+P)xN rectangle, where the effective picture of size MxN should be copied into top left corner.
The problem I was aware of from past experience showed up immediately once the filters connected. After a dynamic format change initiated by the VMR, the filters report different connection media types. DMO Wrapper Filter still shows original connection time connection media type, while Video Mixing Renderer Filter shows media type it switched to.
This looks like DMO Wrapper Filter bug, but was yet to check if it still handles the new media type properly and passes the format change to the underlying DMO object. Taking into consideration the extent DMOs are used by Microsoft, it had to, thought I never met this in the MSDN documentation.
Further debugging revealed that DMO Wrapper Filter does handle the media type change and forward it to the DMO through IMediaObject::SetOutputType method. This happens when DMO already received its first input, and immediately before following IMediaObject::ProcessOutput method call:
rodmo.h(293) : CMediaObjectT<class CVideoDmo>::AllocateStreamingResources: ...
rodshow.h(2716) : CBaseFilterT<class CMainDialog::CSourceFilter>::Pause: ...
rodshow.h(2722) : CBaseFilterT<class CMainDialog::CSourceFilter>::Pause: m_State 0
rodshow.h(2730) : CBaseFilterT<class CMainDialog::CSourceFilter>::Pause: m_State 1
rodshow.h(2784) : CBaseFilterT<class CMainDialog::CSourceFilter>::GetState: nTimeout 0
rodmo.h(679) : CSimpleMediaObjectT<class CVideoDmo,1>::GetInputStatus: nInputStreamIndex 0
rodmo.h(702) : CSimpleMediaObjectT<class CVideoDmo,1>::ProcessInput: nInputStreamIndex 0, nFlags 0x1
rodmo.h(527) : CSimpleMediaObjectT<class CVideoDmo,1>::SetOutputType: nOutputStreamIndex 0, nFlags 0x0
rodmo.h(727) : CSimpleMediaObjectT<class CVideoDmo,1>::ProcessOutput: nFlags 0x1, nOutputBufferCount 1
So, the media type change is handled and the problem is only limited to reporting wrong (original) media type off the wrapper filter output pin.
Investigation of filter connection however brought up another interesting fact, memory allocator properties:
As the filters switched to a new media type, with extended strides, the memory allocator should obviously have been updated accordingly, in order to support larger buffer. However current memory allocator properties indicate old buffer size of 460,800 bytes, while new media type assumes 737,280 buffer size at the very least. The samples are however streamed fine, so the issue is also limited to incorrectly reported size.
This memory allocator is managed by the Video Mixing Renderer Filter, so the problem seems to be relating to it.
Good news however was that video was streamed and displayed correctly.
One thing that deserves special attention is making a DMO private. I already wrote another post on private DMOs some time ago, and this time the sample shows even a better way in case one does not need the DMO available for Intelligent Connect (in which case DMORegister call along with administrative privileges will be required), just for a private instantiation. DMO is typically created by CoCreateInstance from inside the DMO Wrapper Filter, e.g. using IDMOWrapperFilter::Init method, so it is necessary to either register COM class, or… just register its class object for the process using CoRegisterClassObject.
There is no need for direct usage of API since ATL already wrapped the calls and we are to take advantage of what is already available. First of all, the DMO class requires certain CLSID, because we don’t have one typically declared in IDL. A good place for attaching CLSID is class declaration and __declspec keyword:
class __declspec(uuid("88B976BE-EEE6-45b1-A21B-42A941DF8819")) ATL_NO_VTABLE CVideoDmo : public CComObjectRootEx<CComMultiThreadModelNoCS>, public CComCoClass<CVideoDmo>, public CSimpleMediaObjectT<CVideoDmo, 1> {
Further a COM class is typically referenced into module’s object map byan OBJECT_ENTRY_AUTO macro:
//OBJECT_ENTRY_AUTO(__uuidof(VideoDmo), CVideoDmo)
But we don’t need this this time as we don’t need standard COM registration for the class. We will register its class object manually and temporarily, at the time of DMO initialization:
CComQIPtr<IBaseFilter> pBaseFilter; CVideoDmo* pVideoDmo; { BEGIN_OBJECT_MAP(g_pObjectMap) OBJECT_ENTRY(__uuidof(CVideoDmo), CVideoDmo) END_OBJECT_MAP() ATLASSERT(DIM(g_pObjectMap) == 2 && !g_pObjectMap[1].pclsid); ATLENSURE_SUCCEEDED(g_pObjectMap[0].RegisterClassObject(CLSCTX_INPROC_SERVER, REGCLS_SINGLEUSE)); _ATLTRY { CComPtr<IDMOWrapperFilter> pDmoWrapperFilter = ConstructDmoWrappedFilter(__uuidof(CVideoDmo), CVideoDmo::GetCategory()); pBaseFilter = pDmoWrapperFilter; ATLENSURE_THROW(pBaseFilter, E_NOINTERFACE); CComQIPtr<IMediaObject> pMediaObject = pDmoWrapperFilter; ATLENSURE_THROW(pMediaObject, E_NOINTERFACE); pVideoDmo = static_cast<CVideoDmo*>((IMediaObject*) pMediaObject); } _ATLCATCHALL() { ATLVERIFY(SUCCEEDED(g_pObjectMap[0].RevokeClassObject())); _ATLRETHROW; } ATLVERIFY(SUCCEEDED(g_pObjectMap[0].RevokeClassObject())); } pVideoDmo->Initialize(CDmoMediaType(m_pMediaType), CopyYv12MediaBuffer); ATLENSURE_SUCCEEDED(pGraphBuilder->AddFilter(pBaseFilter, CStringW(_T("Pass-Through DMO")))); ATLENSURE_SUCCEEDED(pGraphBuilder->Connect(pOutputPin, _FilterGraphHelper::GetFilterPin(pBaseFilter, PINDIR_INPUT))); pOutputPin = _FilterGraphHelper::GetFilterPin(pBaseFilter, PINDIR_OUTPUT)
What is being done in the code snippet above is:
- local object map with a single OBJECT_ENTRY item to have ATL manage class object registration/revokation for us
- registration of a private class/CLSID
- instantiation and initialization of a DMO Wrapper Filter which is to pick up our class
- cast back to native C++ pointer for easy further access (a private interface would be more accurate here)
- revokation for CLSID
For information, the interfaces queried from the DMO:
Find all "CVideoDmo::InternalQueryInterface", Hidden, Find Results 1, Current Document CVideoDmo::InternalQueryInterface: Interface IMediaObject, Result 0x00000000 CVideoDmo::InternalQueryInterface: Interface IDMOQualityControl, Result 0x80004002 CVideoDmo::InternalQueryInterface: Interface IDMOVideoOutputOptimizations, Result 0x80004002 CVideoDmo::InternalQueryInterface: Interface IMediaObject, Result 0x00000000 CVideoDmo::InternalQueryInterface: Interface IWMGetSecureChannel, Result 0x80004002 CVideoDmo::InternalQueryInterface: Interface IAMOpenProgress, Result 0x80004002 CVideoDmo::InternalQueryInterface: Interface IAMDeviceRemoval, Result 0x80004002 CVideoDmo::InternalQueryInterface: Interface IDirectVobSub, Result 0x80004002 CVideoDmo::InternalQueryInterface: Interface IDirectVobSub, Result 0x80004002 CVideoDmo::InternalQueryInterface: Interface IWMCodecAMVideoAccelerator, Result 0x80004002 CVideoDmo::InternalQueryInterface: Interface IDirectVobSub, Result 0x80004002 CVideoDmo::InternalQueryInterface: Interface IWMCodecAMVideoAccelerator, Result 0x80004002 CVideoDmo::InternalQueryInterface: Interface IWMCodecAMVideoAccelerator, Result 0x80004002 CVideoDmo::InternalQueryInterface: Interface IMediaSeeking, Result 0x80004002 CVideoDmo::InternalQueryInterface: Interface IMediaPosition, Result 0x80004002 CVideoDmo::InternalQueryInterface: Interface IAMFilterMiscFlags, Result 0x80004002 CVideoDmo::InternalQueryInterface: Interface IVideoWindow, Result 0x80004002 CVideoDmo::InternalQueryInterface: Interface IBasicAudio, Result 0x80004002 CVideoDmo::InternalQueryInterface: Interface IAMFilterMiscFlags, Result 0x80004002 CVideoDmo::InternalQueryInterface: Interface IKsPropertySet, Result 0x80004002 CVideoDmo::InternalQueryInterface: Interface IReferenceClock, Result 0x80004002 CVideoDmo::InternalQueryInterface: Interface IWMCodecAMVideoAccelerator, Result 0x80004002 CVideoDmo::InternalQueryInterface: Interface IAMDeviceRemoval, Result 0x80004002 CVideoDmo::InternalQueryInterface: Interface IAMOpenProgress, Result 0x80004002 Matching lines: 24
A Visual C++ .NET 2008 source code is available from SVN. Additional files will be required to compile, but binary is also incuded.
Thanks for the excellent article. You mention that “the media type change is handled and the problem is only limited to reporting wrong (original) media type off the wrapper filter output pin”.
Do you have a workaround for this or it just doesn’matter for all practical purposes?
I had a look at the examples hoping to find an answer in the CSimpleMediaObjectT class, but it seems that its source code is not public (in fact, all files under Common/alax.info are missing).
As far as I remember, it works fine, just misreports the media type. That it, you don’t need a workaround to make it work.
I also run this application now once again on my current Windows 7 Ultimate system and the problem is not here – perhaps it was fixed at some point in the DMO Wrapper Filter.
Correct, this code was never published.
Hi! Thanks again for the quick chat. Please feel free to transcribe it here in case it could help someone else.
Basically The DMO Wrapper filter is not handling dynamic reconnections in wide sense. It is only capable of changing stride on video media type connection, so that it is the same media type (resolution etc) but only stride can chance to better fit Video Mixing Renderer (VMR) Filters. And it’s only VMR which wants this change because there is a hardware requirement behind that.
The DMO is typically plain and simple – you have media types set and then the processing starts. DMOs don’t cover any advanced topics like dynamic changes (except this only one with VMR).