/************************************************************************************************/
/** @file FrameGrabber.cpp 
 * Description: Video frame grabber sink DirectShow component.
 *             This is a replacement for Microsoft's retired COM component:
 *             https://learn.microsoft.com/en-us/windows/win32/directshow/sample-grabber-filter
 * Dependency: baseclasses                                                                     *
 *                 Copyright 2024-2025 Jaroslav Fojtik                                         *
 ***********************************************************************************************/
#include <Windows.h>

#ifdef _DEBUG
 #include <stdio.h>
 #ifdef __WXDEBUG__		// Workaround clash with WxWidgets.
  #undef __WXDEBUG__
 #endif
 #include <wxdebug.h>
#endif

#include <streams.h>
#include <strmif.h>

#include "FrameGrabber.h"

#include <initguid.h>		// Ensure GUID instantionalise.
#include <Dvdmedia.h>		// For VIDEOINFOHEADER2


/// Component's unique GUID {9BB2C3C0-1228-4990-B326-AE9E9F8ECA78}
DEFINE_GUID(CAM_VIEW_FG, 0x9bb2c3c0, 0x1228, 0x4990, 0xb3, 0x26, 0xae, 0x9e, 0x9f, 0x8e, 0xca, 0x78);


class CGrabberInputPin: public CRendererInputPin
{
public:
    CGrabberInputPin(__inout CBaseRenderer *pRenderer, __inout HRESULT *phr,__in_opt LPCWSTR Name);

    virtual HRESULT SetMediaType(const CMediaType *pmt);
    // (passive) accept a connection from another pin
    virtual STDMETHODIMP ReceiveConnection(IPin * pConnector, const AM_MEDIA_TYPE *pmt);

#ifdef _DEBUG
    virtual STDMETHODIMP BeginFlush(void);
    virtual HRESULT BreakConnect(void);
    virtual HRESULT CheckStreaming(void);
    virtual STDMETHODIMP EndFlush(void);
    virtual STDMETHODIMP GetAllocator(__deref_out IMemAllocator **ppAllocator);
    virtual STDMETHODIMP GetAllocatorRequirements(__out ALLOCATOR_PROPERTIES*pProps);
    virtual HRESULT Inactive(void);
    virtual STDMETHODIMP Notify(IBaseFilter *pSender, Quality q);
    virtual STDMETHODIMP NotifyAllocator(IMemAllocator * pAllocator, BOOL bReadOnly);
    virtual STDMETHODIMP PassNotify(Quality& q);
    virtual STDMETHODIMP ReceiveCanBlock(void);
#endif
};


#ifdef _DEBUG
HRESULT CGrabberInputPin::BeginFlush(void)
{
  OutputDebugString("CAMVIEW CGrabberInputPin::BeginFlush\n");
  return CRendererInputPin::BeginFlush();
}

HRESULT CGrabberInputPin::BreakConnect(void)
{
  OutputDebugString("CAMVIEW CGrabberInputPin::BreakConnect\n");
  return CRendererInputPin::BreakConnect();
}

HRESULT CGrabberInputPin::CheckStreaming(void)
{
  OutputDebugString("CAMVIEW CGrabberInputPin::CheckStreaming\n");
  return CRendererInputPin::CheckStreaming();
}

HRESULT CGrabberInputPin::EndFlush(void)
{
  OutputDebugString("CAMVIEW CGrabberInputPin::EndFlush\n");
  return CRendererInputPin::EndFlush();
}

HRESULT CGrabberInputPin::GetAllocator(IMemAllocator **ppAllocator)
{
  OutputDebugString("CAMVIEW CGrabberInputPin::GetAllocator\n");
  return CRendererInputPin::GetAllocator(ppAllocator);
}

HRESULT CGrabberInputPin::GetAllocatorRequirements(__out ALLOCATOR_PROPERTIES *pProps)
{
  OutputDebugString("CAMVIEW CGrabberInputPin::GetAllocatorRequirements\n");
  return CRendererInputPin::GetAllocatorRequirements(pProps);
}

HRESULT CGrabberInputPin::Inactive(void)
{
  OutputDebugString("CAMVIEW CGrabberInputPin::Inactive\n");
  return CRendererInputPin::Inactive();
}

HRESULT CGrabberInputPin::Notify(IBaseFilter *pSender, Quality q)
{
  OutputDebugString("CAMVIEW CGrabberInputPin::Notify\n");
  return CRendererInputPin::Notify(pSender,q);
}

HRESULT CGrabberInputPin::NotifyAllocator(IMemAllocator * pAllocator, BOOL bReadOnly)
{
  OutputDebugString("CAMVIEW CGrabberInputPin::NotifyAllocator\n");
  return CRendererInputPin::NotifyAllocator(pAllocator,bReadOnly);
}

