How to Write a Source Filter for DirectShow

[The feature associated with this page, DirectShow, is a legacy feature. It has been superseded by MediaPlayer, IMFMediaEngine, and Audio/Video Capture in Media Foundation. Those features have been optimized for Windows 10 and Windows 11. Microsoft strongly recommends that new code use MediaPlayer, IMFMediaEngine and Audio/Video Capture in Media Foundation instead of DirectShow, when possible. Microsoft suggests that existing code that uses the legacy APIs be rewritten to use the new APIs if possible.]

This topic describes how to write a custom source filter for DirectShow.

Note

This topic describes push sources only; it does not describe pull sources, such as the Async Reader Filter, or splitter filters that connect to pull sources. For the distinction between push and pull sources, see Data Flow for Filter Developers.

The DirectShow Streaming Model

When you write a source filter, it is important to understand that a push source is not the same thing as a live source. A live source gets data from some external source, such as a camera or a network stream. Generally, a live source cannot control the incoming rate of data. If the downstream filters do not consume the data fast enough, the source will need to drop samples.

But a push source does not have to be a live source. For example, a push source can read data from a local file. In that case, the downstream renderer filters determine how fast they consume the data from the source, based on the reference clock and the sample time stamps. The source filter delivers samples as quickly as possible, but the actual data flow is gated limited by the renderers. The mechanisms for gating the data flow are described in Data Flow for Filter Developers.

Each output pin on the source filter creates a thread called a streaming thread. The pin delivers samples on the streaming thread. Typically, all of the decoding, processing, and rendering happens on this thread, although some downstream filters might create additional threads to queue their output samples.

The streaming thread runs a loop with the following structure:

until (stopped)
  1. Get a media sample from the allocator.
  2. Fill the sample with data.
  3. Time stamp the sample. 
  4. Deliver the sample downstream.

If no samples are available, step 1 blocks until a sample becomes available. Step 4 can also block; for example, it can block while the graph is paused.

The loop runs as quickly as possible, but is limited by how quickly the renderer filter renders each sample. Assuming the filter graph has a reference clock, the rate is determined by the presentation times on the samples. If there is no reference clock, the renderer consumes samples as quickly as possible.

Using CSource and CSourceStream

The DirectShow base classes include two classes that support push sources: CSource and CSourceStream.

Output Pins

A source filter can have more than one output pin. In your filter's constructor method, create one or more pins derived from CSourceStream (one pin per output stream). You don't need to store pointers to the pins; the pins automatically add themselves to the filter when they are created.

Output Formats

The output pin handles format negotiation with the following CSourceStream methods:

Method Description
GetMediaType Gets a media type from the output pin.
The pin must propose at least one media type, because the downstream filter might not propose any types. In most cases, the downstream filter will be a decoder or a renderer, depending on whether the source filter delivers compressed or uncompressed data. A renderer filter usually requires a complete media type, containing all of the format information needed to render the stream. For a decoder, the amount of information that is required in the media type depends very much on the encoding format.
CheckMediaType Checks if the output pin accepts a given media type. Overriding this method is optional, depending on how you implement GetMediaType.

The GetMediaType method is overloaded:

If the source filter's output pin supports exactly one media format, you should override (1) to initialize the CMediaType object with that format. Leave the default implementation of (2) and also leave the default implementation of CheckMediaType.

If the pin supports more than one format, override (2). Initialize the CMediaType object according to the value of the index variable. The pin should return the formats as an ordered list. In this case, you must also override the CheckMediaType to check the media type against your list of formats.

For uncompressed video formats, remember that the downstream filter can propose formats with various stride values. Your filter should accept any valid stride value. For more information, see BITMAPINFOHEADER.

You must also override the pure-virtual CBaseOutputPin::DecideBufferSize method. Use this method to set the size of the sample buffers.

Streaming

The CSourceStream class creates the streaming thread for the pin. The thread procedure is implemented in the CSourceStream::DoBufferProcessingLoop method. This method calls the pure-virtual CSourceStream::FillBuffer method, which the derived class must override. This method is where the pin fills the buffer with data. For example, if your filter delivers uncompressed video, this is where you would draw the video frames.

The base class automatically starts and stops the thread loop at the right times, when the filter pauses or stops. When this happens, the CSourceStream class calls some methods to notify your derived class:

You can override these methods if you need to add any special handling. Otherwise, the default implementations simply return S_OK.

Seeking

If you have a source filter with one output pin, you can use the CSourceSeeking class as a starting point for implementing seeking. Inherit your pin class from both CSourceStream and CSourceSeeking.

Note

CSourceSeeking is not recommended for a filter with more than one output pin. The main issue is that only one pin should respond to seeking requests. Typically this requires communication among the pins and the filter.

The CSourceSeeking class manages the playback rate, start time, stop time, and duration. Your derived class should set the initial stop time and duration. Whenever one of these values changes, the CSourceSeeking::ChangeRate, CSourceSeeking::ChangeStart, or CSourceSeeking::ChangeStop method is called, as appropriate. The methods are all pure virtual methods. The derived pin class overrides these methods to do the following:

  1. Call IPin::BeginFlush on the downstream pin. This causes downstream filters to release samples they are holding and reject new samples.
  2. Call CSourceStream::Stop to stop the streaming thread. The source filter suspends producing new data.
  3. Call IPin::EndFlush on the downstream pin. This signals the downstream filters to accept new data.
  4. Call IPin::NewSegment with the new start and stop times and rate.
  5. Set the discontinuity property on the next sample.

For more information, see Supporting Seeking in a Source Filter.

If your filter supports seeking, the stream position is now independent of the presentation time. After a seek, time stamps reset to zero. The general formula for time stamps is:

  • sample start time = time elapsed / playback rate
  • sample end time = sample start time + (time per frame / playback rate)

where time elapsed is the time that has elapsed since the filter started running or since the last seek command.

Time Formats for Seeking

By default, seek commands are in units of 100-nanoseconds. Your source filter can support additional time formats, such as seeking by frame number. Each time format is identified by a GUID; see Time Format GUIDs.

To support additional time formats, you must implement the following methods on your output pin:

If the application sets a new time format, all of the position parameters in the IMediaSeeking methods are interpreted in terms of the new time format. For example, if the time format is frames, the IMediaSeeking::GetDuration method must return the duration in frames.

In practice, few DirectShow filters support additional time formats, and as a result, few DirectShow applications make use of this capability.

Writing Source Filters