Index Topic Contents | |||
Previous Topic: About Stream Architecture Next Topic: Plug-in Distributors |
Connection Model
This article provides an overview of the filter connection architecture in a Microsoft® DirectShow filter graph by examining the behavior of the base classes that implement connection. Because filters connect to other filters using pins, the architecture describes pin connection. Consequently, the CBasePin, CBaseOutputPin, and CBaseInputPin base classes and the IPin interface are discussed. This article describes the connection process and the default functionality built into these classes.
Contents of this article:
Connection Process
When building a filter graph, the filter graph manager connects pins between filters. It also selects filters based on the media type in the file it has been given to render or selects a predetermined configuration for the filter graph it is assembling. The filter graph manager can be asked specifically to add a filter by using the IFilterGraph::AddFilter method. The filter graph manager calls the IBaseFilter::JoinFilterGraph method on the filter to notify it that it has joined the filter graph. The added filter can then be connected like any other filter. When connecting filters, the filter graph manager requests the filters to enumerate their pins and then, for each connection required, requests that an output pin connect to an input pin.
The base classes handle much of the connection mechanism. However, it is important to understand the connection process when writing a filter so that you can identify what to override and what is expected of your filter. Before two connected filters are prepared to pass media between them, the following connection and negotiation processes must occur in this order.
- The initial pin connection occurs.
- The output pin of one filter retrieves interfaces from the connected input pin.
- Both pins negotiate for a common media type.
- Both pins negotiate for an appropriate transport to pass the media.
In the first step, the filter graph manager informs the output pin of one filter to connect to a specified input pin of another filter. This results in an exchange of IPin interface pointers. Filters should never connect to other filters by themselves. The filter graph manager must always be the agent that initiates a connection, because deadlocks can occur otherwise. A filter or an application can instruct the filter graph manager to connect two pins (through the IGraphBuilder::Connect or IFilterGraph::ConnectDirect method), or the filter graph manager can determine to connect filters when rendering a filter by using the IGraphBuilder::Render or IGraphBuilder::RenderFile method.
In the second step, the output pin may request the IMemInputPin interface from the connected input pin. This is in preparation for the fourth step, where the output pin will use IMemInputPin to retrieve a memory allocator from the input pin. If the output pin already has a memory allocator (or some other transport in the case of hardware filters), it can skip this step or can request some other interface in a proprietary design.
In the third step, media types are tried until one is found that is acceptable to both pins or the pins run out of types to try (in which case the connection fails). First, the output pin asks the connected input pin to propose its list of media types. If none of these are acceptable to the output pin, the output pin proposes its own types.
In the fourth step, the output pin asks the connected input pin for an allocator interface. The output pin then either accepts the allocator, or proposes its own allocator and notifies the input pin of the selection. The output pin makes the final determination.
How the Base Classes Implement Connection
The CBasePin class and its derived base classes, CBaseOutputPin and CBaseInputPin, implement most of the mechanism for the most common connection scenarios, much of which can be overridden by the derived filter class for more control of the process.
The connection procedure relies on the implementation of four interfaces:
- IPin, which is implemented by the CBasePin class and inherited by the CBaseInputPin and CBaseOutputPin classes.
- IEnumMediaTypes, which is implemented by the CEnumMediaTypes class and passed out by the IPin::EnumMediaTypes method.
- IMemInputPin, which is implemented by the CBaseInputPin class.
- IMemAllocator, which is implemented by the CBaseAllocator class and passed out by the IMemInputPin::GetAllocator method.
The IMemInputPin and IMemAllocator interfaces are necessary only if the filter belonging to the connecting input pin (called the downstream filter) is expected to provide a shared memory allocator for transporting samples between the pins. However, the base class implementation in CBaseInputPin assumes this condition and implements IMemInputPin to provide an allocator object to a connected output pin that requests it.
In the connection scenario of the default base class, the pin classes derived from CBaseInputPin and CBaseOutputPin need only to override and implement a few member functions and can let the base classes do the remaining work. Base classes derived from these classes, such as CTransformInputPin and CTransformOutputPin, do much of the required implementation to provide a default connection scheme.
Pin classes derived from CBaseInputPin and CBaseOutputPin need only to override the following member functions to enable pin connection.
- CBasePin::CheckMediaType, which is called for every media type proposed by the media type enumerator. The overriding member function must accept or reject the proposed media type.
- CBasePin::GetMediaType, which is called by the media type of the output pin enumerator to suggest media types already agreed on by the input pin for transform filters. This member function also presents the type of media a source filter will produce.
Additionally, the output pin derived from the CBaseOutputPin class must override the CBaseOutputPin::DecideBufferSize member function. This is called by the base classes to let the output pin inform any acquired allocator of the size and type of media samples that the output pin will provide. This is done by the output pin of the filter because the derived filter class should know the type and size of the data it will send to the input pin of the connected filter.
To see the context of these overriding functions, it is helpful to step through the execution of the connection code in the class library. All connection takes place in the scope of one CBasePin::Connect member function.
The Filter Graph Manager Starts the Connection
The connection starts when the filter graph manager calls the IPin::Connect method on the output pin, passing it a pointer to the input pin to which it is connecting. The filter graph manager has previously retrieved pointers to the IPin interfaces of both filters, for example, by calling the IBaseFilter::EnumPins method on each connecting filter. The EnumPins method creates a CEnumPins object to enumerate the pins, which the enumerator does by repeatedly calling the CBaseFilter::GetPin member function of the derived filter, which the derived filter must implement.
The CBasePin::Connect implementation of IPin::Connect does much of the work in this case. It calls the following functions.
- CheckConnect, which is overridden by CBaseOutputPin.
- AgreeMediaType, which is implemented by CBasePin.
The CBasePin::CheckConnect implementation simply determines that the pin directions are different. The overriding CBaseOutputPin::CheckConnect member function calls the IUnknown::QueryInterface method of the connected input pin to retrieve a pointer to the IMemInputPin interface of that pin. This will be used later in the connection process to request an allocator from the connected input pin. (Your derived class can override CBaseOutputPin::CheckConnect and omit retrieving the IMemInputPin interface if the output pin already has an allocator; for example, it might want to use the allocator from an upstream filter to eliminate copying.)
Negotiating Media Types with CBasePin::AgreeMediaType
The CBasePin::AgreeMediaType member function is called next and attempts to negotiate a media type that both pins agree on. It does this by trying to find a media type presented by the connected input pin with which the output pin agrees. If that fails, it tries to find a media type preferred by the output pin that the connected input pin agrees with.
CBasePin::AgreeMediaType calls the following member functions and methods.
- IPin::EnumMediaTypes on the connected pin.
- CBasePin::TryMediaTypes in the derived output pin class.
The IPin::EnumMediaTypes method of the connected input pin is called to return a media type enumerator (IEnumMediaTypes). This allows the output pin to examine the list of preferred media types belonging to the input pin.
The IEnumMediaTypes::Next method of the enumerator calls the GetMediaType member function of the derived input pin to retrieve each media type. If GetMediaType is not implemented, the base class implementation returns an error but this does not necessarily break the connection. (Pins are not required to have a preferred media type if one pin or the other can propose a type that they both accept. If neither pin can propose types, the connection will fail.)
Determining a Media Type with CBasePin::TryMediaTypes
CBasePin::AgreeMediaType calls CBasePin::TryMediaTypes next. The TryMediaTypes member function cycles through the preferred media types of the connected input pin and calls the CBasePin::CheckMediaType member function of the derived output pin class for each one it finds. CheckMediaType must be implemented by your derived output pin class. If CheckMediaType accepts the media type, the IPin::ReceiveConnection method of the connected input pin is called with the media type to determine if the connected input pin accepts this media type. If so, TryMediaTypes calls the CBaseOutputPin::CompleteConnect member function to finish the connection to the input pin.
If the input pin has no media types that the output type can use, CBasePin::AgreeMediaType repeats the entire process, using the enumerator for the media types of the output pin. (That is, it gets its own enumerator and calls TryMediaTypes with each of its preferred media types.) Again, the enumerator calls GetMediaType for each media type in the list. In this case, GetMediaType should be implemented to provide a media type. If the filter is a source filter, it will have a definite media type to export. If the filter is a transform filter, the media type will be established between the filter's input pin and its connected pin; the transform filter should query for that media type or simply use the enumerator of the upstream filter (unless the transform filter changes the media type from input pin to output pin).
CheckMediaType is called by CBasePin::TryMediaTypes, even when TryMediaTypes enumerates the list of the preferred media types of the output pin. This is because the owning filter might be a transform-inplace filter that is simply using the media type (and enumerator) of an upstream filter; this is the point at which the filter determines if the media type is compatible. The input pin of this transform filter might defer selecting a media type when it is connected, in which case it would be up to the output pin of the transform filter to ensure the media type is compatible with its transform.
If a media type can be established, TryMediaTypes eventually calls the CBaseOutputPin::CompleteConnect member function to negotiate a memory allocator.
First, the CBaseOutputPin::CompleteConnect member function calls the CBaseOutputPin::DecideAllocator member function. This member function negotiates a shared memory allocator with the input pin. It does this by first calling the IMemInputPin::GetAllocator method of the connected input pin, which retrieves a pointer to an IMemAllocator interface provided by the input pin.
Then, CompleteConnect calls the pure virtual CBaseOutputPin::DecideBufferSize member function, which your derived output pin class must override and implement because only the derived class can determine the required buffer size for its media type.
Finally, CompleteConnect calls the IMemInputPin::NotifyAllocator method of the connected pin to inform the input pin of the allocator to use and to provide a pointer to it. The input pin can reject this allocator, in which case the output pin can retry with a different allocator or fail the connection. If your derived class is not using the allocator of the connected input pin, override CBaseOutputPin::DecideAllocator in your derived class to call the NotifyAllocator member function with an allocator.
When a Reconnection Should Occur
Reconnection is always performed through the IFilterGraph interface on the filter graph manager. Reconnect by calling the IFilterGraph2::ReconnectEx method or the IFilterGraph::Reconnect method, both of which pass the IPin interfaces of the two pins to be reconnected. The ReconnectEx method specifies a media type and thus removes the burden of remembering what type to reconnect with from the pins, which makes the reconnection more likely to succeed.
Filters are typically connected with the upstream filter first and the downstream filter second. Therefore, the filter negotiates the connection on its input pin before information is available about the filter being connected to its output pin. When the output pin of the filter connects, it may become clear that the media type or allocator that was established for the input pin of the filter are not appropriate. In this case, the input connection can be broken and reconnected.
For example, consider the following connection scenario. An audio effects filter (for example, a reverberation effect) is inserted between an MPEG-audio decompressor filter and another audio effects filter. During the upstream connection to the decompressor filter, a media type is chosenfor example, 22.05 kHz, 16-bit mono. However, in this scenario, when the reverberation filter connects its output pin, the downstream filter will accept only an 11.025 kHz, 16-bit mono media type. Therefore, after connecting with the downstream filter, the reverberation effects filter must then reconnect with the upstream filter and negotiate for an 11.025 kHz media type.
But media types are not the only reason for reconnection. In many cases, the filter is a transform-inplace filter; that is, a filter that does not require that it either alters the media type or copies the data. Such a filter can be designed to use an allocator of some other filter (upstream or downstream), and likewise use the media type of another filter. That is, the filter is doing its transform "in place" in the buffer of another filter (for example, in the file buffer of the source filter or the video buffer of the rendering filter).
The general rule is that filters of this type should offer the allocator of the downstream filter to the upstream filter, once the allocator has been established for the output pin. This requires a reconnection of the input pin so that, when the input pin is asked for an allocator (in IMemInputPin::GetAllocator) by the upstream output pin, it can offer the allocator retrieved from the downstream filter by the output pin of the transform filter. Therefore, in-place transforms always reconnect.
There are a couple of important rules to follow when requesting a reconnection.
First, a filter must never request a reconnection unless it is certain that the reconnection will succeed. If the reconnection fails, it causes an asynchronous error in the filter graph for which there is no obvious cleanup. Any error that occurs (for example, from incompatible media types) should occur when the pins are connected the first time, when there are ample retry options available at more than one level (by the filter graph manager or the application at least).
Second, a filter should request a reconnection on the same thread as the call to IPin::Connect. For example, the following scenario attempts reconnection on a separate thread and will cause problems.
- The filter graph manager calls Connect on a pin.
- The filter pin carries out the Connect method and creates a thread, which starts to determine whether everything is okay for the connection.
- Connect returns to the filter graph manager.
- The filter graph manager returns to the application.
- The application calls the IMediaControl::Run method of the filter graph manager to start the filter graph, and the filters start running.
- The thread from the initial connection calls the IFilterGraph2::ReconnectEx or IFilterGraph::Reconnect method and the filter graph manager attempts to carry out reconnection.
- Failure occurs because the filters cannot reconnect while in the running state.
The filter graph has code to prevent this failure as long as the IFilterGraph2::ReconnectEx or IFilterGraph::Reconnect method takes effect while the filter graph is still processing the IGraphBuilder::Connect method. Calling the filter graph to reconnect before returning from the IPin::Connect method is the best way to ensure this problem does not occur. The best way to achieve this is to perform all of this on the same thread.
© 1998 Microsoft Corporation. All rights reserved. Terms of Use.