HRESULT CGrabberInputPin::PassNotify(Quality& q)
{
  OutputDebugString("CAMVIEW CGrabberInputPin::PassNotify\n");
  return CRendererInputPin::PassNotify(q);
}

HRESULT CGrabberInputPin::ReceiveCanBlock(void)
{
  OutputDebugString("CAMVIEW CGrabberInputPin::ReceiveCanBlock\n");
  return CRendererInputPin::ReceiveCanBlock();
}
#endif


HRESULT CGrabberInputPin::SetMediaType(const CMediaType *pmt)
{
  OutputDebugString("CAMVIEW CGrabberInputPin::SetMediaType\n");
  return ((CFrameGrabber*)(m_pRenderer))->SetMediaType(pmt);
}


HRESULT CGrabberInputPin::ReceiveConnection(IPin * pConnector, const AM_MEDIA_TYPE *pmt)
{
  OutputDebugString("CAMVIEW CGrabberInputPin::ReceiveConnection\n");
  if(!m_Connected || m_Connected!=pConnector)
	return CRendererInputPin::ReceiveConnection(pConnector,pmt);
  return ((CFrameGrabber*)(m_pRenderer))->ReceiveConnection(pConnector,pmt);
}


CGrabberInputPin::CGrabberInputPin(CBaseRenderer *pRenderer, HRESULT *phr, LPCWSTR Name):
    CRendererInputPin(pRenderer,phr,Name)
{
}



CFrameGrabber::CFrameGrabber(__inout_opt LPUNKNOWN pUnk, __inout HRESULT *phr):
    CBaseRenderer(CAM_VIEW_FG, // CLSID for this renderer
                  "CamView SampleGrabber",         // Debug ONLY description
                  pUnk,       // Aggregated owner object
                  phr),        // General OLE return code
    m_Callback(NULL),
    BufferSamples(false),
    m_MediaSample(NULL),
    ForceMediaType(false)
{  
}


CFrameGrabber::~CFrameGrabber()
{
 BufferSamples = false;
 ClearImediaSample();
}


ISampleGrabber *CreateInternalFrameGrabber(IUnknown *pUnkOuter)
{
  HRESULT hr = S_OK;
  return new CFrameGrabber(pUnkOuter,&hr);
}


CBasePin *CFrameGrabber::GetPin(int n)
{
  OutputDebugString("CAMVIEW CFrameGrabber::GetPin\n");
  if(n==0)
  {
    CAutoLock cObjectCreationLock(&m_ObjectCreationLock);
    if(m_pInputPin==NULL)
    {
      HRESULT hr = NOERROR;
      m_pInputPin = new CGrabberInputPin(this,&hr,L"In");
      if(FAILED(hr) && m_pInputPin!=NULL)
      {
        delete m_pInputPin;
        m_pInputPin = NULL;
      }
    }
    if(m_pInputPin!=NULL)
    {
      return m_pInputPin;
    }
  }
  return CBaseRenderer::GetPin(n);
}


void CFrameGrabber::ClearImediaSample(void)
{
  IMediaSample * const tmpMediaSample = (IMediaSample*)InterlockedExchangePointer((PVOID*)&m_MediaSample,NULL);  
  if(tmpMediaSample) tmpMediaSample->Release();		// Now release a local copy.  
}


HRESULT CFrameGrabber::NonDelegatingQueryInterface(REFIID riid, __deref_out void **ppv)
{
  OutputDebugString("CAMVIEW CFrameGrabber::NonDelegatingQueryInterface\n");
  if(riid == IID_ISampleGrabber)
      return GetInterface((ISampleGrabber*)this, ppv);
  return CBaseRenderer::NonDelegatingQueryInterface(riid,ppv);
}


HRESULT CFrameGrabber::SetMediaType(const CMediaType *pmt)
{
  OutputDebugString("CAMVIEW CFrameGrabber::SetMediaType\n");
  m_Type = *pmt;
  return S_OK;
}


