GDCL Home About GDCL DirectShow WinNt & WDM

 DirectShow Developer Q & A

All Tools and Samples are listed on the Downloads page.

General

What is IAsyncReader for and how can I use it? What is CPullPin?
What's the difference between stopped, paused and running states in DirectShow?
How many threads are there in a typical filter graph?
What is g_Templates and why won't my application link without it?
The DLL registers OK but the wrong module is registered for my filters.
How can I make IQueueCommand work?
Why is frame seeking so slow for Mpeg-1?
Media Player creates my filter twice
My app hangs in FilterGraph->Release() when there are several graphs

AVStream

Stream Format dialog does not work with AVSHWS sample driver: answer
Hangs in CKSQueue or CKSOutputPin during BeginFlush or graph stop: answer
Dynamic format change in AVStream driver: answer

How To...

How can I host DirectX Audio Plug-ins in my application?
How can I play back MPEG streams from live sources or the network?
How to overlay text and graphics on a running movie
How to use in-place transforms with the video renderer

Visual Basic

How can I use the File Writer filter from Visual Basic?
How can I select a reference clock from Visual Basic?
How can I show a filter's property pages from Visual Basic?
How can I set the interleaving properties of the AVI Mux filter from Visual Basic?
How can I select the left or right audio channels when playing MPEG-1 
How can I select the video capture format and capture parameters with the "Stream Format" dialog?
How can I specify a capture format from a VB program?
How can I grab still frames from a movie using Visual Basic?
Why does the DirectX Builder sample not work properly?
How can I list all the video capture filters (or any another category of filters)?
How can I remove filters from a graph in VB

 DirectShow

An overview of the DirectShow filter architecture
The filter graph manager and plug-in distributors
How the filtergraph manager builds graphs
Media types
Data transports
A technical Q & A for developers

What is IAsyncReader for and how can I use it? What is CPullPin?

Most streams in DirectShow use a requested push model. That is, you tell the upstream pin where it should start and stop, and it then delivers you that section of the stream in a sequence of samples delivered to your Receive method on its thread. This works fine for most playback scenarios, but there are some cases where it is not very efficient. One such case is the AVI file format: this is not really designed well for sequential playback, since the chunks you need are often in the wrong place, and must always be referenced via the index. To play this back efficiently, you need to be able to read more at random in the file, rather than just parsing chunks as they arrive as you would with MPEG for example.

In order to extract the file reading parts of both the MPEG parser and the AVI parser into a common filter (so that for example it could be replaced by a URL filter) it was necessary to have an interface at this point that was efficient in both cases. This is the IAsyncReader interface. Using this interface, each sample is requested separately with a random access style. You can either have the requests fulfilled synchronously on your own thread (SyncRead) or queue them and collect the completed requests later (Request and Wait).

CPullPin provides the client code for this interface for a downstream pin, and really is designed to let the input pin look the same whether it uses IAsyncReader or the normal push-model IMemInputPin::Receive. If you connect to an output pin that uses IMemInputPin::Receive, the data will be sent to your Receive method. If you connect to an output pin that only supports IAsyncReader, then CPullPin provides the 'glue' that pulls data from the output pin and delivers it to your Receive method. It doesn't really get any of the benefit of being able to read data from different positions in the stream. Accordingly you will not be surprised to learn that the mpeg parser uses CPullPin (in sync mode) whereas the AVI file parser uses the IAsyncReader interface in async mode, but using its own code to access discontiguous chunks independently.

In sync mode, the async reader just reads the file. When you call IAsyncReader::SyncReadAligned, it does an unbuffered ReadFile call and then returns. CPullPin in this mode just has a loop that gets a buffer, calls the sync-read method and then calls the (overridden) Receive method to get it to your pin.

In async mode you call Request to queue a request, which is passed to a worker thread within the async reader. At some point, you can then call WaitForNext to get a completed buffer back from the async reader. Win95 does not support overlapped i/o, and hence I don't believe the file reader uses overlapped i/o in the Windows NT case either even when it is available.

CPullPin in async mode has a loop that queues a request and waits for a completed request, but always keeping one request queued. So in this case, the only difference between the two modes is that in async mode, the next request will be sent to the disk almost immediately, whereas in sync mode, the next request is not queued to the disk until your Receive method has completed (in most of the standard parsers, this is very quick).

So in a high bandwidth case where you need to queue the next request before completing the Receive processing, you may want to consider async mode, but in other cases, you are ok in sync mode. If you want to receive data from many discontiguous parts of the file, you probably want to discard CPullPin entirely and write your own access code, but for most streaming cases, sync mode with CPullPin gives you what you want.

It's also worth noting that the DirectShow MPEG splitters use IAsyncReader methods to access parts of the file at pin-connection time: IMemInputPin would not provide data until the graph was running (see discussion below on live material)

What's the difference between stopped, paused and running states in DirectShow?

In a DirectShow graph, there are three possible states: stopped, paused and running. If a graph is stopped, filters hold a minimum set of resources and do not process data. If the graph is running, filters hold a maximal set of resources and data is processed and rendered.

If a graph is paused then data is processed but not rendered. Source filters will start (or continue) to push data into the graph and transform filters will process the data but the renderer will not draw or write the data. The transform filter will eventually block since its buffers are not being consumed by the renderer. Thus a graph can be cued for rapid starting simply by leaving it in paused mode: once some data arrives at the renderer, the rest of the graph will be blocked by the normal flow control mechanisms (GetBuffer or Receive will block) until the graph is run.

