Async Filter Sample
I've been looking at the "Async" filter sample for DirectShow, which shows how to implement IAsyncReader. The sample is an odd combination of useful and not-so-useful code. (Or maybe that's not so odd in an SDK sample.)
Alessandro Angeli, one of the DirectShow MVPs, has a good post about using this sample:
[The only comment I'd make is that CAsyncStream is not actually the output pin.]
Here are some notes about this sample.
The source files are located in 4 folders:
- "Base" has base classes for the filter.
- "Filter" has the filter itself, plus the DLL setup stuff.
- "Include" has include files for both of the above.
- "MemFile" is a command-line application that uses the filter.
Here's how the code is organized. First the base classes:
- CAsyncReader is the base filter class
- CAsyncOutputPin is the output pin. It is instantiated in CAsyncReader::m_OutputPin.
- CAsyncStream is an abstract class that models I/O operations. The CAsyncReader constructor takes a pointer to this class. The derived class is expected to implement I/O operations using the interface defined by this class.
- The filter queues requests in the form of CAsyncResult objects.
- CAsyncIo runs the thread that dispatches the requests. It is instantiated in CAsyncReader::m_Io, and the output pin has a pointer to it in CAsyncOutputPin::m_pIo.
And the filter implementation:
- CAsyncFilter is the example filter. It derives from CAsyncReader
- CMemStream derives from CAsyncStream and does "in memory" I/O from a memory buffer.
When the downstream filter (ie the parser) calls IAsyncRequest::Request, the pin calls CAsyncIO::Request, which:
- Creates a new CAsyncRequest object.
- Calls CAsyncRequest::Request(), which makes the I/O request. That's in theory, but see below.
- Puts the object on the queue.
The worker thread pulls CAsyncRequest objects from the queue and calls CAsyncRequest::Complete, which completes the I/O request.
However, as implemented, CAsyncRequest::Request does not actually make an I/O request. Instead, it just caches the parameters. The Complete() method does all the work, by calling CAsyncStream::Read. So in fact, the filter performs synchronous I/O, but on a worker thread.
When the pin is flushed, it calls CAsyncRequest::Cancel() on all pending requests. However, this method does not actually do anything; it simply returns S_OK.
The CAsyncFilter class is sort of peculiar. It reads the entire file into memory when IFileSourceFilter::Load
is called. Then, CMemStream reads the data that's already in memory. Curiously, CMemStream actually has a bits/second parameter, which can be used to artificially throttle read operations (by calling Sleep!).
If I were redesigning this sample, I would change the definition of CAsyncStream to support truly async I/O calls. Something along the lines of:
virtual HRESULT StartRead( PBYTE pbBuffer, DWORD dwBytesToRead, BOOL bAlign, LPOVERLAPPED pOverlapped, /* [in] Caller-supplied OVERLAPPED structure */ LPBOOL pbPending, /* [out] If TRUE, call EndRead to wait for completion */ LPDWORD pdwBytesRead /* [out] Bytes read, if pbPending receives FALSE */ ) = 0; virtual HRESULT EndRead( LPOVERLAPPED pOverlapped, LPDWORD pdwBytesRead ) = 0;
Incidentally, IAsyncReader is not a very good interface for network streaming, because it assumes you can seek arbitrarily within the stream. For network streaming, it is better to use a push model. If you control both the network protocol and the stream format, you can combine these into a single source filter that does both the network IO and the stream parsing.