How To: Implement DirectShow Filter using DirectX Media Object DMO (Part 3: Persistence, Automation and Property Pages)

Previously on the topic:

The principal task of video processing is done but there are still things mandatory for the filter to be usable. First of all, a custom interface is required to be able to control the filter from higher level application and to adjust brightness and constract correction values on the run time. Additionally, persistence would not hurt at all to be able to store correction settings along with other graph settings in GraphEdit graph file or anywhere else. Additionally, it would also be convenient to have a property page for the filter to be able to adjust the correction settings through GUI, both on graph composition and while the graph is running.

All the mentioned tasks are interconnected and ATL has an answer in implementation of:

Also note that DMO Minimum Requirements mention IPersistStream and ISpecifyPropertyPages as not required but recommended as useful, and these interfaces will be used by DirectShow.

We start with custom IDispatch-derived interface to be used to control filter and for persistence. ATL Project Wizard already prepared empty IBrightnessContrastObject interface as a part of project creation, where we are adding Brightness and Contrast properties (in the IDL definition) and adding implementation of corresponding methods to the filter/DMO class:

interface IBrightnessContrastObject : IDispatch
{
	[propget, id(1)] HRESULT Brightness([out, retval] LONG* pnBrightness);
	[propput, id(1)] HRESULT Brightness([in] LONG nBrightness);
	[propget, id(2)] HRESULT Contrast([out, retval] LONG* pnContrast);
	[propput, id(2)] HRESULT Contrast([in] LONG nContrast);
};
// IBrightnessContrastObject
	STDMETHOD(get_Brightness)(LONG* pnBrightness) throw()
	STDMETHOD(put_Brightness)(LONG nBrightness) throw()
	STDMETHOD(get_Contrast)(LONG* pnContrast) throw()
	STDMETHOD(put_Contrast)(LONG nContrast) throw()

To complete implemetnation of persistence we are to inherit from IPersistStreamInitImpl and also add a property map:

class ATL_NO_VTABLE CBrightnessContrastObject :
	...
	public IPersistStreamInitImpl<CBrightnessContrastObject>,
...
BEGIN_COM_MAP(CBrightnessContrastObject)
	COM_INTERFACE_ENTRY(IPersistStreamInit)
	COM_INTERFACE_ENTRY_IID(IID_IPersistStream, IPersistStreamInit)
...
BEGIN_PROP_MAP(CBrightnessContrastObject)
	PROP_ENTRY_TYPE_EX("Brightness", 1, CLSID_NULL, __uuidof(IBrightnessContrastObject), VT_I4)
	PROP_ENTRY_TYPE_EX("Contrast", 2, CLSID_NULL, __uuidof(IBrightnessContrastObject), VT_I4)
END_PROP_MAP()
...
public:
	BOOL m_bRequiresSave;

In the property map we enumerate the persistent properties using PROP_ENTRY_TYPE_EX (a successor of PROP_ENTRY_EX, which is deprecated starting Visual Studio .NET 2008).

Note that ATL implements IPersistStreamInit, however IPersistStream is compatible in method declaration so we can quick-implement this interface through declaring COM_INTERFACE_ENTRY_IID and thus castingĀ IPersistStreamInit to IPersistStream.

Note CLSID_NULL which is a class identifier of the corresponding property page, if any, which we will replace with a non-NULL identifier below as soon as property page is ready.

We also need a m_bRequiresSave variable which is used by IPersist*Impl classes and for this reason it has to be either public or instead we should friend the classes to allow access to the variable.

To ensure automation interface property put accessors are working as expected, let us re-work debug pre-initialization of the correction variables through FinalConstruct method:

#if defined(_DEBUG)
	HRESULT FinalConstruct() throw()
	{
		_ATLTRY
		{
			__C(put_Brightness(-0x0010));
			__C(put_Contrast(0x2000));
		}
		_ATLCATCH(Exception)
		{
			return Exception;
		}
		return S_OK;
	}
#endif // defined(_DEBUG)

The only things remained is a property page, for which we create a new COM object class CGeneralPropertyPage. Luckily, ATL has a wizard-based creation helper for property pages:

We will start needing WTL for convenient GUI implementation. Please refer to Example: Implementing a Property Page on how property page is implemented. Once implementation is complete, we need to inherit from IPersistStreamInitImpl and reference the property page from filter/DMO property map:

class ATL_NO_VTABLE CBrightnessContrastObject :
	...
	public ISpecifyPropertyPagesImpl<CBrightnessContrastObject>,
...
BEGIN_COM_MAP(CBrightnessContrastObject)
	...
	COM_INTERFACE_ENTRY(ISpecifyPropertyPages)
...
BEGIN_PROP_MAP(CBrightnessContrastObject)
	PROP_PAGE(CLSID_GeneralPropertyPage)

Filter’s run time:

Source code: DmoBrightnessContrastSample.03.zip (note that Release build binary is included)

Continued by:

6 Replies to “How To: Implement DirectShow Filter using DirectX Media Object DMO (Part 3: Persistence, Automation and Property Pages)”

  1. Roman,

    I am getting something that I cannot account for, and I am hoping you can help.

    I never seem to get a valid input image extent (width and height) on any of the calls from GraphEdt.exe.

    For some reason, I always seem to get 0,0 for the extents, and I notice that you seem to get around the problem by using a default of 320,240 which replaces the 0,0 in GetOutputType() and by using m_LastKnownInputExtent.

    This works fine for your filter until a video with a larger frame size is used, and then the video displayed when the graph is run is truncated. I get only the 320 by 240 upper left hand area of a video with frame size 496 x 368. I assume that because GraphEdt sees the 320 x 240 as a cropping window, it avoids reading and writing off the end of the buffer.

    Do you know how to get around this problem?

    Thanks,
    Jack

  2. Jack,

    This is not a GraphEdit issue, but the way how the filter connects to its upstream peer, on the input pin. That is it depends on what exactly kind of filter you are connecting your DMO wrapper filter with.

    m_LastKnownInputExtent looks more like a workaround but always worked for me a number of times. An alternate method instead of enumerating 320,240 by default is to provide a media type with major type and subtype initialized, but the rest of the fields empty. This may be a better way (which I never needed to even try though) and I suggest that you give it a try. And also mention what is the filter being connected to the input pin.

  3. Roman,
    My input pin is connected to the output pin (in my test) of ffdshow Video Decoder. I needed 24bpp RGB frames to encode, so I added a .avi file and GraphEdt always adds in AVI Splitter, ffdshow Video Decoder, and a video renderer. I replaced the renderer with my encoder filter and a null renderer. I would actually like to find a way to write the data to a .avi file but I do not seem to have a filter to do that so I may have to modify my filter to write the data to disk itself.

    I did try changing the code to not specify the frame size but for some reason the result kept comming back as 3840 x 720. I have no idea why as the video data I was using was usually 320 x 240 and never had a width over 500.

    Jack

  4. Roman,

    Just FYI. My problems with no image width or height (Extent in your code) disappeared when I dumped ffdshow (which had been inserted by GraphEdt) and replaced it with AVIDecoder. You were correct about the problem being a filter problem and not a GraphEdt problem.

    Thanks again.

    Jack

Leave a Reply