//------------------------------------------------------------------------------

// File: Source.cpp

//

// Desc: DirectShow  base classes - implements CSource, which is a Quartz

//       source filter 'template.'

//

// Copyright (c) 1992-2001 Microsoft Corporation.  All rights reserved.

//------------------------------------------------------------------------------





// Locking Strategy.

//

// Hold the filter critical section (m_pFilter->pStateLock()) to serialise

// access to functions. Note that, in general, this lock may be held

// by a function when the worker thread may want to hold it. Therefore

// if you wish to access shared state from the worker thread you will

// need to add another critical section object. The execption is during

// the threads processing loop, when it is safe to get the filter critical

// section from within FillBuffer().



#include "streams.h"





//

// CSource::Constructor

//

// Initialise the pin count for the filter. The user will create the pins in

// the derived class.

CSource::CSource(TCHAR *pName, LPUNKNOWN lpunk, CLSID clsid)

    : CBaseFilter(pName, lpunk, &m_cStateLock, clsid),

      m_iPins(0),

      m_paStreams(NULL) {

}



CSource::CSource(TCHAR *pName, LPUNKNOWN lpunk, CLSID clsid, HRESULT *phr)

    : CBaseFilter(pName, lpunk, &m_cStateLock, clsid),

      m_iPins(0),

      m_paStreams(NULL) {

    UNREFERENCED_PARAMETER(phr);

}



#ifdef UNICODE

CSource::CSource(CHAR *pName, LPUNKNOWN lpunk, CLSID clsid)

    : CBaseFilter(pName, lpunk, &m_cStateLock, clsid),

      m_iPins(0),

      m_paStreams(NULL) {

}



CSource::CSource(CHAR *pName, LPUNKNOWN lpunk, CLSID clsid, HRESULT *phr)

    : CBaseFilter(pName, lpunk, &m_cStateLock, clsid),

      m_iPins(0),

      m_paStreams(NULL) {

    UNREFERENCED_PARAMETER(phr);

}

#endif



//

// CSource::Destructor

//

CSource::~CSource() {

    /*  Free our pins and pin array */

    while(m_iPins != 0) {

        // deleting the pins causes them to be removed from the array...

        delete m_paStreams[m_iPins - 1];

    }



    ASSERT(m_paStreams == NULL);

}





//

//  Add a new pin

//

HRESULT CSource::AddPin(CSourceStream *pStream) {

    CAutoLock lock(&m_cStateLock);



    /*  Allocate space for this pin and the old ones */

    CSourceStream **paStreams = new CSourceStream *[m_iPins + 1];

    if(paStreams == NULL) {

        return E_OUTOFMEMORY;

    }

    if(m_paStreams != NULL) {

        CopyMemory((PVOID)paStreams, (PVOID)m_paStreams,

            m_iPins * sizeof(m_paStreams[0]));

        paStreams[m_iPins] = pStream;

        delete [] m_paStreams;

    }

    m_paStreams = paStreams;

    m_paStreams[m_iPins] = pStream;

    m_iPins++;

    return S_OK;

}



//

//  Remove a pin - pStream is NOT deleted

//

HRESULT CSource::RemovePin(CSourceStream *pStream) {

    int i;

    for(i = 0; i < m_iPins; i++) {

        if(m_paStreams[i] == pStream) {

            if(m_iPins == 1) {

                delete [] m_paStreams;

                m_paStreams = NULL;

            }

            else {

                /*  no need to reallocate */

                while(++i < m_iPins)

                    m_paStreams[i - 1] = m_paStreams[i];

            }

            m_iPins--;

            return S_OK;

        }

    }

    return S_FALSE;

}



//

// FindPin

//

// Set *ppPin to the IPin* that has the id Id.

// or to NULL if the Id cannot be matched.

STDMETHODIMP CSource::FindPin(LPCWSTR Id, IPin **ppPin) {

    CheckPointer(ppPin,E_POINTER);

    ValidateReadWritePtr(ppPin,sizeof(IPin *));

    // The -1 undoes the +1 in QueryId and ensures that totally invalid

    // strings (for which WstrToInt delivers 0) give a deliver a NULL pin.

    int i = WstrToInt(Id) -1;

    *ppPin = GetPin(i);

    if(*ppPin!=NULL) {

        (*ppPin)->AddRef();

        return NOERROR;

    }

    else {

        return VFW_E_NOT_FOUND;

    }

}



//

// FindPinNumber

//

// return the number of the pin with this IPin* or -1 if none

int CSource::FindPinNumber(IPin *iPin) {

    int i;

    for(i=0; i<m_iPins; ++i) {

        if((IPin *)(m_paStreams[i])==iPin) {

            return i;

        }

    }

    return -1;

}