Media types that can be rendered statically, such as video (where you can show a frozen frame) but not, for example, audio, are typically rendered in paused state. Repainting the video window can simply be achieved by transitioning the graph to paused mode and back.

How many threads are there in a typical filter graph?

DirectShow filter graphs typically have one thread per stream segment -- that is, one thread would be active on a source filter's output pin and would push data right through the transform filters and into the renderer. The transform function and delivery downstream would normally occur on this thread during the Receive call. A parser or other filter that has more than one output pin would typically have a new thread for each output pin (plus the source filter's thread on which it receives data). This can be done straightforwardly using a COutputQueue class in each output pin.

In addition, there will typically be a filter graph manager thread and the application's thread

What is g_Templates and why won't my application link without it?

The DirectShow base class library strmbase.lib provides a COM class factory implementation to simplify the development of filters as COM objects. This class factory uses the template implementation you provide: your filter code will supply the templates for objects in your DLL in the g_Templates array, with a count of entries in g_cTemplates. If you are building a filter, you will need to supply this array and count.

If, however, you are writing an application that uses the DirectShow base classes, you do not need to define this -- you can set g_cTemplates to 0 and then g_Templates will not be referenced.

If your DLL is a COM dll that uses another class factory implementation (perhaps your dll is a ATL control) then you will be exporting DllGetClassFactory from your DLL. This is implemented in a number of places, including the ATL and MFC libraries as well as DirectShow's library. Make sure that the correct library is found first otherwise even if you define g_cTemplates you will not be able to create any of your ATL objects.

The DLL registers OK but the wrong module is registered for my filters.

You need to make sure that your DLL starts at DllEntryPoint, defined in dllentry.cpp in the class library. Otherwise the class library's global variable g_hInst is not set to the correct module handle, and registration will go wrong. The best way to make this work with C/C++ runtime initialization is the following code:

How can I make IQueueCommand work?

IQueueCommand allows you to queue up operations on OLE-automatable interfaces for times in the future. The interface allows you to specify accurate timing (eg when the filter is processing data to be presented at time X), but to date it has only ever been implemented with coarse timing (ie, command is executed when the graph reaches time X).

Watch out for three tricky points:

1. the IID is not stored -- instead the IID* is stored, so don't put the REFIID param on the stack.

2. You need to pass the dispid for the command to be executed. Use the type info to find this.

3. Don't forget that the time param is an old-style REFTIME double, in which the integers are the number of seconds.

The following sample code will cause the graph to stop after 5 seconds of playing

ITypeLib* ptlib;
LoadRegTypeLib(LIBID_QuartzTypeLib, 1, 0, 0, &ptlib);
ITypeInfo* pti;
ptlib->GetTypeInfoOfGuid(IID_IMediaControl, &pti);
ptlib->Release();
WCHAR* pMethod = L"Stop";
long dispid;
pti->GetIDsOfNames(&pMethod, 1, &dispid);
pti->Release();
IQueueCommand* pQ;
m_Player.Graph()->QueryInterface(IID_IQueueCommand, (void**)&pQ);
if (pQ) {
HRESULT hr = pQ->InvokeAtStreamTime(
&m_pCmd, 5,
(GUID*)&IID_IMediaControl,
dispid, DISPATCH_METHOD, 0, 0, 0, 0);
pQ->Release();
}

You can implement IQueueCommand in your filter - eg if you want it to apply with fine precision when you are processing the relevant sample. The base classes in ctlutil.h/cpp will help you there. But the distributor will not call your filter, so your app will need to find your filter and QI for IQueueCommand directly on your filter. The fact that this distributor feature was never implemented is probably why no-one ever used IQueueCommand.

Why is frame seeking so slow for Mpeg-1?

The Mpeg-1 stream splitter in DirectX 8 (and 8.0a) contains a bug which makes seeking to frames (with IMediaSeeking using TIME_FORMAT_FRAMES) slow and inaccurate. The time taken is proportional to the offset within the file, so that seeking 3 or 4 minutes into a file can take as much as 5 or 6 seconds. Use ConvertTimeFormat, or simply seek in time mode. The splitter in any case performs frame mode seeking by assuming a constant frame rate, so you can use this in your code to work around this bug.

Media Player creates my filter twice

This seems to be a bug in WMP8 that I think is fixed in WMP9. If you have a source filter that is registered as the handler for a private URL protocol "myproto://parameters...", WMP8 will create the filter twice. It will add both instances to the same graph and will call IFileSourceFilter::Load on both. Probably the best fix is to check in JoinFilterGraph if another instance of your filter already exists in this graph, and if so return an error from JoinFilterGraph.

My app hangs in FilterGraph->Release() when there are several graphs

I think this is an obscure deadlock in the FGControl component of the filter graph manager. There is a worker thread shared between all filter graphs in the same process. This worker thread is blocked waiting for a lock on graph A, which is held by the main app thread. While waiting for the lock, sent messages are processed which results in winproc code on the main app thread being executed. During this winproc execution, your code releases filter graph B. The destructor for graph B (still running on the main app thread) sends a message to the worker thread informing it that the graph is going away, and it blocks for a response which it never gets.

You will only hit this problem if the main app thread is handling events (such as video window activation and de-activation) for other filter graphs when it receives the message that causes your graph to be destroyed. If you control the application code, the simplest fix is to call InSendMessage before destroying the filtergraph, and if this returns true, postpone the graph destruction until later, using PostMessage.

Otherwise I think the fix is to use the "no threads" filter graph CLSID_FilterGraphNoThread. This must be created on a thread that has a message pump (which in most cases means having a dedicated thread that does the CoCreateInstance and then just pumps messages). If you think you are running into this problem, please let me know.