Bagikan melalui


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:

https://groups.google.com/group/microsoft.public.win32.programmer.directx.video/msg/01dde303fa3088c5

[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:

  1. Creates a new CAsyncRequest object.
  2. Calls CAsyncRequest::Request(), which makes the I/O request. That's in theory, but see below.
  3. 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.