Search code examples
c++-clidirectshow

Problems connecting to the input pins of GMFBridge Sink Filter


I am experiencing a strange problem when trying to use the GMFBridge filter with the output of an Euresys UxH264 card.

I am trying to integrate this card into our solution, that relies on GMFBridge to handle the ability of continuous capture to multiple files, performing muxing and file-splitting without having to stop the capture graph.

This card captures video and audio from analog inputs. It provides a DirectShow filter exposing both a raw stream of the video input and a hardware-encoded H.264 stream. The audio stream is provided as an uncompressed stream only.

When I attempt to directly connect any of the output pins of the Euresys source filters to the input pins of the GMFBridge Sink, they get rejected, with the code VFW_E_NO_ALLOCATOR. (In the past I have successfully connected both H.264 and raw audio streams to the bridge).

Grasping at straws, I plugged in a pair of SampleGrabber filters between the Euresys card filters and the bridge sink filter, and -just like that- the connections between sample grabbers and sink were accepted.

However, I am not getting any packets on the other side of the bridge (the muxing graph). I inspected the running capture graph with GraphStudioNext and somehow the sample grabbers appear detached from my graph, even though I got successful confirmations when I connected them to the source filter!.

Here's the source code creating the graph.

void EuresysSourceBox::BuildGraph() {
   HRESULT hRes;

   CComPtr<IGraphBuilder> pGraph;
   COM_CALL(pGraph.CoCreateInstance(CLSID_FilterGraph));
   #ifdef REGISTER_IN_ROT
      _rotEntry1 = FilterTools::RegisterGraphInROT(IntPtr(pGraph), "euresys graph");
   #endif

   // [*Video Source*]

   String^ filterName = "Ux H.264 Visual Source";
   Guid category = _GUIDToGuid((GUID)AM_KSCATEGORY_CAPTURE);
   FilterMonikerList^ videoSourceList = FilterTools::GetFilterMonikersByName(category, filterName);
   CComPtr<IBaseFilter> pVideoSource;
   int monikerIndex = config->BoardIndex;  // a filter instance will be retrieved for every installed board
   clr_scoped_ptr<CComPtr<IMoniker>>^ ppVideoSourceMoniker = videoSourceList[monikerIndex];

   COM_CALL((*ppVideoSourceMoniker->get())->BindToObject(NULL, NULL, IID_IBaseFilter, (void**)&pVideoSource));
   COM_CALL(pGraph->AddFilter(pVideoSource, L"VideoSource"));

   // [Video Source]
   //
   // [*Audio Source*]

   filterName = "Ux H.264 Audio Encoder";
   FilterMonikerList^ audioSourceList = FilterTools::GetFilterMonikersByName(category, filterName);
   CComPtr<IBaseFilter> pAudioSource;
   clr_scoped_ptr<CComPtr<IMoniker>>^ ppAudioSourceMoniker = audioSourceList[monikerIndex];
   COM_CALL((*ppAudioSourceMoniker->get())->BindToObject(NULL, NULL, IID_IBaseFilter, (void**)&pAudioSource));
   COM_CALL(pGraph->AddFilter(pAudioSource, L"AudioSource"));

   CComPtr<IPin> pVideoCompressedOutPin(FilterTools::GetPin(pVideoSource, "Encoded"));
   CComPtr<IPin> pAudioOutPin(FilterTools::GetPin(pAudioSource, "Audio"));

   CComPtr<IBaseFilter> pSampleGrabber;
   COM_CALL(pSampleGrabber.CoCreateInstance(CLSID_SampleGrabber));
   COM_CALL(pGraph->AddFilter(pSampleGrabber, L"SampleGrabber"));
   CComPtr<IPin> pSampleGrabberInPin(FilterTools::GetPin(pSampleGrabber, "Input"));
   COM_CALL(pGraph->ConnectDirect(pVideoCompressedOutPin, pSampleGrabberInPin, NULL));    // DOES NOT FAIL!!


   CComPtr<IBaseFilter> pSampleGrabber2;
   COM_CALL(pSampleGrabber2.CoCreateInstance(CLSID_SampleGrabber));
   COM_CALL(pGraph->AddFilter(pSampleGrabber2, L"SampleGrabber2"));
   CComPtr<IPin> pSampleGrabber2InPin(FilterTools::GetPin(pSampleGrabber2, "Input"));
   COM_CALL(pGraph->ConnectDirect(pAudioOutPin, pSampleGrabber2InPin, NULL));             // DOES NOT FAIL!!

   // [Video Source]---
   //                  |-->[*Bridge Sink*]
   // [Audio Source]---


   CComPtr<IPin> pSampleGrabberOutPin(FilterTools::GetPin(pSampleGrabber, "Output"));
   CComPtr<IPin> pSampleGrabber2OutPin(FilterTools::GetPin(pSampleGrabber2, "Output"));

   CreateGraphBridge(
      IntPtr(pGraph),
      IntPtr(pSampleGrabberOutPin),
      IntPtr(pSampleGrabber2OutPin)
   );

   // Root graph to parent object
   _ppCaptureGraph.reset(new CComPtr<IGraphBuilder>(pGraph));
}

COM_CALL is my HRESULT checking macro, it will raise a managed exception if the result is other than S_OK. So the connection between pins succeeded, but here is how the graph looks disjointed when it is running:

Disjoint Graph after running

So, I have three questions:

1) What could VFW_E_NO_ALLOCATOR mean in this instance? (the source filter can be successfully connected to other components such as LAV Video decoder or ffdshow video decoder).

2) Is there a known workaround to circumvent the VFW_E_NO_ALLOCATOR problem?