//

// GetPinCount

//

// Returns the number of pins this filter has

int CSource::GetPinCount(void) {



    CAutoLock lock(&m_cStateLock);

    return m_iPins;

}





//

// GetPin

//

// Return a non-addref'd pointer to pin n

// needed by CBaseFilter

CBasePin *CSource::GetPin(int n) {



    CAutoLock lock(&m_cStateLock);



    // n must be in the range 0..m_iPins-1

    // if m_iPins>n  && n>=0 it follows that m_iPins>0

    // which is what used to be checked (i.e. checking that we have a pin)

    if((n >= 0) && (n < m_iPins)) {



        ASSERT(m_paStreams[n]);

        return m_paStreams[n];

    }

    return NULL;

}





//





// *

// * --- CSourceStream ----

// *



//

// Set Id to point to a CoTaskMemAlloc'd

STDMETHODIMP CSourceStream::QueryId(LPWSTR *Id) {

    CheckPointer(Id,E_POINTER);

    ValidateReadWritePtr(Id,sizeof(LPWSTR));



    // We give the pins id's which are 1,2,...

    // FindPinNumber returns -1 for an invalid pin

    int i = 1+ m_pFilter->FindPinNumber(this);

    if(i<1) return VFW_E_NOT_FOUND;

    *Id = (LPWSTR)CoTaskMemAlloc(8);

    if(*Id==NULL) {

        return E_OUTOFMEMORY;

    }

    IntToWstr(i, *Id);

    return NOERROR;

}







//

// CSourceStream::Constructor

//

// increments the number of pins present on the filter

CSourceStream::CSourceStream(

    TCHAR *pObjectName,

    HRESULT *phr,

    CSource *ps,

    LPCWSTR pPinName)

    : CBaseOutputPin(pObjectName, ps, ps->pStateLock(), phr, pPinName),

      m_pFilter(ps) {



    *phr = m_pFilter->AddPin(this);

}



#ifdef UNICODE

CSourceStream::CSourceStream(

    char *pObjectName,

    HRESULT *phr,

    CSource *ps,

    LPCWSTR pPinName)

    : CBaseOutputPin(pObjectName, ps, ps->pStateLock(), phr, pPinName),

      m_pFilter(ps) {



    *phr = m_pFilter->AddPin(this);

}

#endif

//

// CSourceStream::Destructor

//

// Decrements the number of pins on this filter

CSourceStream::~CSourceStream(void) {



    m_pFilter->RemovePin(this);

}





//

// CheckMediaType

//

// Do we support this type? Provides the default support for 1 type.

HRESULT CSourceStream::CheckMediaType(const CMediaType *pMediaType) {



    CAutoLock lock(m_pFilter->pStateLock());



    CMediaType mt;

    GetMediaType(&mt);



    if(mt == *pMediaType) {

        return NOERROR;

    }



    return E_FAIL;

}





//

// GetMediaType/3

//

// By default we support only one type

// iPosition indexes are 0-n

HRESULT CSourceStream::GetMediaType(int iPosition, CMediaType *pMediaType) {



    CAutoLock lock(m_pFilter->pStateLock());



    if(iPosition<0) {

        return E_INVALIDARG;

    }

    if(iPosition>0) {

        return VFW_S_NO_MORE_ITEMS;

    }

    return GetMediaType(pMediaType);

}





//

// Active

//

// The pin is active - start up the worker thread

HRESULT CSourceStream::Active(void) {



    CAutoLock lock(m_pFilter->pStateLock());



    HRESULT hr;



    if(m_pFilter->IsActive()) {

        return S_FALSE; // succeeded, but did not allocate resources (they already exist...)

    }



    // do nothing if not connected - its ok not to connect to

    // all pins of a source filter

    if(!IsConnected()) {

        return NOERROR;

    }



    hr = CBaseOutputPin::Active();

    if(FAILED(hr)) {

        return hr;

    }



    ASSERT(!ThreadExists());



    // start the thread

    if(!Create()) {

        return E_FAIL;

    }



    // Tell thread to initialize. If OnThreadCreate Fails, so does this.

    hr = Init();

    if(FAILED(hr))

        return hr;



    return Pause();

}





//

// Inactive

//

// Pin is inactive - shut down the worker thread

// Waits for the worker to exit before returning.

