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

To be an official capture filter, 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 AMPROPERTY_PIN for a complete list of pin categories.) You must support IKsPropertySet if you are a capture filter or an application can't tell how to connect your 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.

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 are responsible for sending a copy of the frame out your preview pin as well as 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 priority is with the capture pin.

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

If you can capture only one format of data, and the preview and capture pins must therefore output 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 out the preview and capture pins. If somebody reconnects your capture pin with a different format, you must reconnect your preview pin with the same format to make it work. If someone has connected the capture pin but not the preview pin, you must allow only your preview pin to connect with the same media type as the capture pin. They must match.

Note: If your preview pin is producing 8-bit RGB and must reconnect using 16-bit RGB, the reconnect might fail, because if you are connected to a video renderer, you might need a color converter filter inserted between you to convert the 16-bit to 8-bit for the video renderer to accept it. In this case, calling the IFilterGraph::Reconnect method will fail. You must do a full-fledged connect again. If you only change between different sizes of motion JPEG, don't worry; a simple reconnect will always work.

The following sample code shows how the more complicated reconnection would work.


// Capture pin is being told to use a certain media type
//
CCapturePin::SetMediaType(CMediaType *pmt);
{
    if (m_pMyPreviewPin->IsConnected()) {

	// We need to reconnect our 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 we simple reconnect
		m_pGraph->Reconnect(m_pMyPreviewPin);
	} else {
		// The other filter WON'T accept this new time. Time to do
		// the connection all over again, possibly pulling in new
		// filters to help connect them
		IPin *pPin = m_pMyPreviewPin->GetConnected();
		m_pGraph->Disconnect(pPin);	// disconnect upstream first
		m_pGraph->Disconnect(m_pMyPreviewPin);
		// The sample code below will make sure the new connection
		// happens with the same media type as we are using
		hr = m_pGraph->Connect(m_pMyPreviewPin, pPin);
		if (FAILED(hr))
			; // UH OH !!!
	}
    }
}

CPreviewPin::CheckMediaType(CMediaType *pmt)
{
	CMediaType cmt = m_pMyCapturePin->m_mt;
 	if (m_pMyCapturePin->IsConnected() && *pmt != cmt)
		// Sorry, our 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, we will be 
		// reconnected to use the same format

	// if our capture pin is connected, and this is the same media type,
	// we are OK.
	return NOERROR;
}

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 in a running state. You do not send data out your pins when in a paused state. 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 each pin can be turned 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. If your preview pin supports IAMStreamControl then time stamp your preview frame. See Video Capture Filter sample for details.

You should also set the MediaTime of the CSample you deliver, and for your capture pin, set the regular time stamp as well. The MediaTime is the frame number of the sample. For example, as you capture frames and send them, and assuming frame 3 gets dropped and isn't sent, you would set the MediaTime values to be (0,1) (1,2) (2,3) (4,5) (5,6) and so on. This enables the downstream filters to know 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 across a pause.

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.

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