3) Is it possible that a filter gets disconnected at runtime as it seems to be happening in my case?


Solution

  • I found a reference by Geraint Davies giving a reason as to why the GMFBridge sink filter may be rejecting the connection.

    It looks as though the parser is insisting on using its own allocator -- this is common with parsers where the output samples are merely pointers into the input samples. However, the bridge cannot implement suspend mode without using its own allocator, so a copy is required.

    With this information, I decided to create an ultra simple CTransformFilter filter that simply accepts the allocator offered by the bridge and copies to the output sample whatever comes in from the input sample. I am not 100% sure that what I did was right, but it is working now. I could successfully plug-in the Euresys card as part of my capture infrastructure.

    For reference, if anyone experiences something similar, here is the code of the filter I created:

    class SampleCopyGeneratorFilter : public CTransformFilter {
    protected:
       HRESULT CheckInputType(const CMediaType* mtIn) override { return S_OK; }
       HRESULT GetMediaType(int iPosition, CMediaType* pMediaType) override;
       HRESULT CheckTransform(const CMediaType *mtIn, const CMediaType *mtOut) override { return S_OK; }
       HRESULT DecideBufferSize(IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *pProp) override;
       HRESULT Transform(IMediaSample *pSource, IMediaSample *pDest) override;
    public:
       SampleCopyGeneratorFilter();
    };
    
    //--------------------------------------------------------------------------------------------------------------------
    
    SampleCopyGeneratorFilter::SampleCopyGeneratorFilter() 
       : CTransformFilter(NAME("SampleCopyGeneratorFilter"), NULL, GUID_NULL)       
    {
    
    }
    
    
    HRESULT SampleCopyGeneratorFilter::GetMediaType(int iPosition, CMediaType* pMediaType) {
       HRESULT hRes;
       ASSERT(m_pInput->IsConnected());
       if( iPosition < 0 )
          return E_INVALIDARG;
    
       CComPtr<IPin> connectedTo;
       COM_CALL(m_pInput->ConnectedTo(&connectedTo));
    
       CComPtr<IEnumMediaTypes> pMTEnumerator;
       COM_CALL(connectedTo->EnumMediaTypes(&pMTEnumerator));
       AM_MEDIA_TYPE* pIteratedMediaType;
       for( int i = 0; i <= iPosition; i++ ) {
          if( pMTEnumerator->Next(1, &pIteratedMediaType, NULL) != S_OK )
             return VFW_S_NO_MORE_ITEMS;
          if( i == iPosition )
             *pMediaType = *pIteratedMediaType;
          DeleteMediaType(pIteratedMediaType);
       }
       return S_OK;
    }
    
    
    HRESULT SampleCopyGeneratorFilter::DecideBufferSize(IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *pProp) {
       HRESULT hRes;
       AM_MEDIA_TYPE mt;
       COM_CALL(m_pInput->ConnectionMediaType(&mt));
       try {
          BITMAPINFOHEADER* pBMI = HEADER(mt.pbFormat);
          pProp->cbBuffer = DIBSIZE(*pBMI);   // format is compressed, uncompressed size should be enough
          if( !pProp->cbAlign )
             pProp->cbAlign = 1;
          pProp->cbPrefix = 0;
          pProp->cBuffers = 4;
    
          ALLOCATOR_PROPERTIES actualProperties;
          COM_CALL(pAlloc->SetProperties(pProp, &actualProperties));
          if( pProp->cbBuffer > actualProperties.cbBuffer )
             return E_FAIL;
          return S_OK;
       } finally{
          FreeMediaType(mt);
       }
    }
    
    
    HRESULT SampleCopyGeneratorFilter::Transform(IMediaSample *pSource, IMediaSample *pDest) {
       HRESULT hRes;
       BYTE* pBufferIn;
       BYTE* pBufferOut;
       long destSize = pDest->GetSize();
       long dataLen = pSource->GetActualDataLength();
       if( dataLen > destSize )
          return VFW_E_BUFFER_OVERFLOW;
       COM_CALL(pSource->GetPointer(&pBufferIn));
       COM_CALL(pDest->GetPointer(&pBufferOut));
       memcpy(pBufferOut, pBufferIn, dataLen);
       pDest->SetActualDataLength(dataLen);
       pDest->SetSyncPoint(pSource->IsSyncPoint() == S_OK);
       return S_OK;
    }
    

    Here is how I inserted the filter in the capture graph:

       CComPtr<IPin> pAACEncoderOutPin(FilterTools::GetPin(pAACEncoder, "XForm Out"));
       CComPtr<IPin> pVideoSourceCompressedOutPin(FilterTools::GetPin(pVideoSource, "Encoded"));
    
       CComPtr<IBaseFilter> pSampleCopier;
       pSampleCopier = new SampleCopyGeneratorFilter();
       COM_CALL(pGraph->AddFilter(pSampleCopier, L"SampleCopier"));
       CComPtr<IPin> pSampleCopierInPin(FilterTools::GetPin(pSampleCopier, "XForm In"));
       COM_CALL(pGraph->ConnectDirect(pVideoSourceCompressedOutPin, pSampleCopierInPin, NULL));
       CComPtr<IPin> pSampleCopierOutPin(FilterTools::GetPin(pSampleCopier, "XForm Out"));
    
    
       CreateGraphBridge(
          IntPtr(pGraph),
          IntPtr(pSampleCopierOutPin),
          IntPtr(pAACEncoderOutPin)
       );
    

    Now, I still have no idea why inserting the sample grabber instead did not work and resulted in detached graphs. I corroborated this quirk by examining the graphs with Graphedit Plus too. If anyone can offer me an explanation, I would be very grateful indeed.