| //------------------------------------------------------------------------------ | |
| // 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; | |
| } | |