//------------------------------------------------------------------------------
// 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(__in_opt LPCTSTR pName, __inout_opt LPUNKNOWN lpunk, CLSID clsid)
    : CBaseFilter(pName, lpunk, &m_cStateLock, clsid),
      m_iPins(0),
      m_paStreams(NULL)
{
}
 
CSource::CSource(__in_opt LPCTSTR pName, __inout_opt LPUNKNOWN lpunk, CLSID clsid, __inout HRESULT *phr)
    : CBaseFilter(pName, lpunk, &m_cStateLock, clsid),
      m_iPins(0),
      m_paStreams(NULL)
{
    UNREFERENCED_PARAMETER(phr);
}
 
#ifdef UNICODE
CSource::CSource(__in_opt LPCSTR pName, __inout_opt LPUNKNOWN lpunk, CLSID clsid)
    : CBaseFilter(pName, lpunk, &m_cStateLock, clsid),
      m_iPins(0),
      m_paStreams(NULL)
{
}
 
CSource::CSource(__in_opt LPCSTR pName, __inout_opt LPUNKNOWN lpunk, CLSID clsid, __inout 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(__in 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(__in 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, __deref_out 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(__in 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(__deref_out 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(sizeof(WCHAR) * 12);
    if (*Id==NULL) {
       return E_OUTOFMEMORY;
    }
    IntToWstr(i, *Id);
    return NOERROR;
}
 
 
 
//
// CSourceStream::Constructor
//
// increments the number of pins present on the filter
CSourceStream::CSourceStream(
    __in_opt LPCTSTR pObjectName,
    __inout HRESULT *phr,
    __inout CSource *ps,
    __in_opt LPCWSTR pPinName)
    : CBaseOutputPin(pObjectName, ps, ps->pStateLock(), phr, pPinName),
      m_pFilter(ps) {
 
     *phr = m_pFilter->AddPin(this);
}
 
#ifdef UNICODE
CSourceStream::CSourceStream(
    __in_opt LPCSTR pObjectName,
    __inout HRESULT *phr,
    __inout CSource *ps,
    __in_opt 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, __inout 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;
}
 

V1037 Two or more case-branches perform the same actions. Check lines: 420, 433

V668 There is no sense in testing the 'paStreams' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error.

V776 Potentially infinite loop. The variable in the loop exit condition 'm_iPins != 0' does not change its value between iterations.