## 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
- Optimizing Capture Versus Preview (Optional)
- Registering a Video Capture Filter
- Producing Data
- Controlling Individual Streams
- Time Stamping
- Necessary Interfaces

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.

Note: If your preview pin is producing 8-bit RGB and must reconnect using 16-bit RGB, the reconnect might fail. This failure might occur if you are connected to a video renderer, because the renderer might need a color converter filter inserted between the filters to convert the 16-bit RGB to 8-bit RGB. In this case, calling the IFilterGraph::Reconnect method will fail. You must do a full-fledged connect again (with CBasePin::Connect). 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 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.

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.

- Implement IAMDroppedFrames on your filter or on each output pin that sends data.
- Implement IAMStreamConfig on each output pin that sends video data.
- Implement IAMStreamControl on each output pin that sends data.
- Implement IAMVideoCompression on each output pin that sends video data.

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