HRESULT CFrameGrabber::ReceiveConnection(IPin * pConnector, const AM_MEDIA_TYPE *pmt)
{
  OutputDebugString("CAMVIEW CFrameGrabber::ReceiveConnection\n");
  if(pConnector==NULL || pmt==NULL) return E_POINTER;

  if(pmt->majortype == *m_Type.Type() &&
     pmt->subtype == *m_Type.Subtype())
  {
    const BITMAPINFOHEADER *pBmiNew = NULL;
    const BITMAPINFOHEADER *pBmiOrig = NULL;

    if(pmt->formattype == FORMAT_VideoInfo)
    {
      const VIDEOINFOHEADER *pVIH = (VIDEOINFOHEADER*)pmt->pbFormat;
      if(pVIH) pBmiNew = &pVIH->bmiHeader;
    }
    else if(pmt->formattype == FORMAT_VideoInfo2)
    {
      const VIDEOINFOHEADER2 *pVIH2 = (VIDEOINFOHEADER2*)pmt->pbFormat;
      if(pVIH2) pBmiNew = &pVIH2->bmiHeader;
    }

    if(*m_Type.FormatType() == FORMAT_VideoInfo)
    {
       const VIDEOINFOHEADER *pVIH = (VIDEOINFOHEADER*)m_Type.pbFormat;
       if(pVIH) pBmiOrig = &pVIH->bmiHeader;
    }
    else if(*m_Type.FormatType() == FORMAT_VideoInfo2)
    {
      const VIDEOINFOHEADER2 *pVIH2 = (VIDEOINFOHEADER2*)m_Type.pbFormat;
      if(pVIH2) pBmiOrig = &pVIH2->bmiHeader;
    }

    if(pBmiNew!=NULL && pBmiOrig!=NULL)
    {
      if(labs(pBmiNew->biWidth)!=0 && labs(pBmiNew->biHeight)!=0)
      {
        m_Type = *pmt;
        if(m_Callback!=NULL)
            return m_Callback->SampleCB(-1,NULL);
        else
            return S_OK;
      }
    } 
  }
  return E_FAIL;
}


HRESULT CFrameGrabber::CheckMediaType(const CMediaType *pmt)
{  
  if(pmt==NULL)
  {
    OutputDebugString("CAMVIEW CFrameGrabber::CheckMediaType E_POINTER\n");
    return E_POINTER;
  }

  if(ForceMediaType)
  {
    if(*pmt->Subtype() != *m_Type.Subtype()) return S_FALSE;
  }

  if(GetRealState()==State_Running)
  {
    if(*pmt->Type() == *m_Type.Type() &&		// Colorspace change is not supported yet.
       *pmt->Subtype() == *m_Type.Subtype())
    {
      const BITMAPINFOHEADER *pBmiNew = NULL;
      const BITMAPINFOHEADER *pBmiOrig = NULL;

      if(*pmt->FormatType() == FORMAT_VideoInfo)
      {
         const VIDEOINFOHEADER *pVIH = (VIDEOINFOHEADER*)pmt->pbFormat;
         if(pVIH) pBmiNew = &pVIH->bmiHeader;
      }
      else if(*pmt->FormatType() == FORMAT_VideoInfo2)
      {
        const VIDEOINFOHEADER2 *pVIH2 = (VIDEOINFOHEADER2*)pmt->pbFormat;
        if(pVIH2) pBmiNew = &pVIH2->bmiHeader;
      }

      if(*m_Type.FormatType() == FORMAT_VideoInfo)
      {
         const VIDEOINFOHEADER *pVIH = (VIDEOINFOHEADER*)m_Type.pbFormat;
         if(pVIH) pBmiOrig = &pVIH->bmiHeader;
      }
      else if(*m_Type.FormatType() == FORMAT_VideoInfo2)
      {
        const VIDEOINFOHEADER2 *pVIH2 = (VIDEOINFOHEADER2*)m_Type.pbFormat;
        if(pVIH2) pBmiOrig = &pVIH2->bmiHeader;
      }

      if(pBmiNew!=NULL && pBmiNew!=NULL)
      {
        if(labs(pBmiNew->biWidth)!=0 && labs(pBmiNew->biHeight)!=0)
        {
#ifdef _DEBUG
	  char buffer[200];
	  sprintf(buffer,"CAMVIEW CFrameGrabber::CheckMediaType Reconnection (%d,%d) S_OK\n",pBmiNew->biWidth,pBmiNew->biHeight);
          OutputDebugString(buffer);
#endif
          return S_OK;
        }
      } 
    }
    
    OutputDebugString("CAMVIEW CFrameGrabber::CheckMediaType Reconnection fails\n");
    return E_FAIL;	// Disable dynamic format change.
  // CBasePin::ReceiveConnection does not implement dynamic reconnection, this is just a safe workaround.
  }

  if(*pmt->Type()!=*m_Type.Type())
  {
    if((*m_Type.Type()!=GUID_NULL) && (*pmt->Type()!=MEDIATYPE_Video)) return S_FALSE;
  }
  if(*pmt->FormatType()!=FORMAT_VideoInfo && *pmt->FormatType()!=FORMAT_VideoInfo2)
  {
    OutputDebugString("CAMVIEW CFrameGrabber::CheckMediaType Not accepted S_FALSE\n");
    return S_FALSE;
  }

  OutputDebugString("CAMVIEW CFrameGrabber::CheckMediaType S_OK\n");
  return S_OK;
}


