DirectShow Animated Header -- Write a Video Capture Filter DirectShow Animated Header -- Write a Video Capture Filter* Microsoft DirectShow SDK
*Index  *Topic Contents
*Previous Topic: How to...
*Next Topic: Write an Audio Capture Filter

Write a Video Capture Filter


This article outlines important points to consider when writing a video capture filter. The Microsoft® DirectShow™ SDK includes a standard VFW Video Capture filter.

Contents of this article:

Capture and Preview Pin Requirements

The capture pin and preview pin (if there is one) of the capture filter must support the IKsPropertySet interface. Applications call this interface to ask "what category of pin are you?" by getting the AMPROPERTY_PIN_CATEGORY value of the AMPROPSETID_Pin property set. The value you return is typically either the PIN_CATEGORY_CAPTURE or PIN_CATEGORY_PREVIEW GUID. (See Pin Property Set for a complete list of pin categories.) A capture filter must support IKsPropertySet or an application can't tell how to connect the filter in a filter graph.

You can name the pin anything you want and you can have other output pins for any additional purposes that you want. If your pin name begins with the tilde (~) character, the filter graph will not automatically render that pin when an application calls IGraphBuilder::RenderFile. For instance, if you have a capture filter with both a capture pin and a preview pin, you might want to name the capture pin "~capture" and the preview pin "preview." Given those names, if an application renders that filter in a graph, the preview pin will be connected to a video renderer, and nothing will be connected to the capture filter, which is probably what you want to happen by default. This can also apply to pins that are just informational and are not meant to be rendered, or need to be enumerated so that their properties can be set.

The tilde (~) prefix only affects the behavior of RenderFile and intelligent connect (IGraphBuilder::Connect). Note that intelligent connect can still be used to connect pins with this property if they implement the IPin::Connect method. However, output pins of intermediate filters which are being used to complete the connection which have the tilde at the start of their name will not be connected as part of the intelligent connection attempt.

See Audio Capture Pin Requirements for more details about audio capture filters.

The following sample code demonstrates how to implement IKsPropertySet on a capture pin.


//
// PIN CATEGORIES - let the world know that we are a CAPTURE pin
//

HRESULT CMyCapturePin::Set(REFGUID guidPropSet, DWORD dwPropID, LPVOID pInstanceData, DWORD cbInstanceData, LPVOID pPropData, DWORD cbPropData)
{
    return E_NOTIMPL;
}

// To get a property, the caller allocates a buffer which the called
// function fills in. To determine necessary buffer size, call Get with
// pPropData=NULL and cbPropData=0.
HRESULT CMyCapturePin::Get(REFGUID guidPropSet, DWORD dwPropID, LPVOID pInstanceData, DWORD cbInstanceData, LPVOID pPropData, DWORD cbPropData, DWORD *pcbReturned)
{
    if (guidPropSet != AMPROPSETID_Pin)
	return E_PROP_SET_UNSUPPORTED;

    if (dwPropID != AMPROPERTY_PIN_CATEGORY)
	return E_PROP_ID_UNSUPPORTED;

    if (pPropData == NULL && pcbReturned == NULL)
	return E_POINTER;

    if (pcbReturned)
	*pcbReturned = sizeof(GUID);

    if (pPropData == NULL)
	return S_OK;

    if (cbPropData < sizeof(GUID))
	return E_UNEXPECTED;

    *(GUID *)pPropData = PIN_CATEGORY_CAPTURE;
    return S_OK;
}


// QuerySupported must either return E_NOTIMPL or correctly indicate
// if getting or setting the property set and property is supported.
// S_OK indicates the property set and property ID combination is
HRESULT CMyCapturePin::QuerySupported(REFGUID guidPropSet, DWORD dwPropID, DWORD *pTypeSupport)
{
    if (guidPropSet != AMPROPSETID_Pin)
	return E_PROP_SET_UNSUPPORTED;

    if (dwPropID != AMPROPERTY_PIN_CATEGORY)
	return E_PROP_ID_UNSUPPORTED;

    if (pTypeSupport)
	*pTypeSupport = KSPROPERTY_SUPPORT_GET;
    return S_OK;
}

Optimizing Capture Versus Preview (Optional)

When your filter is running and capturing data, you must send a copy of the frame from your preview pin as well as from your capture pin. If you can do hardware-assisted preview — through an overlay, for example — and if you have a preview pin, you can use the IOverlay interface transport for your preview pin instead of the IMemInputPin interface. Using IOverlay is optional. If you can't do hardware-assisted preview, only send a frame out the preview pin if you have some spare time. Don't do it if it will make you drop any frames — the capture pin has priority.

For example, you might deliver a frame from the preview pin only if you have nothing to send from the capture pin right now and the downstream filter has released all buffers previously delivered from the capture pin.

If you can capture only one format of data, and the preview and capture pins must therefore produce the same media type, or if you want information about how to properly reconnect pins, read on. Otherwise, skip this section.

Send data of the same format from the preview and capture pins. If the filter graph manager reconnects your capture pin with a different format, you must reconnect your preview pin with the same format to make it work. If your capture pin is connected but your preview pin is not, you must allow only your preview pin to connect with the same media type as the capture pin. They must match.

The following sample code shows how the reconnection should work.