HRESULT CSourceStream::Inactive(void) {



    CAutoLock lock(m_pFilter->pStateLock());



    HRESULT hr;



    // do nothing if not connected - its ok not to connect to

    // all pins of a source filter

    if(!IsConnected()) {

        return NOERROR;

    }



    // !!! need to do this before trying to stop the thread, because

    // we may be stuck waiting for our own allocator!!!



    hr = CBaseOutputPin::Inactive();  // call this first to Decommit the allocator

    if(FAILED(hr)) {

        return hr;

    }



    if(ThreadExists()) {

        hr = Stop();



        if(FAILED(hr)) {

            return hr;

        }



        hr = Exit();

        if(FAILED(hr)) {

            return hr;

        }



        Close();    // Wait for the thread to exit, then tidy up.

    }



    // hr = CBaseOutputPin::Inactive();  // call this first to Decommit the allocator

    //if (FAILED(hr)) {

    //  return hr;

    //}



    return NOERROR;

}





//

// ThreadProc

//

// When this returns the thread exits

// Return codes > 0 indicate an error occured

DWORD CSourceStream::ThreadProc(void) {



    HRESULT hr;  // the return code from calls

    Command com;



    do {

        com = GetRequest();

        if(com != CMD_INIT) {

            DbgLog((LOG_ERROR, 1, TEXT("Thread expected init command")));

            Reply((DWORD) E_UNEXPECTED);

        }

    } while(com != CMD_INIT);



    DbgLog((LOG_TRACE, 1, TEXT("CSourceStream worker thread initializing")));



    hr = OnThreadCreate(); // perform set up tasks

    if(FAILED(hr)) {

        DbgLog((LOG_ERROR, 1, TEXT("CSourceStream::OnThreadCreate failed. Aborting thread.")));

        OnThreadDestroy();

        Reply(hr);  // send failed return code from OnThreadCreate

        return 1;

    }



    // Initialisation suceeded

    Reply(NOERROR);



    Command cmd;

    do {

        cmd = GetRequest();



        switch(cmd) {



            case CMD_EXIT:

                Reply(NOERROR);

                break;



            case CMD_RUN:

                DbgLog((LOG_ERROR, 1, TEXT("CMD_RUN received before a CMD_PAUSE???")));

                // !!! fall through???



            case CMD_PAUSE:

                Reply(NOERROR);

                DoBufferProcessingLoop();

                break;



            case CMD_STOP:

                Reply(NOERROR);

                break;



            default:

                DbgLog((LOG_ERROR, 1, TEXT("Unknown command %d received!"), cmd));

                Reply((DWORD) E_NOTIMPL);

                break;

        }

    } while(cmd != CMD_EXIT);



    hr = OnThreadDestroy(); // tidy up.

    if(FAILED(hr)) {

        DbgLog((LOG_ERROR, 1, TEXT("CSourceStream::OnThreadDestroy failed. Exiting thread.")));

        return 1;

    }



    DbgLog((LOG_TRACE, 1, TEXT("CSourceStream worker thread exiting")));

    return 0;

}





//

// DoBufferProcessingLoop

//

// Grabs a buffer and calls the users processing function.

// Overridable, so that different delivery styles can be catered for.

HRESULT CSourceStream::DoBufferProcessingLoop(void) {



    Command com;



    OnThreadStartPlay();



    do {

        while(!CheckRequest(&com)) {



            IMediaSample *pSample;



            HRESULT hr = GetDeliveryBuffer(&pSample,NULL,NULL,0);

            if(FAILED(hr)) {

                Sleep(1);

                continue;   // go round again. Perhaps the error will go away

                // or the allocator is decommited & we will be asked to

                // exit soon.

            }



            // Virtual function user will override.

            hr = FillBuffer(pSample);



            if(hr == S_OK) {

                hr = Deliver(pSample);

                pSample->Release();



                // downstream filter returns S_FALSE if it wants us to

                // stop or an error if it's reporting an error.

                if(hr != S_OK) {

                    DbgLog((LOG_TRACE, 2, TEXT("Deliver() returned %08x; stopping"), hr));

                    return S_OK;

                }



            }

            else if(hr == S_FALSE) {

                // derived class wants us to stop pushing data

                pSample->Release();

                DeliverEndOfStream();

                return S_OK;

            }

            else {

                // derived class encountered an error

                pSample->Release();

                DbgLog((LOG_ERROR, 1, TEXT("Error %08lX from FillBuffer!!!"), hr));

                DeliverEndOfStream();

                m_pFilter->NotifyEvent(EC_ERRORABORT, hr, 0);

                return hr;

            }



            // all paths release the sample

        }



        // For all commands sent to us there must be a Reply call!



        if(com == CMD_RUN || com == CMD_PAUSE) {

            Reply(NOERROR);

        }

        else if(com != CMD_STOP) {

            Reply((DWORD) E_UNEXPECTED);

            DbgLog((LOG_ERROR, 1, TEXT("Unexpected command!!!")));

        }

    } while(com != CMD_STOP);



    return S_FALSE;

}





