Exploring the Grabber Sample Source Code

Exploring the Grabber Sample Source Code

The Grabber Sample begins with a class ID and an interface ID unique to the module, so it can be instantiated (with its class ID) and queried for its interfaces (with its interface ID). Next is a typedef that defines the callback format that is, what data will be passed to the application through the Grabber Sample. This callback format is different from the one used by the Sample Grabber. The Grabber Sample callback is implemented as a function, not as a COM interface as it is in the Sample Grabber. Next comes the interface definition for the IGrabberSample interface. IGrabberSample implements methods that the application uses to communicate with the Grabber Sample. This interface can be located using QueryInterface.

//------------------------------------------------------------------------- // Define new GUID and IID for the grabber example so that they do NOT // conflict with the official DirectX Grabber Sample filter //------------------------------------------------------------------------- // {2FA4F053-6D60-4cb0-9503-8E89234F3F73} DEFINE_GUID(CLSID_GrabberSample, 0x2fa4f053, 0x6d60, 0x4cb0, 0x95, 0x3, 0x8e, 0x89, 0x23, 0x4f, 0x3f, 0x73); DEFINE_GUID(IID_IGrabberSample, 0x6b652fff, 0x11fe, 0x4fce, 0x92, 0xad, 0x02, 0x66, 0xb5, 0xd7, 0xc7, 0x8f); // We define a callback typedef for this example. // Normally, you would make the Grabber Sample support a COM interface, // and in one of its methods you would pass in a pointer to a COM interface // used for calling back. typedef HRESULT (*SAMPLECALLBACK) ( IMediaSample * pSample, REFERENCE_TIME * StartTime, REFERENCE_TIME * StopTime, BOOL TypeChanged ); // We define the interface the app can use to program us MIDL_INTERFACE("6B652FFF-11FE-4FCE-92AD-0266B5D7C78F") IGrabberSample : public IUnknown { public: virtual HRESULT STDMETHODCALLTYPE SetAcceptedMediaType( const CMediaType *pType) = 0; virtual HRESULT STDMETHODCALLTYPE GetConnectedMediaType( CMediaType *pType) = 0; virtual HRESULT STDMETHODCALLTYPE SetCallback( SAMPLECALLBACK Callback) = 0; virtual HRESULT STDMETHODCALLTYPE SetDeliveryBuffer( ALLOCATOR_PROPERTIES props, BYTE *pBuffer) = 0; };

Defining and Implementing the Filter Class

The definition of the filter class CSampleGrabber is closely based on the definition of the IGrabberSample interface. CSampleGrabber includes declarations for all the methods declared within IGrabberSample, but in this case, it actually includes the implementations of these methods. Here s the definition of the CSampleGrabber class:

class CSampleGrabber : public CTransInPlaceFilter, public IGrabberSample { friend class CSampleGrabberInPin; friend class CSampleGrabberAllocator; protected: CMediaType m_mtAccept; SAMPLECALLBACK m_callback; CCritSec m_Lock; // serialize access to our data BOOL IsReadOnly( ) { return !m_bModifiesData; } // PURE, override this to ensure we get // connected with the right media type HRESULT CheckInputType( const CMediaType * pmt ); // PURE, override this to callback // the user when a sample is received HRESULT Transform( IMediaSample * pms ); // override this so we can return S_FALSE directly. // The base class CTransInPlace // Transform( ) method is called by its // Receive( ) method. There is no way // to get Transform( ) to return an S_FALSE value // (which means "stop giving me data"), // to Receive( ) and get Receive( ) to return S_FALSE as well. HRESULT Receive( IMediaSample * pms ); public: static CUnknown *WINAPI CreateInstance(LPUNKNOWN punk, HRESULT *phr); // Expose IGrabberSample STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void ** ppv); DECLARE_IUNKNOWN; CSampleGrabber( IUnknown * pOuter, HRESULT * pHr, BOOL ModifiesData ); // IGrabberSample STDMETHODIMP SetAcceptedMediaType( const CMediaType * pmt ); STDMETHODIMP GetConnectedMediaType( CMediaType * pmt ); STDMETHODIMP SetCallback( SAMPLECALLBACK Callback ); STDMETHODIMP SetDeliveryBuffer( ALLOCATOR_PROPERTIES props, BYTE * m_pBuffer ); };

CSampleGrabber uses the C++ feature of multiple inheritance, declaring both CTransInPlaceFilter and ISampleGrabber as ancestor classes and inheriting the variables and implementation of both classes. The only method implemented in the class definition is the IsReadOnly method, which checks the m_bModifiesData Boolean variable and returns its inverse. If m_bModifiesData is true, the filter is modifying data in its buffers.

Here s the implementation of the three protected methods of CSampleGrabber CheckInputType, Transform, and Receive:

HRESULT CSampleGrabber::CheckInputType( const CMediaType * pmt ) { CheckPointer(pmt,E_POINTER); CAutoLock lock( &m_Lock ); // if the major type is not set, then accept anything GUID g = *m_mtAccept.Type( ); if( g == GUID_NULL ) { return NOERROR; } // if the major type is set, don't accept anything else if( g != *pmt->Type( ) ) { return VFW_E_INVALID_MEDIA_TYPE; } // subtypes must match, if set. if not set, accept anything g = *m_mtAccept.Subtype( ); if( g == GUID_NULL ) { return NOERROR; } if( g != *pmt->Subtype( ) ) { return VFW_E_INVALID_MEDIA_TYPE; } // format types must match, if one is set g = *m_mtAccept.FormatType( ); if( g == GUID_NULL ) { return NOERROR; } if( g != *pmt->FormatType( ) ) { return VFW_E_INVALID_MEDIA_TYPE; } // at this point, for this sample code, this is good enough, // but you may want to make it more strict return NOERROR; } HRESULT CSampleGrabber::Receive( IMediaSample * pms ) { CheckPointer(pms,E_POINTER); HRESULT hr; AM_SAMPLE2_PROPERTIES * const pProps = m_pInput->SampleProps(); if (pProps->dwStreamId != AM_STREAM_MEDIA) { if( m_pOutput->IsConnected() ) return m_pOutput->Deliver(pms); else return NOERROR; } if (UsingDifferentAllocators()) { // We have to copy the data. pms = Copy(pms); if (pms == NULL) { return E_UNEXPECTED; } } // have the derived class transform the data hr = Transform(pms); if (FAILED(hr)) { DbgLog((LOG_TRACE, 1, TEXT("Error from TransInPlace"))); if (UsingDifferentAllocators()) { pms->Release(); } return hr; } if (hr == NOERROR) { hr = m_pOutput->Deliver(pms); } // release the output buffer. If the connected pin still needs it, // it will have addrefed it itself. if (UsingDifferentAllocators()) { pms->Release(); } return hr; } HRESULT CSampleGrabber::Transform ( IMediaSample * pms ) { CheckPointer(pms,E_POINTER); CAutoLock lock( &m_Lock ); if( m_callback ) { REFERENCE_TIME StartTime, StopTime; pms->GetTime( &StartTime, &StopTime); StartTime += m_pInput->CurrentStartTime( ); StopTime += m_pInput->CurrentStartTime( ); BOOL * pTypeChanged = &((CSampleGrabberInPin*) m_pInput)->m_bMediaTypeChanged; HRESULT hr = m_callback( pms, &StartTime, &StopTime, *pTypeChanged ); *pTypeChanged = FALSE; // now that we notified user, can clear it return hr; } return NOERROR; }

The CheckInputType method implements a series of comparisons against the media type that was specified by the application instancing the Grabber Sample. If no local type has been set for the CSampleGrabber object implying that the application did not call SetAcceptedMediaType when initializing the filter anything passed to CheckInputType will return without an error. If a media type is defined, the major type must match, and the subtype and format must match if they ve been defined. Like the Sample Grabber, the Grabber Sample checks only the major type, subtype, and format type. It doesn t examine the details of the format (for example, sample rate on an audio stream, or image width and height in a video stream), so it s possible that the filter will accept a connection to a media stream that the application can t work with.

The Receive method is, in many ways, the heart of the filter class implementation. It overrides CTransInPlaceFilter::Receive, which is called by the input pin s IMemInputPin::Receive method. The method examines the properties of the sample that has been presented on the input pin by calling the CBase InputPin::SampleProps method, which returns an AM_SAMPLE2_PROPERTIES data structure. The method then examines the dwStreamId field of the structure. If this field contains the value AM_STREAM_MEDIA, the sample presented on the input pin is stream data and needs to be processed by the method. If the field contains anything else, the sample on the input pin contains stream control data and is immediately passed along to the Deliver method on the output pin if the output pin is connected to anything downstream.

Next the method calls the CTransInPlaceFilter::UsingDifferentAllocators method. This method returns true if the input and output buffers are different. The Receive method then copies the buffers from the input to output pin buffer using the CTransInPlaceFilter::Copy method, which is necessary because in some cases a transform-in-place filter is forced to act like a copy transform filter with two allocators. (This situation is described exhaustively in the DirectX SDK documentation for the CTransInPlaceFilter class.) Once the buffer has been copied (if necessary), the class s Transform method is invoked, which performs any necessary manipulations of the stream data as it passes from input to output pin. If the Transform completes successfully, the buffer is sent along to the output pin with a call to the output pin s Deliver method. The buffer allocated in the Copy method call (if any) is released, and the method exits.

The Transform method doesn t do anything to the data in the buffer, so in this sense, this transform filter doesn t transform anything. However, this method is where the call to the user-defined callback is made. (This arrangement of filter and callback is similar to the relationship between the Histogram application and the Sample Grabber given earlier in this chapter.) If there s a user-defined callback set up through an invocation of the SetCallback method, it s called. The callback is passed a pointer to the stream buffer, the sample s start time and stop time (in REFERENCE_TIME units of 100 nanoseconds), and a flag indicating whether the media s type has changed. (Your own filters should check for a type change in either the Receive or the Transform method.) Once that s done, Transform exits.

Now let s look at the implementation of the class constructor and the methods that COM needs to publish through the IGrabberSample interface:

CUnknown * WINAPI CSampleGrabber::CreateInstance(LPUNKNOWN punk, HRESULT *phr) { ASSERT(phr); // assuming we don't want to modify the data CSampleGrabber *pNewObject = new CSampleGrabber(punk, phr, FALSE); if(pNewObject == NULL) { if (phr) *phr = E_OUTOFMEMORY; } return pNewObject; } CSampleGrabber::CSampleGrabber( IUnknown * pOuter, HRESULT * phr, BOOL ModifiesData ) : CTransInPlaceFilter( TEXT("SampleGrabber"), (IUnknown*) pOuter, CLSID_GrabberSample, phr, (BOOL)ModifiesData ) , m_callback( NULL ) { // this is used to override the input pin with our own m_pInput = (CTransInPlaceInputPin*) new CSampleGrabberInPin( this, phr ); if( !m_pInput ) { if (phr) *phr = E_OUTOFMEMORY; } // Ensure that the output pin gets created. // This is necessary because our // SetDeliveryBuffer() method assumes // that the input/output pins are created, but // the output pin isn't created until GetPin() is called. The // CTransInPlaceFilter::GetPin() method will create the output pin, // since we have not already created one. IPin *pOutput = GetPin(1); // The pointer is not AddRef'ed by GetPin(), so don't release it } STDMETHODIMP CSampleGrabber::NonDelegatingQueryInterface( REFIID riid, void ** ppv) { CheckPointer(ppv,E_POINTER); if(riid == IID_IGrabberSample) { return GetInterface((IGrabberSample *) this, ppv); } else { return CTransInPlaceFilter::NonDelegatingQueryInterface(riid, ppv); } }

These methods translate neatly into COM methods that we re already familiar with. The CreateInstance method is normally invoked by a COM call to CoCreateInstance, and it instantiates and then returns a CSampleGrabber object. The CSampleGrabber::CSampleGrabber constructor method calls its parent s constructor CTransInPlaceFilter::CTransInPlaceFilter for initialization. It then creates a new input pin with a call to the constructor for CSampleGrabberInPin and an output pin with a call to the parent method CTransInPlace Filter::GetPin. Finally the NonDelegatingQueryInterface method handles the particulars of the QueryInterface COM method and returns an IGrabberSample interface to a passed object, if that s what s requested, or passes the request up the inheritance chain to CTransInPlaceFilter::NonDelegatingQueryInterface.

Now all that s left are the few functions that implement the published interfaces on IGrabberSample: SetAcceptedMediaType, GetConnectedMedia Type, SetCallback, and SetDeliveryBuffer. Here s the implementation of these four methods:

STDMETHODIMP CSampleGrabber::SetAcceptedMediaType( const CMediaType * pmt ) { CAutoLock lock( &m_Lock ); if( !pmt ) { m_mtAccept = CMediaType( ); return NOERROR; } HRESULT hr; hr = CopyMediaType( &m_mtAccept, pmt ); return hr; } STDMETHODIMP CSampleGrabber::GetConnectedMediaType( CMediaType * pmt ) { if( !m_pInput !m_pInput->IsConnected( ) ) { return VFW_E_NOT_CONNECTED; } return m_pInput->ConnectionMediaType( pmt ); } STDMETHODIMP CSampleGrabber::SetCallback( SAMPLECALLBACK Callback ) { CAutoLock lock( &m_Lock ); m_callback = Callback; return NOERROR; } STDMETHODIMP CSampleGrabber::SetDeliveryBuffer( ALLOCATOR_PROPERTIES props, BYTE * m_pBuffer ) { // have the input/output pins been created? if( !InputPin( ) !OutputPin( ) ) { return E_POINTER; } // they can't be connected // if we're going to be changing delivery buffers if( InputPin( )->IsConnected( ) OutputPin( )->IsConnected( ) ) { return E_INVALIDARG; } return ((CSampleGrabberInPin*)m_pInput)->SetDeliveryBuffer( props, m_pBuffer ); }

The SetAcceptedMediaType method establishes the media type acceptable to the filter. If NULL is passed, an empty CMediaType object is instantiated; otherwise, the object is copied locally using the DirectX function CopyMediaType. The GetConnectedMediaType method will return the media type specified on the filter s input pin if that pin is connected. SetCallback sets the member variable m_callback to the value of the supplied parameter. This value, if it exists, will be used when the Transform method is invoked.

Finally the SetDeliveryBuffer method defines the allocator properties and buffer to be used by the filter. If either the input or output pin does not exist, the method fails, which is a defense against a bad pointer and unlikely to happen. Alternatively, if both exist but either is connected, the function fails because if either of the pins is connected, it has already negotiated an allocator. If all these tests pass successfully, the actual work is passed off to CSampleGrabberInPin::SetDeliveryBuffer.

Defining and Implementing the Allocator Class

Although the Grabber Sample is descended from CTransInPlaceFilter and doesn t need to copy input or output buffers, it requires a custom allocator implementation because the Grabber Sample has a feature that allows the application hooked into it to allocate the memory for the sample buffers. This feature gives the application even greater control over the Grabber Sample, as it creates and owns the memory pool that the Grabber Sample uses for its IMediaSample objects. The application can set up this pool by calling CSampleGrabber::Set DeliveryBuffer. Here s the implementation of the custom allocator methods:

class CSampleGrabberAllocator : public CMemAllocator { friend class CSampleGrabberInPin; friend class CSampleGrabber; protected: // our pin who created us CSampleGrabberInPin * m_pPin; public: CSampleGrabberAllocator( CSampleGrabberInPin * pParent, HRESULT *phr ) : CMemAllocator( TEXT("SampleGrabberAllocator\0"), NULL, phr ) , m_pPin( pParent ) { }; ~CSampleGrabberAllocator( ) { // wipe out m_pBuffer before we try to delete it. // It's not an allocated buffer, // and the default destructor will try to free it! m_pBuffer = NULL; } HRESULT Alloc( ); void ReallyFree(); // Override to reject anything that does not match the actual buffer // that was created by the application STDMETHODIMP SetProperties(ALLOCATOR_PROPERTIES *pRequest, ALLOCATOR_PROPERTIES *pActual); };

The CSampleGrabberAllocator object is a descendant of CMemAllocator, which is a DirectX-defined object that allocates storage for media samples, such as DirectShow streams. This definition overrides the constructor CSample Grabber Allocator::CSampleGrabberAllocator, which does little more than invoke the parent method in CMemAllocator and the destructor CSample Grabber Allocator::~CSampleGrabberAllocator, which sets the buffer pointer to NULL, ensuring that a buffer created by the application is released by the application and not by the Grabber Sample. The real work takes place in the three CSampleGrabberAllocator methods: Alloc, ReallyFree, and SetProperties. Here s the implementation of those methods:

HRESULT CSampleGrabberAllocator::Alloc( ) { // look at the base class code to see where this came from! CAutoLock lck(this); // Check he has called SetProperties HRESULT hr = CBaseAllocator::Alloc(); if (FAILED(hr)) { return hr; } // If the requirements haven't changed then don't reallocate if (hr == S_FALSE) { ASSERT(m_pBuffer); return NOERROR; } ASSERT(hr == S_OK); // we use this fact in the loop below // Free the old resources if (m_pBuffer) { ReallyFree(); } // Compute the aligned size LONG lAlignedSize = m_lSize + m_lPrefix; if (m_lAlignment > 1) { LONG lRemainder = lAlignedSize % m_lAlignment; if (lRemainder != 0) { lAlignedSize += (m_lAlignment - lRemainder); } } ASSERT(lAlignedSize % m_lAlignment == 0); // don't create the buffer - use what was passed to us // m_pBuffer = m_pPin->m_pBuffer; if (m_pBuffer == NULL) { return E_OUTOFMEMORY; } LPBYTE pNext = m_pBuffer; CMediaSample *pSample; ASSERT(m_lAllocated == 0); // Create the new samples - // we have allocated m_lSize bytes for each sample // plus m_lPrefix bytes per sample as a prefix. We set the pointer to // the memory after the prefix - // so that GetPointer() will return a pointer to m_lSize bytes. for (; m_lAllocated < m_lCount; m_lAllocated++, pNext += lAlignedSize) { pSample = new CMediaSample( NAME("Sample Grabber memory media sample"), this, &hr, pNext + m_lPrefix, // GetPointer() value m_lSize); // not including prefix ASSERT(SUCCEEDED(hr)); if (pSample == NULL) return E_OUTOFMEMORY; // This CANNOT fail m_lFree.Add(pSample); } m_bChanged = FALSE; return NOERROR; } void CSampleGrabberAllocator::ReallyFree() { // look at the base class code to see where this came from! // Should never be deleting this unless all buffers are freed ASSERT(m_lAllocated == m_lFree.GetCount()); // Free up all the CMediaSamples CMediaSample *pSample; for (;;) { pSample = m_lFree.RemoveHead(); if (pSample != NULL) { delete pSample; } else { break; } } m_lAllocated = 0; // don't free the buffer - let the app do it } HRESULT CSampleGrabberAllocator::SetProperties( ALLOCATOR_PROPERTIES *pRequest, ALLOCATOR_PROPERTIES *pActual ) { HRESULT hr = CMemAllocator::SetProperties(pRequest, pActual); if (FAILED(hr)) { return hr; } ALLOCATOR_PROPERTIES *pRequired = &(m_pPin->m_allocprops); if (pRequest->cbAlign != pRequired->cbAlign) { return VFW_E_BADALIGN; } if (pRequest->cbPrefix != pRequired->cbPrefix) { return E_FAIL; } if (pRequest->cbBuffer > pRequired->cbBuffer) { return E_FAIL; } if (pRequest->cBuffers > pRequired->cBuffers) { return E_FAIL; } *pActual = *pRequired; m_lCount = pRequired->cBuffers; m_lSize = pRequired->cbBuffer; m_lAlignment = pRequired->cbAlign; m_lPrefix = pRequired->cbPrefix; return S_OK; }

The Alloc method is called when the allocator object needs to allocate the media samples. For this allocator, the application provides the memory buffer, so the allocator object doesn t need to allocate any buffer memory. However, the allocator object does need to create the media sample objects. An IMedia Sample object manages a pointer to a block of memory, but the block is allocated and released independently.

The Alloc method determines whether storage has already been allocated for the media samples; if the requirements haven t changed since the previous call to Alloc, the method returns without doing anything. Otherwise, the media samples are released and a new series of CMediaSample objects are created. Each CMediaSample holds a pointer that points to a section of the memory buffer.

The ReallyFree method has a deceptive name. Although this method is supposed to free the buffers allocated by the allocator object, in this case it won t because those buffers haven t been allocated by this object. However, because Alloc did create m_lAllocated CMediaSample objects, the ReallyFree method must release those objects.

Finally the SetProperties method specifies the number of buffers to be allocated and the size of each buffer. This is a request, not a command, so Set Properties returns the actual number and size of buffers allocated by the call, which might be at variance with the number requested. You should always check the returned value. The parameters are passed in an ALLOCATOR_PROPERTIES structure, which has the following definition:

typedef struct _AllocatorProperties { long cBuffers; long cbBuffer; long cbAlign; long cbPrefix; } ALLOCATOR_PROPERTIES;

The cBuffers field specifies the number of buffers to be created by the allocator, while cbBuffer specifies the size of each buffer in bytes. The cbAlign field specifies the byte alignment of each buffer, and cbPrefix allocates a prefix of a specific number of bytes before each buffer, which is useful for buffer header information. Because no allocation is taking place inside this allocator, SetProperties passes the allocation request to its parent class, CMemAllocator, and then performs a series of checks to ensure that the allocations have been performed successfully. Because the allocator properties are set by the application, the request can t exceed these properties. If any of these checks fail, a failure is reported to the caller. If all the checks passed successfully, SetProperties returns the actual allocation values to the caller.

Defining and Implementing the Input Pin Class

The DirectShow filter implementation provides an overridden implementation of the class CTransInPlaceInputPin, which implements all the methods required for handling input pin connections on CTransInPlaceFilter objects. Here s the definition of that class, CSampleGrabberInPin:

class CSampleGrabberInPin : public CTransInPlaceInputPin { friend class CSampleGrabberAllocator; friend class CSampleGrabber; CSampleGrabberAllocator * m_pPrivateAllocator; ALLOCATOR_PROPERTIES m_allocprops; BYTE * m_pBuffer; BOOL m_bMediaTypeChanged; protected: CSampleGrabber * SampleGrabber( ) { return (CSampleGrabber*) m_pFilter; } HRESULT SetDeliveryBuffer( ALLOCATOR_PROPERTIES props, BYTE * m_pBuffer ); public: CSampleGrabberInPin( CTransInPlaceFilter * pFilter, HRESULT * pHr ) : CTransInPlaceInputPin( TEXT("GrabberSampleInputPin\0"), pFilter, pHr, L"Input\0" ) , m_pPrivateAllocator( NULL ) , m_pBuffer( NULL ) , m_bMediaTypeChanged( FALSE ) { memset( &m_allocprops, 0, sizeof( m_allocprops ) ); } ~CSampleGrabberInPin( ) { if( m_pPrivateAllocator ) delete m_pPrivateAllocator; } // override to provide major media type for fast connects HRESULT GetMediaType( int iPosition, CMediaType *pMediaType ); // override this or GetMediaType is never called STDMETHODIMP EnumMediaTypes( IEnumMediaTypes **ppEnum ); // override this to refuse any allocators besides // the one the user wants, if this is set STDMETHODIMP NotifyAllocator( IMemAllocator *pAllocator, BOOL bReadOnly ); // override this so we always return the special allocator, // if necessary STDMETHODIMP GetAllocator( IMemAllocator **ppAllocator ); HRESULT SetMediaType( const CMediaType *pmt ); // we override this to tell whoever's upstream of us what kind of // properties we're going to demand to have STDMETHODIMP GetAllocatorRequirements( ALLOCATOR_PROPERTIES *pProps ); };

Most of the variables and methods in CSampleGrabberInPin concern the allocation of buffers for stream data transfer with an upstream pin, but three methods, GetMediaType, EnumMediaTypes, and SetMediaType, are used during the negotiation of the media type in the pin-to-pin connection process. The constructor, CSampleGrabberInPin::CSampleGrabberInPin, calls its parent s constructor, CTransInPlaceInputPin::CTransInPlaceInputPin, clears a few variables, and zeroes some memory, while the destructor simply frees an associated CSampleGrabberAllocator object if one was created by the pin. Here are the method implementations for the rest of the class:

HRESULT CSampleGrabberInPin::GetMediaType( int iPosition, CMediaType * pMediaType ) { CheckPointer(pMediaType,E_POINTER); if (iPosition < 0) { return E_INVALIDARG; } if (iPosition > 0) { return VFW_S_NO_MORE_ITEMS; } *pMediaType = CMediaType( ); pMediaType->SetType( ((CSampleGrabber*)m_pFilter)->m_mtAccept.Type( ) ); return S_OK; } STDMETHODIMP CSampleGrabberInPin::EnumMediaTypes( IEnumMediaTypes **ppEnum ) { CheckPointer(ppEnum,E_POINTER); ValidateReadWritePtr(ppEnum,sizeof(IEnumMediaTypes *)); // if the output pin isn't connected yet, offer the possibly // partially specified media type that has been set by the user if( !((CSampleGrabber*)m_pTIPFilter)->OutputPin( )->IsConnected() ) { // Create a new reference counted enumerator *ppEnum = new CEnumMediaTypes( this, NULL ); return (*ppEnum) ? NOERROR : E_OUTOFMEMORY; } // if the output pin is connected, // offer it's fully qualified media type return ((CSampleGrabber*)m_pTIPFilter)->OutputPin( )-> GetConnected()->EnumMediaTypes( ppEnum ); } STDMETHODIMP CSampleGrabberInPin::NotifyAllocator ( IMemAllocator *pAllocator, BOOL bReadOnly ) { if( m_pPrivateAllocator ) { if( pAllocator != m_pPrivateAllocator ) { return E_FAIL; } else { // if the upstream guy wants to be read only and we don't, // then that's bad // if the upstream guy doesn't request read only, // but we do, that's okay if( bReadOnly && !SampleGrabber( )->IsReadOnly( ) ) { return E_FAIL; } } } return CTransInPlaceInputPin::NotifyAllocator( pAllocator, bReadOnly ); } STDMETHODIMP CSampleGrabberInPin::GetAllocator( IMemAllocator **ppAllocator ) { if( m_pPrivateAllocator ) { CheckPointer(ppAllocator,E_POINTER); *ppAllocator = m_pPrivateAllocator; m_pPrivateAllocator->AddRef( ); return NOERROR; } else { return CTransInPlaceInputPin::GetAllocator( ppAllocator ); } } HRESULT CSampleGrabberInPin::GetAllocatorRequirements ( ALLOCATOR_PROPERTIES *pProps ) { CheckPointer(pProps,E_POINTER); if (m_pPrivateAllocator) { *pProps = m_allocprops; return S_OK; } else { return CTransInPlaceInputPin::GetAllocatorRequirements(pProps); } } HRESULT CSampleGrabberInPin::SetDeliveryBuffer( ALLOCATOR_PROPERTIES props, BYTE * pBuffer ) { // don't allow more than one buffer if( props.cBuffers != 1 ) { return E_INVALIDARG; } if( !pBuffer ) { return E_POINTER; } m_allocprops = props; m_pBuffer = pBuffer; // If there is an existing allocator, make sure that it is released // to prevent a memory leak if (m_pPrivateAllocator) { m_pPrivateAllocator->Release(); m_pPrivateAllocator = NULL; } HRESULT hr = S_OK; m_pPrivateAllocator = new CSampleGrabberAllocator( this, &hr ); if( !m_pPrivateAllocator ) { return E_OUTOFMEMORY; } m_pPrivateAllocator->AddRef( ); return hr; } HRESULT CSampleGrabberInPin::SetMediaType( const CMediaType *pmt ) { m_bMediaTypeChanged = TRUE; return CTransInPlaceInputPin::SetMediaType( pmt ); }

The GetMediaType method returns a pointer to a CMediaType object. We haven t covered this class before. The CMediaType class is a wrapper for the AM_MEDIA_TYPE structure, which provides accessor methods (such as SetType, SetSubtype, and GetSampleSize) for the fields in the AM_MEDIA_TYPE structure. In addition, the class overrides the assignment operator = and comparison operators != and ==, so when testing one instance of a CMediaType object with another, a test is made of each of the fields within the AM_MEDIA_TYPE structure.

The EnumMediaTypes method returns an enumerated list within an IEnumMediaTypes object of all the media types that the input pin will propose for the connection. Although CBasePin (the ancestor class for all pin objects) implements this functionality, CTransInPlaceInputPin (the ancestor class for this pin) overrides this behavior, so no types are returned when the input pin is unconnected. The Grabber Sample overrides this (yet again) so that media types are always returned, whether the pin is connected or unconnected.

If the output pin on the filter has already been connected, it has already negotiated its media type with a downstream filter. In that situation, the media type of the output pin is returned by EnumMediaTypes as the type required by the input pin. If the output pin isn t connected, the method instantiates an object of class CEnumMediaTypes (IEnumMediaTypes is an interface to CEnum MediaTypes) and returns a pointer to it. Because the constructor is passed with a reference to the CSampleGrabberInPin object, any media type established on the pin perhaps by an earlier invocation of the SetMediaTypes method will be returned in the enumeration.

The next few methods handle stream buffer allocation for the pin. The NotifyAllocator method determines whether the allocation is private does it belong to the filter? If the application has allocated the memory for the pool of samples, the Grabber Sample must insist on using that allocator. If so, it then determines whether the upstream filter s output pin is requesting a read-only buffer. A read-only buffer is acceptable only if the Grabber Sample filter is not modifying the data. (The m_bModifiesData flag is set in the constructor if the buffer will be changed during the callback function.) If that test passes successfully or if the buffer is not private, the method is handed off to the parent, CTransInPlaceInputPin::NotifyAllocator. The GetAllocator method returns a pointer to the IMemAllocator object owned by the CSampleGrabberInPin object if the allocator is private; otherwise, it passes the request to the parent method, CTransInPlaceInputPin::GetAllocator. Much the same happens in the Get Allocator Requirements method. If the allocation is private, a pointer to m_allocprops is returned; otherwise, the request is passed to CTransInPlace InputPin::GetAllocatorRequirements.

Allocation for the pin is handled through the SetDeliveryBuffer method, which stores its passed parameters in the m_allocprops and m_pBuffer variables within the CSampleGrabberInPin object. The method then releases any private allocators and instances a new private allocator, creating a new CSample Grabber Allocator. In this case, the memory allocation is handled by the application, which passes a pointer to the buffer in pBuffer. Finally the media type for the input pin is set through a call to SetMediaType, which simply sets the local Boolean variable m_bMediaTypeChanged to true and then calls the parent method CTransInPlaceInputPin::SetMediaType. The parent classes do all the media type checking, which eventually resolves into a call to CGrabberSample::CheckInputType. The implementation of the input pin is now complete. Together with the allocator, we have everything in place to implement the filter class.

There s just a little more definition work required to finish the entire specification of the filter. Here it is:

const AMOVIESETUP_PIN psudSampleGrabberPins[] = { { L"Input"  // strName , FALSE // bRendered , FALSE // bOutput , FALSE // bZero , FALSE // bMany , &CLSID_NULL // clsConnectsToFilter , L""  // strConnectsToPin , 0 // nTypes , NULL // lpTypes } , { L"Output"  // strName , FALSE // bRendered , TRUE // bOutput , FALSE // bZero , FALSE // bMany , &CLSID_NULL // clsConnectsToFilter , L""  // strConnectsToPin , 0 // nTypes , NULL // lpTypes } }; const AMOVIESETUP_FILTER sudSampleGrabber = { &CLSID_GrabberSample // clsID , L"Grabber Example Filter"  // strName , MERIT_DO_NOT_USE // dwMerit , 2 // nPins , psudSampleGrabberPins }; // lpPin // Needed for the CreateInstance mechanism CFactoryTemplate g_Templates[]= { { L"Grabber Example Filter" , &CLSID_GrabberSample , CSampleGrabber::CreateInstance , NULL , &sudSampleGrabber } };

These data structures AMOVIESETUP_PIN and AMOVIESETUP_FILTER are used to identify the specifics of the filter to DirectShow. For example, the strName field of AMOVIESETUP_FILTER is the filter name as it shows up in GraphEdit or in any DirectShow enumeration of available filters. Changing that field will change the name as it appears in GraphEdit and across DirectShow.

Now, because we re creating a system-wide object, we need to add some entry points common to Microsoft Windows dynamic-link libraries (DLLs).

STDAPI DllRegisterServer() { return AMovieDllRegisterServer2(TRUE); } STDAPI DllUnregisterServer() { return AMovieDllRegisterServer2(FALSE); } // // DllEntryPoint // extern "C" BOOL WINAPI DllEntryPoint(HINSTANCE, ULONG, LPVOID); BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, LPVOID lpReserved) { return DllEntryPoint((HINSTANCE)(hModule), dwReason, lpReserved); }

Finally, in a separate file, grabber.def, we define the DLL entry points for our filter.

LIBRARY grabber EXPORTS DllMain PRIVATE DllGetClassObject PRIVATE DllCanUnloadNow PRIVATE DllRegisterServer PRIVATE DllUnregisterServer PRIVATE

When the project is compiled, you ll end up with a module named grabber.ax, which is a DLL object that must be registered with the system using the regsvr32.exe command. At the command line (and in the directory containing grabber.ax), type regsvr32 grabber.ax.

If all goes smoothly, you ll be informed that the DLL was successfully installed. The Grabber Sample filter is now available to all applications, including GraphEdit. Because you also have the class ID and interface ID for the filter, you can begin to use it in all your DirectShow applications.



Programming Microsoft DirectShow for Digital Video and Television
Programming Microsoft DirectShow for Digital Video and Television (Pro-Developer)
ISBN: 0735618216
EAN: 2147483647
Year: 2002
Pages: 108
Authors: Mark D. Pesce

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net