// The capture pin cannot accept a media type if the preview pin is connected to a filter 
// that won't accept the new media type. The preview pin must always output the same type as 
// the capture pin.
//
CCapturePin::CheckMediaType(CMediaType *pmt)
{
	if (m_pMyPreviewPin->IsConnected()) {
		// Does the preview pin's connection like this type?
  		if (m_pMyPreviewPin->GetConnected()->QueryAccept(pmt) != NOERROR) {
			// Sorry, the preview pin can't reconnect with this new type.
			return E_INVALIDARG;
		}
	}

	// You decide if you like this media type or not, maybe by
	// knowing what the capture pin will connect with. But don't
	// worry; when the capture pin is connected, you will be 
	// reconnected to use the same format.

	return NOERROR;
}

// The preview pin can only accept the media type that the capture pin is using.
//
CPreviewPin::CheckMediaType(CMediaType *pmt)
{
	CMediaType cmt = m_pMyCapturePin->m_mt;
 	if (m_pMyCapturePin->IsConnected() && *pmt != cmt)
		// Sorry, the preview pin is only allowed to connect with
		// the same format as the capture pin.
		return E_INVALIDARG;

	else if (!m_pMyCapturePin->IsConnected())
		// You decide if you like this media type or not, maybe by
		// knowing what the capture pin will connect with. But don't
		// worry; when the capture pin is connected, you will be 
		// reconnected to use the same format.

	// If the capture pin is connected, and this is the same media type,
	// you are OK.
	return NOERROR;
}

// The capture pin is being told to use a certain media type. Now the preview pin
// must be made to use that same mediatype.
//
CCapturePin::SetMediaType(CMediaType *pmt);
{
    if (m_pMyPreviewPin->IsConnected()) {

	// You need to reconnect the preview pin with this media type.
  	if (m_pMyPreviewPin->GetConnected()->QueryAccept(pmt) == NOERROR) {

		// The other filter that the preview pin is connected to
		// can accept this new media type, so simply reconnect.
		m_pFilter->m_pGraph->Reconnect(m_pMyPreviewPin);
	}
    }
}

Registering a Video Capture Filter

You must register your filter in the video capture filter category. See AMovieDllRegisterServer2 for more information.

Producing Data

Produce data on capture and preview pins only when the filter graph is in a running state. You do not send data from your pins when the filter graph is paused. This will confuse the filter graph unless you return VFW_S_CANT_CUE from the CBaseFilter::GetState function, warning the filter graph that you do not send data when paused. The following code shows you what to do.


CMyVidcapFilter::GetState(DWORD dw, FILTER_STATE *State)
{
	*State = m_State;
	if (m_State == State_Paused)
		return VFW_S_CANT_CUE;
	else
		return S_OK;
}

Controlling Individual Streams

All output pins should support the IAMStreamControl interface, so an application can turn each pin on or off individually (for instance, to preview without capturing). IAMStreamControl enables you to switch between preview and capture without rebuilding a different graph.

Time Stamping

When you capture a frame and are sending it, time stamp the frame with the time the graph's clock says it is when the frame is captured. The end time is the start time plus the duration. For example, if you are capturing at 10 frames per second, and the graph's clock says 200,000,000 units at the time the frame is captured, the sample is stamped (200000000, 201000000) (there are 10,000,000 units per second).

A preview frame should have no time stamp because of latency problems. The latency is due to the fact that, if the time of the sample is the graph's time when it leaves the preview pin, by the time the sample gets to the renderer, it will be late. Therefore the renderer may choose not to draw the sample in order to save time and "catch up", which can't happen for a live stream. Implementing IAMStreamControl requires time stamps, so you can choose not to implement stream control on the preview pin, only time stamp the preview pin sample when there are outstanding requests to start or stop, or live with the latency problem. See the source code for the VidCap Sample (Video Capture Filter) sample for details.

You should set the media time of the sample you deliver; also set the regular time stamp for your capture pin. The media time is the frame number of the sample. For example, if you are capturing and sending frames and frame 3 gets dropped, you would set the media time values to be (0,1) (1,2) (2,3) (4,5) (5,6) and so on. This informs the downstream filters if any video frames were dropped even when the regular time stamps are a little random because the clock being used is not the video digitizing clock.

Also, if you are in a running state, and then pause, and then run again, you must not send a sample with a time stamp less than the last one you sent before pausing. Time stamps can never go back in time, not even back to before a pause occurred.

Frame Rates

The frame rate at which your filter should produce data is determined by the AvgTimePerFrame field of the VIDEOINFOHEADER of the media type your output pin is connected with. You may not be able to capture at any arbitrary frame rate, but only certain rates. If your pin is connected with a media type that asks for a frame rate you can't provide, you should provide frames at the next lowest frame rate possible. For instance, if your media type has AvgTimePerFrame=333333 (approx 1/30 of a second, meaning 30 frames per second), and you can only capture 29.97 or 35 frames per second, you should provide frames at 29.97 frames per second, since that is the closest value lower than 30 that you can provide. You can provide a higher frame rate than asked if the frame rate you provide does not create frame durations more than 1 microsecond shorter than requested, since the AvgTimePerFrame may simply have rounding errors. If the AvgTimePerFrame field is 0, you can supply frames at any default frame rate that you like.

Necessary Interfaces

Read about the following interfaces and consider implementing them. You should implement these interfaces to provide functionality that applications might rely on, so these interfaces are strongly recommended.

© 1998 Microsoft Corporation. All rights reserved. Terms of Use.

*Top of Page