HRESULT CFrameGrabber::DoRenderSample(IMediaSample *pMediaSample)
{
	// Please note that pMediaSample has at least one referrence kept from caller.
  OutputDebugString("CAMVIEW CFrameGrabber::DoRenderSample\n");
  if(BufferSamples && pMediaSample!=NULL)
  {
    pMediaSample->AddRef();			// Add referrence before attachment to m_MediaSample.
    {
      IMediaSample * const pMediaSample2 = (IMediaSample*)InterlockedExchangePointer((PVOID*)&m_MediaSample,pMediaSample);
      if(pMediaSample2) pMediaSample2->Release();		// Original value m_MediaSample needs release.
    }
    double MediaTime = 0;
    LONGLONG TimeStart;
    LONGLONG TimeEnd;
    if(SUCCEEDED(pMediaSample->GetMediaTime(&TimeStart,&TimeEnd)))
    {
      MediaTime = TimeStart / 10000000.0;
    }
    return m_Callback->SampleCB(MediaTime,pMediaSample);
  }
  else
  {	// Original pMediaSample is useless here, throw it away.
    pMediaSample = (IMediaSample*)InterlockedExchangePointer((PVOID*)&m_MediaSample,NULL);
    if(pMediaSample) pMediaSample->Release();
  }

  return S_OK;
}


HRESULT CFrameGrabber::Stop(void)
{
OutputDebugString("CAMVIEW CFrameGrabber::Stop\n");
HRESULT hr = CBaseRenderer::Stop();
  ClearImediaSample();
  return hr;
}


HRESULT CFrameGrabber::Run(REFERENCE_TIME tStart)
{
OutputDebugString("CAMVIEW CFrameGrabber::Start\n");
  ClearImediaSample();  
  return CBaseRenderer::Run(tStart);
}


HRESULT CFrameGrabber::BeginFlush(void)
{
  ClearImediaSample();  
  return CBaseRenderer::BeginFlush();
}


//////////////////////////////////////////////////////////////////


HRESULT CFrameGrabber::GetConnectedMediaType(AM_MEDIA_TYPE *pType)
{
OutputDebugString("CAMVIEW CFrameGrabber::GetConnectedMediaType\n");
  if(pType==NULL) return E_POINTER;
  memcpy(pType, (AM_MEDIA_TYPE*)&m_Type, sizeof(AM_MEDIA_TYPE));
  if(pType->pbFormat != NULL)
  {
    pType->pbFormat = (BYTE*)CoTaskMemAlloc(pType->cbFormat);
    if(pType->pbFormat != NULL)
    {
      memcpy(pType->pbFormat, m_Type.pbFormat, pType->cbFormat);
    }
    else
        pType->cbFormat = 0;
  }
  //m_Type.SetFormat((BYTE*)pType,sizeof(AM_MEDIA_TYPE));
  return S_OK;
}


HRESULT CFrameGrabber::GetCurrentBuffer(long *pBufferSize, long *pBuffer)
{
OutputDebugString("CAMVIEW CFrameGrabber::GetCurrentBuffer\n");
  if(pBufferSize) pBufferSize=0;
  return E_NOTIMPL;
}


HRESULT CFrameGrabber::GetCurrentSample(IMediaSample **ppSample)
{
OutputDebugString("CAMVIEW CFrameGrabber::GetCurrentSample\n");
  if(ppSample==NULL) return E_POINTER;
  *ppSample = (IMediaSample*)InterlockedExchangePointer((PVOID*)&m_MediaSample,NULL);
  return S_OK;			 		// m_MediaSample should have 1 referrence from here.
}


HRESULT CFrameGrabber::SetBufferSamples(BOOL BufferThem)
{
OutputDebugString("CAMVIEW CFrameGrabber::SetBufferSamples\n");
  BufferSamples = BufferThem; 
  if(m_MediaSample && !BufferThem) 
  {
    ClearImediaSample();
  } 
  return S_OK;
}


HRESULT CFrameGrabber::SetCallback(ISampleGrabberCB *pCallback, long WhichMethodToCallback)
{
OutputDebugString("CAMVIEW CFrameGrabber::SetCallback\n");
  m_Callback = pCallback;
  return S_OK;
}


HRESULT CFrameGrabber::SetMediaType(const AM_MEDIA_TYPE *pType)
{
OutputDebugString("CAMVIEW CFrameGrabber::SetMediaType\n");
  ForceMediaType = false;
  if(pType==NULL)
      m_Type.ResetFormatBuffer();
  else
  {
    m_Type = *pType;
    if(pType->subtype != GUID_NULL) ForceMediaType=true;
  }
  return S_OK;
}


HRESULT CFrameGrabber::SetOneShot(BOOL OneShot)
{
OutputDebugString("CAMVIEW CFrameGrabber::SetOneShot\n");
  return E_NOTIMPL;
}



