//------------------------------------------------------------------------------ // File: DMOBase.h // // Desc: A collection of DMO base classes. // // Copyright (c) 1999-2001 Microsoft Corporation. All rights reserved. //------------------------------------------------------------------------------ // Current hierarchy: // // IMediaObject // | // +-- C1in1outDMO - generic base class for DMOs with 1 in and 1 out // | | // | +-- FBRDMO - base class for fixed sample size, fixed bitrate DMOs // | | | // | | +-- CPCMDMO - base class for PCM audio DMOs // | | // | +-- C1for1DMO - base class for single sample per buffer 1-in/1-out DMOs // | | // | +-- C1for1QCDMO - adds IDMOQualityControl to C1for1DMO // | // +-- CGenericDMO - resonably generic base class for multi-input/output DMOs // #ifndef __DMOBASE_H_ #define __DMOBASE_H_ #include "dmo.h" #include "assert.h" #include "math.h" // // locking helper class // #ifdef DMO_NOATL class CDMOAutoLock { public: CDMOAutoLock(CRITICAL_SECTION* pcs) : m_pcs(pcs) { EnterCriticalSection(m_pcs); } ~CDMOAutoLock() { LeaveCriticalSection(m_pcs); } private: CRITICAL_SECTION* m_pcs; }; #else class CDMOAutoLock { public: CDMOAutoLock(CComAutoCriticalSection* pcs) : m_pcs(pcs) { m_pcs->Lock(); } ~CDMOAutoLock() { m_pcs->Unlock(); } private: CComAutoCriticalSection* m_pcs; }; #endif // // C1in1outDMO - generic base class for 1-input/1-output DMOs. // // // // C1in1outDMO implements all IMediaObject methods. The derived class // customizes the DMO's behavior by overriding some or all of the following // virtual functions: // // Main Streaming: // AcceptInput // accept one new input buffer // ProduceOutput // fill up one output buffer with new data // AcceptingInput // check if DMO is ready for new input // Other streaming: // PrepareForStreaming // hook called after both types have been set // Discontinuity // notify DMO of a discontinuity // DoFlush // discard all data and start anew // Mediatype negotiation: // GetInputType // input type enumerator // GetOutputType // output type enumerator // CheckInputType // verifies proposed input type is acceptable // CheckOutputType // verifies proposed output type is acceptable // Buffer size negotiation: // GetInputFlags // input data flow flags // GetOutputFlags // output fata flow flags // GetInputSizeInfo // input buffer size requirements // GetOutputSizeInfo // output buffer size requirements // // This base class assumes that the derived class will not override any // IMediaObject methods directly - the derived class should override the // methods listed above instead. // // // // The base class provides a default implementation for each of the // overridables listed above. However, to make a useful DMO the derived class // probably needs to override at least the following two methods: // // HRESULT AcceptingInput(); // HRESULT AcceptInput(BYTE* pData, // ULONG ulSize, // DWORD dwFlags, // REFERENCE_TIME rtTimestamp, // REFERENCE_TIME rtTimelength, // IMediaBuffer* pMediaBuffer); // HRESULT ProduceOutput(BYTE *pData, // ULONG ulAvail, // ULONG* pulUsed, // DWORD* pdwStatus, // REFERENCE_TIME *prtTimestamp, // REFERENCE_TIME *prtTimelength); // // All good DMOs should also override these (the default implementation // simply accepts any mediatype, which in general is not good DMO behavior): // // HRESULT GetInputType(ULONG ulTypeIndex, DMO_MEDIA_TYPE *pmt); // HRESULT GetOutputType(ULONG ulTypeIndex, DMO_MEDIA_TYPE *pmt); // HRESULT CheckInputType(const DMO_MEDIA_TYPE *pmt); // HRESULT CheckOutputType(const DMO_MEDIA_TYPE *pmt); // // DMOs that store data and/or state information may need to implement // // HRESULT PrepareForStreaming(); // HRESULT Discontinuity(); // HRESULT Flush(); // // Finally, DMOs that make any buffer size assumptions will need to override // these: // // HRESULT GetInputFlags(DWORD* pdwFlags); // HRESULT GetOutputFlags(DWORD* pdwFlags); // HRESULT GetInputSizeInfo(ULONG *pulSize, ULONG *pcbMaxLookahead, ULONG *pulAlignment); // HRESULT GetOutputSizeInfo(ULONG *pulSize, ULONG *pulAlignment); // // // // The following functions are provided by this base class exclusively for use // by the derived class. The derived class should call these to find out the // currently set mediatype(s) whenever it needs to make a decision that // depends on the mediatype used. Each of these returns NULL if the mediatype // has not been set yet. // // DMO_MEDIA_TYPE *InputType(); // DMO_MEDIA_TYPE *OutputType(). // #define PROLOGUE \ CDMOAutoLock l(&m_cs); \ if (ulStreamIndex >= 1) \ return DMO_E_INVALIDSTREAMINDEX class C1in1outDMO : public IMediaObject { public: C1in1outDMO() : m_bInputTypeSet(FALSE), m_bOutputTypeSet(FALSE), m_bIncomplete(FALSE) { #ifdef DMO_NOATL InitializeCriticalSection(&m_cs); #endif } ~C1in1outDMO() { FreeInputType(); FreeOutputType(); #ifdef DMO_NOATL DeleteCriticalSection(&m_cs); #endif } public: // // IMediaObject methods // STDMETHODIMP GetStreamCount(unsigned long *pulNumberOfInputStreams, unsigned long *pulNumberOfOutputStreams) { CDMOAutoLock l(&m_cs); if (pulNumberOfInputStreams == NULL || pulNumberOfOutputStreams == NULL) { return E_POINTER; } *pulNumberOfInputStreams = 1; *pulNumberOfOutputStreams = 1; return S_OK; } STDMETHODIMP GetInputStreamInfo(ULONG ulStreamIndex, DWORD *pdwFlags) { if( NULL == pdwFlags ) { return E_POINTER; } PROLOGUE; return GetInputFlags(pdwFlags); } STDMETHODIMP GetOutputStreamInfo(ULONG ulStreamIndex, DWORD *pdwFlags) { if( NULL == pdwFlags ) { return E_POINTER; } PROLOGUE; return GetOutputFlags(pdwFlags); } STDMETHODIMP GetInputType(ULONG ulStreamIndex, ULONG ulTypeIndex, DMO_MEDIA_TYPE *pmt) { PROLOGUE; return GetInputType(ulTypeIndex, pmt); } STDMETHODIMP GetOutputType(ULONG ulStreamIndex, ULONG ulTypeIndex, DMO_MEDIA_TYPE *pmt) { PROLOGUE; return GetOutputType(ulTypeIndex, pmt); } STDMETHODIMP GetInputCurrentType(ULONG ulStreamIndex, DMO_MEDIA_TYPE *pmt) { PROLOGUE; if (m_bInputTypeSet) return MoCopyMediaType(pmt, &m_InputType); else return DMO_E_TYPE_NOT_SET; } STDMETHODIMP GetOutputCurrentType(ULONG ulStreamIndex, DMO_MEDIA_TYPE *pmt) { PROLOGUE; if (m_bOutputTypeSet) return MoCopyMediaType(pmt, &m_OutputType); else return DMO_E_TYPE_NOT_SET; } STDMETHODIMP GetInputSizeInfo(ULONG ulStreamIndex, ULONG *pulSize, ULONG *pcbMaxLookahead, ULONG *pulAlignment) { if( (NULL == pulSize) || (NULL == pcbMaxLookahead) || (NULL == pulAlignment) ) { return E_POINTER; } PROLOGUE; if (!m_bInputTypeSet) return DMO_E_TYPE_NOT_SET; return GetInputSizeInfo(pulSize, pcbMaxLookahead, pulAlignment); } STDMETHODIMP GetOutputSizeInfo(ULONG ulStreamIndex, ULONG *pulSize, ULONG *pulAlignment) { if( (NULL == pulSize) || (NULL == pulAlignment) ) { return E_POINTER; } PROLOGUE; if (!m_bOutputTypeSet) return DMO_E_TYPE_NOT_SET; return GetOutputSizeInfo(pulSize, pulAlignment); } STDMETHODIMP SetInputType(ULONG ulStreamIndex, const DMO_MEDIA_TYPE *pmt, DWORD dwFlags) { PROLOGUE; HRESULT hr = ValidateSetTypeParameters(pmt, dwFlags); if (FAILED(hr)) { return hr; } if (DMO_SET_TYPEF_CLEAR & dwFlags) { FreeInputType(); return NOERROR; } else { hr = CheckInputType(pmt); if (FAILED(hr)) return hr; if (dwFlags & DMO_SET_TYPEF_TEST_ONLY) return NOERROR; hr = AtomicCopyMediaType(pmt, &m_InputType, m_bInputTypeSet); if (FAILED(hr)) { return hr; } m_bInputTypeSet = TRUE; if (m_bOutputTypeSet) { hr = PrepareForStreaming(); if (FAILED(hr)) { FreeInputType(); return hr; } } return NOERROR; } } STDMETHODIMP SetOutputType(ULONG ulStreamIndex, const DMO_MEDIA_TYPE *pmt, DWORD dwFlags) { PROLOGUE; HRESULT hr = ValidateSetTypeParameters(pmt, dwFlags); if (FAILED(hr)) { return hr; } if (DMO_SET_TYPEF_CLEAR & dwFlags) { FreeOutputType(); return NOERROR; } else { hr = CheckOutputType(pmt); if (FAILED(hr)) return hr; if (dwFlags & DMO_SET_TYPEF_TEST_ONLY) return NOERROR; hr = AtomicCopyMediaType(pmt, &m_OutputType, m_bOutputTypeSet); if (FAILED(hr)) { return hr; } m_bOutputTypeSet = TRUE; if (m_bInputTypeSet) { hr = PrepareForStreaming(); if (FAILED(hr)) { FreeOutputType(); return hr; } } return NOERROR; } } STDMETHODIMP GetInputStatus( ULONG ulStreamIndex, DWORD *pdwStatus ) { if( NULL == pdwStatus ) { return E_POINTER; } PROLOGUE; *pdwStatus = 0; if (AcceptingInput() == S_OK) *pdwStatus |= DMO_INPUT_STATUSF_ACCEPT_DATA; return NOERROR; } STDMETHODIMP GetInputMaxLatency(unsigned long ulStreamIndex, REFERENCE_TIME *prtLatency) { return E_NOTIMPL; } STDMETHODIMP SetInputMaxLatency(unsigned long ulStreamIndex, REFERENCE_TIME rtLatency) { return E_NOTIMPL; } STDMETHODIMP Discontinuity(ULONG ulStreamIndex) { PROLOGUE; return Discontinuity(); } STDMETHODIMP Flush() { CDMOAutoLock l(&m_cs); DoFlush(); return NOERROR; } STDMETHODIMP AllocateStreamingResources() {return S_OK;} STDMETHODIMP FreeStreamingResources() {return S_OK;} // // Processing methods - public entry points // STDMETHODIMP ProcessInput( DWORD ulStreamIndex, IMediaBuffer *pBuffer, // [in], must not be NULL DWORD dwFlags, // [in] - discontinuity, timestamp, etc. REFERENCE_TIME rtTimestamp, // [in], valid if flag set REFERENCE_TIME rtTimelength // [in], valid if flag set ) { PROLOGUE; if (!TypesSet()) { return DMO_E_TYPE_NOT_SET; } if (AcceptingInput() != S_OK) return DMO_E_NOTACCEPTING; if (!pBuffer) return E_POINTER; // deal with the IMediaBuffer so the derived class doesn't have to BYTE *pData; ULONG ulSize; HRESULT hr = pBuffer->GetBufferAndLength(&pData, &ulSize); if (FAILED(hr)) return hr; if (pData == NULL) ulSize = 0; m_bIncomplete = TRUE; // new input means we may be able to produce output return AcceptInput(pData, ulSize, dwFlags, rtTimestamp, rtTimelength, pBuffer); } STDMETHODIMP ProcessOutput( DWORD dwReserved, DWORD ulOutputBufferCount, DMO_OUTPUT_DATA_BUFFER *pOutputBuffers, DWORD *pdwStatus) { HRESULT hr; CDMOAutoLock l(&m_cs); if (pdwStatus == NULL) { return E_POINTER; } *pdwStatus = 0; if (ulOutputBufferCount != 1) return E_INVALIDARG; if (!TypesSet()) { return DMO_E_TYPE_NOT_SET; } pOutputBuffers[0].dwStatus = 0; // deal with the IMediaBuffer so the derived class doesn't have to BYTE *pOut; ULONG ulSize; ULONG ulAvail; if (pOutputBuffers[0].pBuffer) { hr = pOutputBuffers[0].pBuffer->GetBufferAndLength(&pOut, &ulSize); if (FAILED(hr)) return hr; hr = pOutputBuffers[0].pBuffer->GetMaxLength(&ulAvail); if (FAILED(hr)) return hr; if (ulSize) { // skip any already used portion of the buffer if (ulSize > ulAvail) return E_INVALIDARG; ulAvail -= ulSize; pOut += ulSize; } } else { // no IMediaBuffer // // If (a) the output stream says it can operate without buffers, AND // (b) the DISCARD flag was set in dwReserved, // then call ProduceOutput with a NULL output buffer pointer. // // Otherwise just return the INCOMPLETE flag without any processing. // DWORD dwFlags; if (SUCCEEDED(GetOutputFlags(&dwFlags)) && ((dwFlags & DMO_OUTPUT_STREAMF_DISCARDABLE) || (dwFlags & DMO_OUTPUT_STREAMF_OPTIONAL) ) && (dwReserved & DMO_PROCESS_OUTPUT_DISCARD_WHEN_NO_BUFFER)) { // process, but discard the output pOut = NULL; ulAvail = 0; } else { // just report the incomplete status without altering our state if (m_bIncomplete) pOutputBuffers[0].dwStatus |= DMO_OUTPUT_DATA_BUFFERF_INCOMPLETE; return NOERROR; } } ULONG ulProduced = 0; hr = ProduceOutput(pOut, ulAvail, &ulProduced, &(pOutputBuffers[0].dwStatus), &(pOutputBuffers[0].rtTimestamp), &(pOutputBuffers[0].rtTimelength)); if (FAILED(hr)) return hr; HRESULT hrProcess = hr; // remember this in case it's S_FALSE // remember the DMO's incomplete status if (pOutputBuffers[0].dwStatus & DMO_OUTPUT_DATA_BUFFERF_INCOMPLETE) m_bIncomplete = TRUE; else m_bIncomplete = FALSE; if (pOut) { // if using an output buffer, set the amount we used if (ulProduced > ulAvail) return E_FAIL; hr = pOutputBuffers[0].pBuffer->SetLength(ulSize + ulProduced); if (FAILED(hr)) return hr; } return hrProcess; } #ifdef FIX_LOCK_NAME STDMETHODIMP DMOLock(LONG lLock) #else STDMETHODIMP Lock(LONG lLock) #endif { if (lLock) { #ifdef DMO_NOATL EnterCriticalSection(&m_cs); #else m_cs.Lock(); #endif } else { #ifdef DMO_NOATL LeaveCriticalSection(&m_cs); #else m_cs.Unlock(); #endif } return S_OK; } protected: HRESULT AtomicCopyMediaType(const DMO_MEDIA_TYPE *pmtSource, DMO_MEDIA_TYPE *pmtDestination, BOOL bDestinationInitialized) { // pmtDestination should always point to a valid DMO_MEDIA_TYPE structure. assert(NULL != pmtDestination); DMO_MEDIA_TYPE mtTempDestination; // actually set the type HRESULT hr = MoCopyMediaType(&mtTempDestination, pmtSource); if (FAILED(hr)) { return hr; } // Free any previous mediatype if (bDestinationInitialized) { MoFreeMediaType(pmtDestination); } *pmtDestination = mtTempDestination; return S_OK; } // // private methods for use by derived class // DMO_MEDIA_TYPE *InputType() { if (m_bInputTypeSet) return &m_InputType; else return NULL; } DMO_MEDIA_TYPE *OutputType() { if (m_bOutputTypeSet) return &m_OutputType; else return NULL; } protected: // // To be overriden by the derived class // virtual HRESULT GetInputFlags(DWORD* pdwFlags) { *pdwFlags = 0; // default implementation assumes no lookahead return NOERROR; } virtual HRESULT GetOutputFlags(DWORD* pdwFlags) { *pdwFlags = 0; return NOERROR; } virtual HRESULT GetInputType(ULONG ulTypeIndex, DMO_MEDIA_TYPE *pmt) { return DMO_E_NO_MORE_ITEMS; // default implementation exposes no types } virtual HRESULT GetOutputType(ULONG ulTypeIndex, DMO_MEDIA_TYPE *pmt) { return DMO_E_NO_MORE_ITEMS; // default implementation exposes no types } virtual HRESULT CheckInputType(const DMO_MEDIA_TYPE *pmt) { if ((pmt == NULL) || ((pmt->cbFormat > 0) && (pmt->pbFormat == NULL))) return E_POINTER; return S_OK; // default implementation accepts anything } virtual HRESULT CheckOutputType(const DMO_MEDIA_TYPE *pmt) { if ((pmt == NULL) || ((pmt->cbFormat > 0) && (pmt->pbFormat == NULL))) return E_POINTER; return S_OK; // default implementation accepts anything } virtual HRESULT GetInputSizeInfo(ULONG *pulSize, ULONG *pcbMaxLookahead, ULONG *pulAlignment) { *pulSize = 1; // default implementation imposes no size requirements *pcbMaxLookahead = 0; // default implementation assumes no lookahead *pulAlignment = 1; // default implementation assumes no alignment return NOERROR; } virtual HRESULT GetOutputSizeInfo(ULONG *pulSize, ULONG *pulAlignment) { *pulSize = 1; // default implementation imposes no size requirements *pulAlignment = 1; // default implementation assumes no alignment return NOERROR; } virtual HRESULT PrepareForStreaming() { return NOERROR; } virtual HRESULT AcceptingInput() { return S_FALSE; } virtual HRESULT Discontinuity() { return NOERROR; } virtual HRESULT DoFlush() { return NOERROR; } virtual HRESULT AcceptInput(BYTE* pData, ULONG ulSize, DWORD dwFlags, REFERENCE_TIME rtTimestamp, REFERENCE_TIME rtTimelength, IMediaBuffer* pMediaBuffer ) { m_bIncomplete = FALSE; return S_FALSE; } virtual HRESULT ProduceOutput(BYTE *pData, ULONG ulAvail, ULONG* pulUsed, DWORD* pdwStatus, REFERENCE_TIME *prtTimestamp, REFERENCE_TIME *prtTimelength ) { *pulUsed = 0; return S_FALSE; } HRESULT ValidateSetTypeParameters(const DMO_MEDIA_TYPE *pmt, DWORD dwFlags) { // Validate parameters. if (!(DMO_SET_TYPEF_CLEAR & dwFlags)) { // The DMO specification states that pmt CANNOT be NULL if // the DMO_SET_TYPEF_CLEAR flag is NOT set. if (NULL == pmt) { return E_POINTER; } } // The caller cannot set the DMO_SET_TYPEF_CLEAR flag and the // DMO_SET_TYPEF_TEST_ONLY flag. The DMO specification prohibits // this combination because the two flags are mutually exclusive. if ((DMO_SET_TYPEF_CLEAR & dwFlags) && (DMO_SET_TYPEF_TEST_ONLY & dwFlags)) { return E_INVALIDARG; } // Check for illegal flags. if (~(DMO_SET_TYPEF_CLEAR | DMO_SET_TYPEF_TEST_ONLY) & dwFlags) { return E_INVALIDARG; } return S_OK; } bool TypesSet() { return m_bInputTypeSet && m_bOutputTypeSet; } void FreeInputType() { if (m_bInputTypeSet) { MoFreeMediaType( &m_InputType ); m_bInputTypeSet = FALSE; } } void FreeOutputType() { if (m_bOutputTypeSet) { MoFreeMediaType( &m_OutputType ); m_bOutputTypeSet = FALSE; } } protected: // mediatype stuff BOOL m_bInputTypeSet; BOOL m_bOutputTypeSet; DMO_MEDIA_TYPE m_InputType; DMO_MEDIA_TYPE m_OutputType; BOOL m_bIncomplete; protected: #ifdef DMO_NOATL CRITICAL_SECTION m_cs; #else CComAutoCriticalSection m_cs; #endif }; // // C1for1DMO - base class for 1-input/1-output DMOs which // - work on whole samples at a time, one sample per buffer // - produce exactly one output sample for every input sample // - don't need to accumulate more than 1 input sample before producing // - don't produce any additional stuff at the end // - the output sample corresponds in time to the input sample // // The derived class must implement: // HRESULT Process(BYTE* pIn, // ULONG ulBytesIn, // BYTE* pOut, // ULONG* pulProduced); // HRESULT GetSampleSizes(ULONG* pulMaxInputSampleSize, // ULONG* pulMaxOutputSampleSize); // // // The derived class should implement: // HRESULT GetInputType(ULONG ulTypeIndex, DMO_MEDIA_TYPE *pmt); // HRESULT GetOutputType(ULONG ulTypeIndex, DMO_MEDIA_TYPE *pmt); // HRESULT CheckInputType(const DMO_MEDIA_TYPE *pmt); // HRESULT CheckOutputType(const DMO_MEDIA_TYPE *pmt); // // The derived class may implement if it needs to: // HRESULT Init(); // // The following methods are implemented by the base class. The derived class // should call these to find out if the input/output type has been set and if // so what it was set to. // DMO_MEDIA_TYPE *InputType(); // DMO_MEDIA_TYPE *OutputType(). // class C1for1DMO : public C1in1outDMO { public: C1for1DMO() : m_pBuffer(NULL) { } ~C1for1DMO() { if (m_pBuffer) m_pBuffer->Release(); } protected: // // Implement C1in1outDMO overridables // virtual HRESULT GetInputFlags(DWORD* pdwFlags) { *pdwFlags = DMO_INPUT_STREAMF_WHOLE_SAMPLES | DMO_INPUT_STREAMF_SINGLE_SAMPLE_PER_BUFFER; return NOERROR; } virtual HRESULT GetOutputFlags(DWORD* pdwFlags) { *pdwFlags = DMO_OUTPUT_STREAMF_WHOLE_SAMPLES | DMO_OUTPUT_STREAMF_SINGLE_SAMPLE_PER_BUFFER; return NOERROR; } HRESULT GetInputSizeInfo(ULONG *pulSize, ULONG *pcbMaxLookahead, ULONG *pulAlignment) { HRESULT hr = GetSampleSizes(&m_ulMaxInputSize, &m_ulMaxOutputSize); if (FAILED(hr)) return hr; *pulSize = m_ulMaxInputSize; *pcbMaxLookahead = 0; *pulAlignment = 1; return NOERROR; } HRESULT GetOutputSizeInfo(ULONG *pulSize, ULONG *pulAlignment) { HRESULT hr = GetSampleSizes(&m_ulMaxInputSize, &m_ulMaxOutputSize); if (FAILED(hr)) return hr; *pulSize = m_ulMaxOutputSize; *pulAlignment = 1; return NOERROR; } HRESULT PrepareForStreaming() { HRESULT hr = GetSampleSizes(&m_ulMaxInputSize, &m_ulMaxOutputSize); if (FAILED(hr)) return hr; return Init(); } HRESULT AcceptingInput() { return m_pBuffer ? S_FALSE : S_OK; // accept unless holding one already } HRESULT AcceptInput(BYTE* pData, ULONG ulSize, DWORD dwFlags, REFERENCE_TIME rtTimestamp, REFERENCE_TIME rtTimelength, IMediaBuffer* pMediaBuffer ) { if (AcceptingInput() != S_OK) return E_FAIL; m_pData = pData; m_ulSize = ulSize; m_dwFlags = dwFlags; m_rtTimestamp = rtTimestamp; m_rtTimelength = rtTimelength; m_pBuffer = pMediaBuffer; pMediaBuffer->AddRef(); return NOERROR; } HRESULT DoFlush() { Discontinuity(); if (m_pBuffer) { m_pBuffer->Release(); m_pBuffer = NULL; } return NOERROR; } HRESULT ProduceOutput(BYTE *pOut, ULONG ulAvail, ULONG* pulUsed, DWORD* pdwStatus, REFERENCE_TIME *prtTimestamp, REFERENCE_TIME *prtTimelength ) { *pulUsed = 0; *pdwStatus = 0; if (!m_pBuffer) return S_FALSE; if (pOut) { if (ulAvail < m_ulMaxOutputSize) return E_INVALIDARG; } HRESULT hr = Process(m_pData, m_ulSize, pOut, pulUsed); m_pBuffer->Release(); m_pBuffer = NULL; if (FAILED(hr)) return hr; if (*pulUsed == 0) return S_FALSE; if (m_dwFlags & DMO_INPUT_DATA_BUFFERF_SYNCPOINT) *pdwStatus |= DMO_OUTPUT_DATA_BUFFERF_SYNCPOINT; if (m_dwFlags & DMO_INPUT_DATA_BUFFERF_TIME) *pdwStatus |= DMO_OUTPUT_DATA_BUFFERF_TIME; if (m_dwFlags & DMO_INPUT_DATA_BUFFERF_TIMELENGTH) *pdwStatus |= DMO_OUTPUT_DATA_BUFFERF_TIMELENGTH; *prtTimestamp = m_rtTimestamp; *prtTimelength = m_rtTimelength; return hr; } protected: // // To be implemented by derived class // virtual HRESULT Process(BYTE* pIn, ULONG ulBytesIn, BYTE* pOut, ULONG* pulProduced) = 0; virtual HRESULT GetSampleSizes(ULONG* pulMaxInputSampleSize, ULONG* pulMaxOutputSampleSize) = 0; virtual HRESULT Init() { return NOERROR; } IMediaBuffer* m_pBuffer; BYTE* m_pData; ULONG m_ulSize; DWORD m_dwFlags; REFERENCE_TIME m_rtTimestamp; REFERENCE_TIME m_rtTimelength; ULONG m_ulMaxOutputSize; ULONG m_ulMaxInputSize; }; // // C1for1QCDMO - adds an IDMOQualityControl implementation to C1for1DMO. Just like // C1for1DMO, this base class assumes that the DMO produces exactly one output sample // for each input sample, etc. etc. // // A class that derives from C1for1QCDMO has access to / ability to override all // the same methods as with C1for1DMO, except // (1) A class derived from C1for1QCDMO should override QCProcess instead of // Process because C1for1QCDMO::Process implements some code required for // quality control. QCProcess has the same prototype as C1for1DMO::Process. // (2) If a class derived from C1for1QCDMO overrides Init(), it should at some // point call C1for1QCDMO::Init() to make sure C1for1QCDMO's quality control // data members are properly initialized. // class C1for1QCDMO : public C1for1DMO, public IDMOQualityControl { public: // // IDMOQualityControl // STDMETHODIMP SetNow(REFERENCE_TIME rtNow) { // Remember SetNow values even if quality control is not currently enabled DWORD dwTicks = GetTickCount(); CDMOAutoLock l(&m_cs); m_rtNow = rtNow; m_dwNow = dwTicks; return NOERROR; } STDMETHODIMP SetStatus(DWORD dwFlags) { // Any point in grabbing the object lock here ? if (dwFlags & DMO_QUALITY_STATUS_ENABLED) m_bQualityControlEnabled = TRUE; else m_bQualityControlEnabled = FALSE; return NOERROR; } STDMETHODIMP GetStatus(DWORD *pdwFlags) { // Any point in grabbing the object lock here ? if (m_bQualityControlEnabled) *pdwFlags = DMO_QUALITY_STATUS_ENABLED; else *pdwFlags = 0; return NOERROR; } protected: HRESULT Init() { m_bQualityControlEnabled = FALSE; m_rtProcess = 100000; // 10 ms - initial guess at processing time return NOERROR; } // Override Process to add quality control HRESULT Process(BYTE* pIn,ULONG ulBytesIn,BYTE* pOut,ULONG* pulProduced) { // Skip the sample if it is likely to be late. if (m_bQualityControlEnabled && (m_dwFlags & DMO_INPUT_DATA_BUFFERF_TIME) && // timestamp present (m_rtNow + (GetTickCount() - m_dwNow) * 10000 + m_rtProcess > m_rtTimestamp + 0000000)) { *pulProduced = 0; return S_FALSE; } DWORD dwBefore = GetTickCount(); HRESULT hr = QCProcess(m_pData, m_ulSize, pOut, pulProduced); DWORD dwAfter = GetTickCount(); // Make the new m_rtProcess a weighted average of the old m_rtProcess // and the value we just got. 0.8 and 0.2 give a time constant of about 4, // and it takes about 10 iterations to reach 90% - seems reasonable, but // I don't know what the optimal value is. m_rtProcess = (REFERENCE_TIME)(0.8 * m_rtProcess + 0.2 * (((REFERENCE_TIME)(dwAfter - dwBefore)) * 10000)); return hr; } // To be implemented by derived class virtual HRESULT QCProcess(BYTE* pIn, ULONG ulBytesIn, BYTE* pOut, ULONG* pulProduced) = 0; private: // variables used by quality control code BOOL m_bQualityControlEnabled; REFERENCE_TIME m_rtNow; DWORD m_dwNow; REFERENCE_TIME m_rtProcess; // average processing delay }; // // CFBRDMO - DMO base class for 'fixed bitrate' DMOs. More specifically, // this base class assumes the following: // - 1 input, 1 output; // - both input and output consist of equally sized 'quanta'; // - input/output quantum sizes can be determined from mediatypes; // - each output quantum can be generated independently (without looking at // previous output quanta); // - if multiple input quanta are needed to generate a particular output // quantum ('window overhead'), then the range of input required has an upper // bound derived from mediatypes on both sides (i.e., both 'lookahead' // and 'input memory' are bounded). // // The derived class must implement the following virtual functions: // HRESULT FBRProcess(DWORD cQuanta, BYTE *pIn, BYTE *pOut); // HRESULT GetStreamingParams( // DWORD *pdwInputQuantumSize, // in bytes // DWORD *pdwOutputQuantumSize, // in bytes // DWORD *pdwMaxLookahead, // in input quanta, 0 means no lookahead // DWORD *pdwLookBehind, // REFERENCE_TIME *prtQuantumDuration, // same for input and output quanta // REFERENCE_TIME *prtDurationDenominator // optional, normally 1 // ); // The derived class should also implement the following: // HRESULT GetInputType(ULONG ulTypeIndex, DMO_MEDIA_TYPE *pmt); // HRESULT GetOutputType(ULONG ulTypeIndex, DMO_MEDIA_TYPE *pmt); // HRESULT CheckInputType(const DMO_MEDIA_TYPE *pmt); // HRESULT CheckOutputType(const DMO_MEDIA_TYPE *pmt); // The derived class may need to implement the followng: // HRESULT Init(); // HRESULT Discontinuity(); // // The derived class may use these entry points into the base class to get // the currently set mediatypes: // DMO_MEDIA_TYPE *InputType(); // DMO_MEDIA_TYPE *OutputType(). // // The sum of *pdwMaxLookahead and *pdwLoookbehind is the 'window overhead' of // the algorithm (the window overhead is 0 if the algorithm only needs the // current input sample). // // Because the non-zero window overhead case is more complicated, it is handled by a // separate set of functions in this base class. The names of all non-zero // window overhead functions have the 'NZWO' prefix. The names of the // zero window overhead functions begin with 'ZWO'. // // A data copy on the input side is necessary in the non-zero window overhead case. // class CFBRDMO : public C1in1outDMO { public: CFBRDMO() : m_bParametersSet(FALSE), m_pMediaBuffer(NULL), m_pAllocAddr(NULL), m_bStreaming(FALSE) { } ~CFBRDMO() { /* if (m_bStreaming) StopStreaming(); */ if (m_pAllocAddr) delete[] m_pAllocAddr; if (m_pMediaBuffer) m_pMediaBuffer->Release(); } protected: // // Implement C1in1outDMO overridables // HRESULT GetInputSizeInfo(ULONG *pulSize, ULONG *pcbMaxLookahead, ULONG *pulAlignment) { if (!(InputType() && OutputType())) return DMO_E_TYPE_NOT_SET; // // For efficiency reasons we might like to be fed fairly large amounts // of data at a time, but technically all we need is one quantum. // *pulSize = m_ulInputQuantumSize; *pcbMaxLookahead = 0; // this base class does not rely on HOLDS_BUFFERS *pulAlignment = 1; return NOERROR; } HRESULT GetOutputSizeInfo(ULONG *pulSize, ULONG *pulAlignment) { if (!(InputType() && OutputType())) return DMO_E_TYPE_NOT_SET; *pulSize = m_ulOutputQuantumSize; *pulAlignment = 1; return NOERROR; } virtual HRESULT Discontinuity() { m_bDiscontinuity = TRUE; return NOERROR; } virtual HRESULT AcceptInput(BYTE* pData, ULONG ulSize, DWORD dwFlags, REFERENCE_TIME rtTimestamp, REFERENCE_TIME rtTimelength, IMediaBuffer* pBuffer ) { BOOL bTimestamp = (dwFlags & DMO_INPUT_DATA_BUFFERF_TIME) ? TRUE : FALSE; if (m_ulWindowOverhead) return NZWOProcessInput(pBuffer, pData, ulSize, bTimestamp, rtTimestamp); else return ZWOProcessInput(pBuffer, pData, ulSize, bTimestamp, rtTimestamp); } virtual HRESULT ProduceOutput(BYTE *pOut, ULONG ulAvail, ULONG* pulUsed, DWORD* pdwStatus, REFERENCE_TIME *prtTimestamp, REFERENCE_TIME *prtTimelength ) { HRESULT hr; if (!m_bParametersSet) return DMO_E_TYPE_NOT_SET; // call Discontinuity() if this is the first ProcessOutput() call if (!m_bStreaming) { HRESULT hr = Discontinuity(); if (FAILED(hr)) return hr; m_bStreaming = TRUE; } *pdwStatus = 0; ULONG ulInputQuantaAvailable = InputQuantaAvailable(); if (!ulInputQuantaAvailable) return S_FALSE; // did not produce anything ULONG ulOutputQuantaPossible = ulAvail / m_ulOutputQuantumSize; if (!ulOutputQuantaPossible) return E_INVALIDARG; ULONG ulQuantaToProcess = min(ulOutputQuantaPossible, ulInputQuantaAvailable); assert(ulQuantaToProcess > 0); BOOL bTimestamp; if (m_ulWindowOverhead) hr = NZWOProcessOutput(pOut, ulQuantaToProcess, &bTimestamp, prtTimestamp); else hr = ZWOProcessOutput(pOut, ulQuantaToProcess, &bTimestamp, prtTimestamp); if (FAILED(hr)) return hr; *pulUsed = ulQuantaToProcess * m_ulOutputQuantumSize; *pdwStatus |= DMO_OUTPUT_DATA_BUFFERF_SYNCPOINT; if (bTimestamp) *pdwStatus |= DMO_OUTPUT_DATA_BUFFERF_TIME; // any data left ? if (InputQuantaAvailable()) // yes - set incomplete *pdwStatus |= DMO_OUTPUT_DATA_BUFFERF_INCOMPLETE; else if (m_bDiscontinuity) // no - process any discontinuity DoFlush(); return NOERROR; } HRESULT DoFlush() { Discontinuity(); // reset flags m_bDiscontinuity = FALSE; m_bTimestamps = FALSE; if (m_ulWindowOverhead) NZWODiscardData(); else ZWODiscardData(); return NOERROR; } HRESULT AcceptingInput() { if (!m_bParametersSet) // uninitialized return S_FALSE; BOOL bResult; if (m_ulWindowOverhead) bResult = NZWOQueryAccept(); else bResult = ZWOQueryAccept(); return bResult ? S_OK : S_FALSE; } // End C1in1out overridables implementation private: // // Common private code (window overhead or no window overhead) // // returns the number of input quanta available minus any window overhead ULONG InputQuantaAvailable() { if (m_ulWindowOverhead) return NZWOAvail(); else return ZWOAvail(); } // Private method to compute/allocate stuff once all types have been set. HRESULT PrepareForStreaming () { m_bParametersSet = FALSE; // Now that both types are set, query the derived class for params HRESULT hr; if (FAILED(hr = GetStreamingParams(&m_ulInputQuantumSize, &m_ulOutputQuantumSize, &m_ulLookahead, &m_ulLookbehind, &m_rtDurationNumerator, &m_rtDenominator))) return hr; // m_ulOutputQuantumSize and m_ulInputQuantumSize should never be 0. assert( (0 != m_ulInputQuantumSize) && (0 != m_ulOutputQuantumSize) ); if (!m_rtDenominator) { assert(!"bad object - duration denominator should not be 0 !"); return E_FAIL; } // Attempt to reduce the fraction. Probably the most complicated number // we will ever see is 44100 = (3 * 7 * 2 * 5) ^ 2, so trying the first // few numbers should suffice in most cases. DWORD dwP[] = {2,3,5,7,11,13,17,19,23,29,31}; for (DWORD c = 0; c < sizeof(dwP) / sizeof(DWORD); c++) { while ((m_rtDurationNumerator % dwP[c] == 0) && (m_rtDenominator % dwP[c] == 0)) { m_rtDurationNumerator /= dwP[c]; m_rtDenominator /= dwP[c]; } } // We cannot afford to have huge denominators, unfortunately, because // we store timestamp numerators using 64 bits, so a large denominator // could result in timestamp overflows. So if the denominator is still // too large, reduce it anyway with loss of precision. ULONG ulMax = 0x10000; // largest acceptable denominator value if (m_rtDenominator >= ulMax) { double actual_ratio = (double)m_rtDurationNumerator * (double)m_rtDenominator; ULONG ulDenominator = 1; // Repeatedly increase the denominator until either the actual ratio // can be represented precisely using the denominator, or the // denominator gets too large. do { double fractional_part = actual_ratio * (double)ulDenominator - floor(actual_ratio * (double)ulDenominator); if (fractional_part == 0) break; ULONG ulNewDenominator = (ULONG)floor(ulDenominator / fractional_part); if (ulNewDenominator >= ulMax) break; ulDenominator = ulNewDenominator; } while(1); m_rtDurationNumerator = (ULONG)floor(actual_ratio * ulDenominator); m_rtDenominator = ulDenominator; } m_ulWindowOverhead = m_ulLookahead + m_ulLookbehind; if (!m_ulWindowOverhead) // No window overhead - the simple case m_bParametersSet = TRUE; else // The complicated case with window overhead AllocateCircularBuffer(); m_bTimestamps = FALSE; m_bDiscontinuity = FALSE; if (m_bStreaming) { //StopStreaming(); m_bStreaming = FALSE; } hr = Init(); if( FAILED( hr ) ) { m_bParametersSet = FALSE; return hr; } return m_bParametersSet ? NOERROR : E_FAIL; } // end common code // // zero window overhead case code // HRESULT ZWOProcessInput(IMediaBuffer* pBuffer, BYTE* pData, ULONG ulSize, BOOL bTimestamp, REFERENCE_TIME rtTimestamp) { assert(!m_pMediaBuffer); m_bTimestamp = bTimestamp; m_rtTimestamp = rtTimestamp; m_pData = pData; m_ulData = ulSize; m_ulUsed = 0; // make sure they gave us a meaningful amount of data if (m_ulData < m_ulInputQuantumSize) return S_FALSE; // save the buffer we were given m_pMediaBuffer = pBuffer; pBuffer->AddRef(); return NOERROR; } HRESULT ZWOProcessOutput(BYTE* pOut, ULONG ulQuantaToProcess, BOOL* pbTimestamp, REFERENCE_TIME* prtTimestamp) { assert(m_ulUsed % m_ulInputQuantumSize == 0); HRESULT hr = FBRProcess(ulQuantaToProcess, m_pData + m_ulUsed, pOut); if (FAILED(hr)) return hr; ZWOConsume(ulQuantaToProcess); if (m_bTimestamp) { // there was a timestamp on this input buffer // m_rtTimestamp refers to the beginning of the input buffer. // Extrapolate to the beginning of the area we just processed. *prtTimestamp = m_rtTimestamp + (m_ulUsed % m_ulInputQuantumSize) * m_rtDurationNumerator / m_rtDenominator; *pbTimestamp = TRUE; } else if (m_bTimestamps) { // there was a timestamp earlier // should we extrapolate from a previous timestamp ? *pbTimestamp = FALSE; } else // no timestamps at all *pbTimestamp = FALSE; return NOERROR; } ULONG ZWOAvail() { if (m_pMediaBuffer) { assert(m_ulData - m_ulUsed >= m_ulInputQuantumSize); return (m_ulData - m_ulUsed) / m_ulInputQuantumSize; } else return 0; } void ZWOConsume(ULONG ulN) { // the zero window overhead version assert(m_pMediaBuffer); m_ulUsed += ulN * m_ulInputQuantumSize; assert(m_ulData >= m_ulUsed); if (m_ulData - m_ulUsed < m_ulInputQuantumSize) { m_pMediaBuffer->Release(); m_pMediaBuffer = NULL; } } BOOL ZWOQueryAccept() { // Accept if and only if (IFF) the DMO is not already holding a buffer. if (!m_pMediaBuffer) return TRUE; else return FALSE; } void ZWODiscardData() { if (m_pMediaBuffer) { m_pMediaBuffer->Release(); m_pMediaBuffer = NULL; } } // End zero window overhead case code // // Non zero window overhead case code. // HRESULT NZWOProcessInput(IMediaBuffer* pBuffer, BYTE* pData, ULONG ulSize, BOOL bTimestamp, REFERENCE_TIME rtTimestamp) { if (bTimestamp) { // process the timestamp if (!m_bTimestamps) { // this is the first timestamp we've seen // Just getting started - initialize the timestamp to refer to // the first input quantum for which we will actually generate // output (the first m_ulLookbehind quanta are pure lookbehind and // generate no output). m_rtTimestampNumerator = rtTimestamp * m_rtDenominator + m_ulLookbehind * m_rtDurationNumerator; } else { // We are already streaming and just got a new timestamp. Use it // to check if our stored timestamp has somehow drifted away from // where it should be and adjust if it is far enough off. ULONG ulInputQuantaAvailable = InputQuantaAvailable(); if (ulInputQuantaAvailable) { // ulInputQuantaAvailable is how far back in time the next // quantum we would process is located relative the beginning // of the new buffer we just received. // Compute what the timestamp back there ought to be now. REFERENCE_TIME rtTimestampNumerator; rtTimestampNumerator = m_rtDenominator * rtTimestamp - ulInputQuantaAvailable * m_rtDurationNumerator; // Adjust the stored timestamp if it is off by more than half // the duration of a quantum. Should also have a DbgLog here. if ((m_rtTimestampNumerator >= rtTimestampNumerator + m_rtDurationNumerator / 2) || (m_rtTimestampNumerator <= rtTimestampNumerator - m_rtDurationNumerator / 2)) { m_rtTimestampNumerator = rtTimestampNumerator; } } else { // We must still be accumulating the initial window overhead. // Too early to need an adjustment, one would hope. } } m_bTimestamps = TRUE; } if (BufferUsed() + ulSize > m_ulBufferAllocated) return E_FAIL; // need a max input size to prevent this // append to our buffer AppendData(pData, ulSize); // are we ready to produce now ? if (NZWOAvail()) return NOERROR; else return S_FALSE; // no output can be produced yet } HRESULT NZWOProcessOutput(BYTE* pOut, ULONG ulQuantaToProcess, BOOL* pbTimestamp, REFERENCE_TIME* prtTimestamp) { // // Handle any timestamps // if (m_bTimestamps) { // In window overhead mode the stored timestamp refers to the input // data immediately after lookbehind, which corresponds to the // begining of the output buffer by definition of FDRProcess. *prtTimestamp = m_rtTimestampNumerator / m_rtDenominator; *pbTimestamp = TRUE; } else *pbTimestamp = FALSE; // // Handle the data // HRESULT hr; ULONG ulInputNeeded = m_ulInputQuantumSize * (ulQuantaToProcess + m_ulWindowOverhead); assert(ulInputNeeded < BufferUsed()); if (m_ulDataHead + ulInputNeeded <= m_ulBufferAllocated) { // No wraparound, everything is easy hr = FBRProcess(ulQuantaToProcess, m_pCircularBuffer + m_ulDataHead + m_ulLookbehind * m_ulInputQuantumSize, pOut); if (FAILED(hr)) return hr; NZWOConsume(ulQuantaToProcess); } else { // The data we want to send wraps around the end // Q.: does it wrap around inside the window overhead area // or inside the main data area ? if (m_ulDataHead + m_ulWindowOverhead * m_ulInputQuantumSize < m_ulBufferAllocated) { // The wraparound occurs inside the main data area. Advance the // window overhead up to the wraparound point by processing some data. ULONG ulAdvance = m_ulBufferAllocated - (m_ulDataHead + m_ulWindowOverhead * m_ulInputQuantumSize); assert(ulAdvance % m_ulInputQuantumSize == 0); ulAdvance /= m_ulInputQuantumSize; // convert to quanta assert(ulAdvance > 0); assert(ulAdvance < ulQuantaToProcess); hr = FBRProcess(ulAdvance, m_pCircularBuffer + m_ulDataHead + m_ulLookbehind * m_ulInputQuantumSize, pOut); if (FAILED(hr)) return hr; NZWOConsume(ulAdvance); // Adjust stuff so that the code below can act // as if this extra process call never happened. pOut += m_ulOutputQuantumSize * ulAdvance; ulQuantaToProcess -= ulAdvance; assert(ulQuantaToProcess > 0); // Now the wraparound point should be exactly on the boundary // between window overhead and main data. assert(m_ulDataHead + m_ulWindowOverhead * m_ulInputQuantumSize == m_ulBufferAllocated); } // wraparound in main data // When we get here, the wraparound point occurs somewhere inside // the window overhead area or right on the border between window overhead and // main data. assert(m_ulDataHead + m_ulWindowOverhead * m_ulInputQuantumSize >= m_ulBufferAllocated); ULONG ulLookaheadToCopy = m_ulBufferAllocated - m_ulDataHead; // copy to the special area we reserved at the front memcpy(m_pCircularBuffer - ulLookaheadToCopy, m_pCircularBuffer + m_ulDataHead, ulLookaheadToCopy); // Now the block we are interested in is all in one piece hr = FBRProcess(ulQuantaToProcess, m_pCircularBuffer - ulLookaheadToCopy + m_ulLookbehind * m_ulInputQuantumSize, pOut); if (FAILED(hr)) return hr; NZWOConsume(ulQuantaToProcess); } // data handling - wraparound case return NOERROR; } void AllocateCircularBuffer() { // free any previously allocated input buffer if (m_pAllocAddr) delete[] m_pAllocAddr; // need a better way to decide this number m_ulBufferAllocated = max(m_ulInputQuantumSize * 16, 65536L); m_ulDataHead = m_ulDataTail = 0; // reserve room at the front for copying window overhead ULONG ulPrefix = m_ulWindowOverhead * m_ulInputQuantumSize; m_pAllocAddr = new BYTE[m_ulBufferAllocated + ulPrefix]; if (!m_pAllocAddr) return; m_pCircularBuffer = m_pAllocAddr + ulPrefix; m_bParametersSet = TRUE; } BOOL NZWOQueryAccept() { // We are using a temp input buffer. Is there room to append more ? // The answer really depends on how much data they will try to feed // us. Without knowing the maximum input buffer size, we will accept // more if the input buffer is less than half full. if (2 * BufferUsed() < m_ulBufferAllocated) return TRUE; else return FALSE; } ULONG NZWOAvail() { ULONG ulInputQuantaAvailable = BufferUsed() / m_ulInputQuantumSize; if (ulInputQuantaAvailable > m_ulWindowOverhead) return ulInputQuantaAvailable - m_ulWindowOverhead; else return 0; } void NZWOConsume(ULONG ulN) { // the window overhead version assert(ulN * m_ulInputQuantumSize <= BufferUsed()); m_ulDataHead += ulN * m_ulInputQuantumSize; if (m_ulDataHead > m_ulBufferAllocated) //wraparound m_ulDataHead -= m_ulBufferAllocated; // Advance the timestamp. // The same denominator is used for both timestamp and duration. m_rtTimestampNumerator += ulN * m_rtDurationNumerator; } ULONG BufferUsed() { if (m_ulDataTail >= m_ulDataHead) return m_ulDataTail - m_ulDataHead; else return m_ulBufferAllocated - (m_ulDataHead - m_ulDataTail); } void AppendData(BYTE *pData, ULONG ulSize) { if (m_ulDataTail + ulSize <= m_ulBufferAllocated) { // no wraparound memcpy(m_pCircularBuffer + m_ulDataTail, pData, ulSize); m_ulDataTail += ulSize; } else { // wraparound memcpy(m_pCircularBuffer + m_ulDataTail, pData, m_ulBufferAllocated - m_ulDataTail); memcpy(m_pCircularBuffer, pData + m_ulBufferAllocated - m_ulDataTail, ulSize - (m_ulBufferAllocated - m_ulDataTail)); m_ulDataTail += ulSize; m_ulDataTail -= m_ulBufferAllocated; } } void NZWODiscardData() { m_ulDataHead = m_ulDataTail = 0; } // End window overhead case code protected: // // To be implemebted by the derived class // virtual HRESULT FBRProcess(DWORD cQuanta, BYTE *pIn, BYTE *pOut) = 0; virtual HRESULT GetStreamingParams( DWORD *pdwInputQuantumSize, // in bytes DWORD *pdwOutputQuantumSize, // in bytes DWORD *pdwMaxLookahead, // in input quanta, 0 means no lookahead DWORD *pdwLookbehind, REFERENCE_TIME *prtQuantumDuration, // same for input and output quanta REFERENCE_TIME *prtDurationDenominator // optional, normally 1 ) = 0; virtual HRESULT Init() { return NOERROR; } private: BOOL m_bNewInput; // streaming parameters BOOL m_bParametersSet; ULONG m_ulInputQuantumSize; ULONG m_ulOutputQuantumSize; ULONG m_ulLookahead; ULONG m_ulLookbehind; ULONG m_ulWindowOverhead; REFERENCE_TIME m_rtDurationNumerator; REFERENCE_TIME m_rtDenominator; // streaming state BOOL m_bTimestamps; // we have seen at least one timestamp BOOL m_bDiscontinuity; BOOL m_bStreaming; // zero window overhead case input data IMediaBuffer *m_pMediaBuffer; BYTE *m_pData; ULONG m_ulData; ULONG m_ulUsed; BOOL m_bTimestamp; // timestamp on current buffer REFERENCE_TIME m_rtTimestamp; // window overhead case input data BYTE *m_pCircularBuffer; BYTE *m_pAllocAddr; ULONG m_ulBufferAllocated; ULONG m_ulDataHead; ULONG m_ulDataTail; REFERENCE_TIME m_rtTimestampNumerator; // uses the same denominator as duration }; // CPCMDMO - base class for PCM audio transform filters. // Helps non-converting PCM audio transforms with mediatype negotiation. // Based on CFBRDMO - study that first. // // Derived class must implement: // FBRProcess() // Deriver class may implement: // Discontinuity() // default implementaion does nothing // Init() // default implementaion does nothing // GetPCMParams() // default implementation proposes 44100/2/16 // CheckPCMParams() // default implementation accepts any 8/16 bit format // GetWindowParams() // default implementation assumes no lookahead/lookbehind // // This class conveniently provides the following data members accessible // by the derived class: // ULONG m_ulSamplingRate // ULONG m_cChannels // BOOL m_b8bit // #include #include class CPCMDMO : public CFBRDMO { protected: // // implement pure virtual CFBRDMO methods // HRESULT GetInputType(ULONG ulTypeIndex, DMO_MEDIA_TYPE *pmt) { if (ulTypeIndex > 0) return DMO_E_NO_MORE_ITEMS; if (pmt != NULL) { HRESULT hr = GetType(pmt, OutputType()); if (FAILED(hr)) { return hr; } } return S_OK; } HRESULT GetOutputType(ULONG ulTypeIndex, DMO_MEDIA_TYPE *pmt) { if (ulTypeIndex > 0) return DMO_E_NO_MORE_ITEMS; if (pmt != NULL) { HRESULT hr = GetType(pmt, InputType()); if (FAILED(hr)) { return hr; } } return S_OK; } HRESULT CheckInputType(const DMO_MEDIA_TYPE *pmt) { return CheckType(pmt, OutputType()); } HRESULT CheckOutputType(const DMO_MEDIA_TYPE *pmt) { return CheckType(pmt, InputType()); } HRESULT Init() { return NOERROR; } HRESULT Discontinuity() { return NOERROR; } HRESULT GetStreamingParams( DWORD *pdwInputQuantumSize, // in bytes DWORD *pdwOutputQuantumSize, // in bytes DWORD *pdwMaxLookahead, // in input quanta, 0 means no lookahead DWORD *pdwMaxLookbehind, REFERENCE_TIME *prtQuantumDuration, // same for input and output quanta REFERENCE_TIME *prtDurationDenominator // optional, normally 1 ) { // Sanity check: all of this should have been taken care of by base class DMO_MEDIA_TYPE* pmtIn = InputType(); DMO_MEDIA_TYPE* pmtOut = OutputType(); if (!pmtIn || !pmtOut) return DMO_E_TYPE_NOT_SET; if (CheckType(pmtIn, NULL) || CheckType(pmtOut, pmtIn)) return DMO_E_TYPE_NOT_ACCEPTED; WAVEFORMATEX *pWave = (WAVEFORMATEX*)pmtIn->pbFormat; m_b8bit = (pWave->wBitsPerSample == 8); m_cChannels = pWave->nChannels; m_ulSamplingRate = pWave->nSamplesPerSec; *pdwInputQuantumSize = pWave->nBlockAlign; *pdwOutputQuantumSize = pWave->nBlockAlign; *prtQuantumDuration = 10000000; // rt units per sec *prtDurationDenominator = pWave->nSamplesPerSec; GetWindowParams(pdwMaxLookahead, pdwMaxLookbehind); return NOERROR; } protected: // // Methods to be overridden by derived class // // We use this to get lookahead/lookbehind from the derived class virtual void GetWindowParams(DWORD *pdwMaxLookahead, DWORD *pdwMaxLookbehind) { *pdwMaxLookahead = 0; *pdwMaxLookbehind = 0; } // derived class can override these if it has specific requirements virtual void GetPCMParams(BOOL* pb8bit, DWORD* pcChannels, DWORD* pdwSamplesPerSec) { // These values are what the DMO will advertise in its media type. // Specifying them here does not mean that this is the only acceptable // combination - CheckPCMParams() is the ultimate authority on what we will // accept. *pb8bit = FALSE; *pcChannels = 2; *pdwSamplesPerSec = 44100; } virtual BOOL CheckPCMParams(BOOL b8bit, DWORD cChannels, DWORD dwSamplesPerSec) { // Default implementation accepts anything. Override if you have specific // requirements WRT sampling rate, number of channels, or bit depth. return TRUE; } private: // // private helpers // HRESULT GetType(DMO_MEDIA_TYPE* pmt, const DMO_MEDIA_TYPE *pmtOther) { HRESULT hr; // If the other type is set, enumerate that. Otherwise propose 44100/2/16. if (pmtOther) { hr = MoCopyMediaType(pmt, pmtOther); if (FAILED(hr)) { return hr; } return NOERROR; } hr = MoInitMediaType(pmt, sizeof(WAVEFORMATEX)); if (FAILED(hr)) return hr; pmt->majortype = MEDIATYPE_Audio; pmt->subtype = MEDIASUBTYPE_PCM; pmt->formattype = FORMAT_WaveFormatEx; WAVEFORMATEX* pWave = (WAVEFORMATEX*) pmt->pbFormat; pWave->wFormatTag = WAVE_FORMAT_PCM; BOOL b8bit; DWORD cChannels; GetPCMParams(&b8bit, &cChannels, &(pWave->nSamplesPerSec)); (pWave->nChannels) = (unsigned short)cChannels; pWave->wBitsPerSample = b8bit ? 8 : 16; pWave->nBlockAlign = pWave->nChannels * pWave->wBitsPerSample / 8; pWave->nAvgBytesPerSec = pWave->nSamplesPerSec * pWave->nBlockAlign; pWave->cbSize = 0; return NOERROR; } HRESULT CheckType(const DMO_MEDIA_TYPE *pmt, DMO_MEDIA_TYPE *pmtOther) { if (NULL == pmt) { return E_POINTER; } // verify that this is PCM with a WAVEFORMATEX format specifier if ((pmt->majortype != MEDIATYPE_Audio) || (pmt->subtype != MEDIASUBTYPE_PCM) || (pmt->formattype != FORMAT_WaveFormatEx) || (pmt->cbFormat < sizeof(WAVEFORMATEX)) || (pmt->pbFormat == NULL)) return DMO_E_TYPE_NOT_ACCEPTED; // If other type set, accept only if identical to that. Otherwise accept // any standard PCM audio. if (pmtOther) { if (memcmp(pmt->pbFormat, pmtOther->pbFormat, sizeof(WAVEFORMATEX))) return DMO_E_TYPE_NOT_ACCEPTED; } else { WAVEFORMATEX* pWave = (WAVEFORMATEX*)pmt->pbFormat; if ((pWave->wFormatTag != WAVE_FORMAT_PCM) || ((pWave->wBitsPerSample != 8) && (pWave->wBitsPerSample != 16)) || (pWave->nBlockAlign != pWave->nChannels * pWave->wBitsPerSample / 8) || (pWave->nAvgBytesPerSec != pWave->nSamplesPerSec * pWave->nBlockAlign) || !CheckPCMParams((pWave->wBitsPerSample == 8), pWave->nChannels, pWave->nSamplesPerSec)) return DMO_E_TYPE_NOT_ACCEPTED; } return NOERROR; } protected: // format info - the derived class may look at these (but no modify) ULONG m_ulSamplingRate; ULONG m_cChannels; BOOL m_b8bit; }; // // CGenericDMO - generic DMO base class. This is currently the only base // class for DMOs that have multiple inputs or multiple outputs. // // This base class tries to be reasonably generic. The derived class reports // how many streams it supports and describes each stream by calling // CreateInputStreams() and CreateOutputStreams(). Each of these functions // takes an array of STREAMDESCRIPTOR structures, each of which poits to an // array of FORMATENTRY structures. // // This base class uses CInputStream and COutputStream classes (both derived // from CStream) to keep track of input and output stream. However, these // objects are not visible to the derived class - the derived class only sees // stream IDs. // // One limitation of the scheme use here is that the derived class cannot // override the GetType/SetType methods individually for each stream. It must // either (a) live with a static, finite set of types communicated via the // STREAMDESCRIPTOR structure, or (b) override all IMediaObject type methods // and handle type negotiation for all streams itself. // // Processing occurs when the base class calles DoProcess (overridden by the // derived class). DoProcess receives an array of input buffer structs and // an array of output buffer structs. The base class takes care of talking // to IMediaBuffers, so the derived class only sees actual data pointers. // // flags used to communicate with the derived class enum _INPUT_STATUS_FLAGS { INPUT_STATUSF_RESIDUAL // cannot be further processed w/o additional input }; // These are used to pass buffers between this class and the derived class. typedef struct _INPUTBUFFER { BYTE *pData; // [in] - if NULL, the rest are garbage DWORD cbSize; // [in] DWORD cbUsed; // [out] DWORD dwFlags; // [in] - DMO_INPUT_DATA_BUFFERF_XXX DWORD dwStatus; // [out] - INPUT_STATUSF_XXX from above REFERENCE_TIME rtTimestamp; // [in] REFERENCE_TIME rtTimelength; // [in] } INPUTBUFFER, *PINPUTBUFFER; typedef struct _OUTPUTBUFFER { BYTE *pData; // [in] DWORD cbSize; // [in] DWORD cbUsed; // [out] DWORD dwFlags; // [out] - DMO_OUTPUT_DATA_BUFFERF_XXX REFERENCE_TIME rtTimestamp; // [out] REFERENCE_TIME rtTimelength; // [out] } OUTPUTBUFFER, *POUTPUTBUFFER; // Used by derived class to describe the format supported by each stream typedef struct _FORMATENTRY { const GUID *majortype; const GUID *subtype; const GUID *formattype; DWORD cbFormat; BYTE* pbFormat; } FORMATENTRY; // These are used by the derived class to described its streams typedef struct _INPUTSTREAMDESCRIPTOR { DWORD cFormats; FORMATENTRY *pFormats; DWORD dwMinBufferSize; BOOL bHoldsBuffers; DWORD dwMaxLookahead; // used if HOLDS_BUFFERS set } INPUTSTREAMDESCRIPTOR; typedef struct _OUTPUTSTREAMDESCRIPTOR { DWORD cFormats; FORMATENTRY *pFormats; DWORD dwMinBufferSize; } OUTPUTSTREAMDESCRIPTOR; // Common input/output stream stuff class CStream { public: DMO_MEDIA_TYPE m_MediaType; BOOL m_bEOS; BOOL m_bTypeSet; DWORD m_cFormats; FORMATENTRY *m_pFormats; DWORD m_dwMinBufferSize; // Should really pass in a format type list CStream() { MoInitMediaType(&m_MediaType, 0); m_bTypeSet = FALSE; Flush(); } ~CStream() { MoFreeMediaType(&m_MediaType); } HRESULT Flush() { m_bEOS = FALSE; return NOERROR; } HRESULT StreamInfo(unsigned long *pdwFlags) { if (pdwFlags == NULL) { return E_POINTER; } *pdwFlags = 0; return S_OK; } HRESULT GetType(ULONG ulTypeIndex, DMO_MEDIA_TYPE *pmt) { if (ulTypeIndex >= m_cFormats) { return E_INVALIDARG; } // Just return our types MoInitMediaType(pmt, m_pFormats[ulTypeIndex].cbFormat); pmt->majortype = *m_pFormats[ulTypeIndex].majortype; pmt->subtype = *m_pFormats[ulTypeIndex].subtype; pmt->formattype = *m_pFormats[ulTypeIndex].formattype; memcpy(pmt->pbFormat, m_pFormats[ulTypeIndex].pbFormat, m_pFormats[ulTypeIndex].cbFormat); return S_OK; } HRESULT GetCurrentType(DMO_MEDIA_TYPE *pmt) { if (NULL == pmt) { return E_POINTER; } if (m_bTypeSet) { // check success MoCopyMediaType(pmt, &(m_MediaType)); return S_OK; } else return DMO_E_TYPE_NOT_SET; } HRESULT SetType(const DMO_MEDIA_TYPE *pmt, DWORD dwFlags) { // Need to check this HRESULT hr = CheckType(pmt, 0); if (FAILED(hr)) { return hr; } if (dwFlags & DMO_SET_TYPEF_TEST_ONLY) { return NOERROR; // check konly } // check success MoCopyMediaType(&m_MediaType, pmt); m_bTypeSet = TRUE;; return S_OK; } HRESULT CheckType(const DMO_MEDIA_TYPE *pmt, DWORD dwFlags) { if (pmt == NULL) { return E_POINTER; } //if (dwFlags & ~DMO_SET_TYPEF_NOT_PARTIAL) // return E_INVALIDARG; // Default - check GUIDs bool bMatched = false; for (DWORD i = 0; i < m_cFormats; i++) { const FORMATENTRY *pFormat = &(m_pFormats[i]); if (pmt->majortype == *(pFormat->majortype) && pmt->subtype == *(pFormat->subtype) && pmt->formattype == *(pFormat->formattype)) { bMatched = true; break; } } if (bMatched) { return S_OK; } else { return DMO_E_INVALIDTYPE; } } HRESULT SizeInfo(ULONG *plSize, ULONG *plAlignment) { if (plSize == NULL || plAlignment == NULL) { return E_POINTER; } *plAlignment = 1; *plSize = m_dwMinBufferSize; return S_OK; } }; // Input stream specific stuff class CInputStream : public CStream { public: BOOL m_bHoldsBuffers; DWORD m_dwMaxLookahead; // used if HOLDS_BUFFERS set // Current input sample IMediaBuffer *m_pMediaBuffer; DWORD m_dwFlags; // discontinuity, etc. REFERENCE_TIME m_rtTimestamp; REFERENCE_TIME m_rtTimelength; BYTE *m_pData; DWORD m_cbSize; DWORD m_cbUsed; // residual BYTE *m_pbResidual; DWORD m_cbResidual; DWORD m_cbResidualBuffer; // temporary buffer for handling the residual BYTE *m_pbTemp; HRESULT Flush() { if (m_pMediaBuffer) { m_pMediaBuffer->Release(); m_pMediaBuffer = NULL; } return CStream::Flush(); } CInputStream() { m_pMediaBuffer = NULL; m_pbResidual = NULL; m_pbTemp = NULL; } ~CInputStream() { if (m_pMediaBuffer) m_pMediaBuffer->Release(); if (m_pbResidual) delete[] m_pbResidual; } HRESULT StreamInfo(DWORD *pdwFlags) { HRESULT hr = CStream::StreamInfo(pdwFlags); if (FAILED(hr)) return hr; if (m_bHoldsBuffers) *pdwFlags |= DMO_INPUT_STREAMF_HOLDS_BUFFERS; return NOERROR; } HRESULT Init(INPUTSTREAMDESCRIPTOR *pDescriptor) { m_cFormats = pDescriptor->cFormats; m_pFormats = pDescriptor->pFormats; m_dwMinBufferSize = pDescriptor->dwMinBufferSize; m_bHoldsBuffers = pDescriptor->bHoldsBuffers; m_dwMaxLookahead = pDescriptor->dwMaxLookahead; // Just in case Init is called multiple times: // delete any preexisting stuff. if (m_pMediaBuffer) { m_pMediaBuffer->Release(); m_pMediaBuffer = NULL; } if (m_pbResidual) { delete[] m_pbResidual; m_pbResidual = NULL; } m_cbResidual = 0; m_cbResidualBuffer = m_dwMinBufferSize * 2; // enough ? m_pbResidual = new BYTE[m_cbResidualBuffer]; return NOERROR; } HRESULT InputStatus(DWORD *pdwStatus) { // objects that hold buffers must implement InputStatus themselves assert(!m_bHoldsBuffers); *pdwStatus = 0; if (!m_pMediaBuffer) *pdwStatus |= DMO_INPUT_STATUSF_ACCEPT_DATA; return NOERROR; } HRESULT Deliver( IMediaBuffer *pBuffer, // [in], must not be NULL DWORD dwFlags, // [in] - discontinuity, timestamp, etc. REFERENCE_TIME rtTimestamp, // [in], valid if flag set REFERENCE_TIME rtTimelength // [in], valid if flag set ) { if (!pBuffer) return E_POINTER; // objects that hold buffers must implement Deliver themselves assert(!m_bHoldsBuffers); DWORD dwStatus = 0; InputStatus(&dwStatus); if (!(dwStatus & DMO_INPUT_STATUSF_ACCEPT_DATA)) return DMO_E_NOTACCEPTING; assert(!m_pMediaBuffer); // can't hold multiple buffers //Deal with the IMediaBuffer HRESULT hr; hr = pBuffer->GetBufferAndLength(&m_pData, &m_cbSize); if (FAILED(hr)) return hr; if (!m_cbSize) // empty buffer return S_FALSE; // no data pBuffer->AddRef(); m_pMediaBuffer = pBuffer; m_dwFlags = dwFlags; m_rtTimestamp = rtTimestamp; m_rtTimelength = rtTimelength; m_cbUsed = 0; return NOERROR; } // // Fetch data from the currently held IMediaBuffer plus any residual // HRESULT PrepareInputBuffer(INPUTBUFFER *pBuffer) { // Q.: do we even have any data to give it ? if (m_pMediaBuffer) { // Is there a residual we need to feed first ? if (m_cbResidual) { // Yes, prepend the residual to the new input // If we have used some of the input buffer by now, we // should have also used up any residual with that. assert(m_cbUsed == 0); // compute how many bytes total we are going to send pBuffer->cbSize = m_cbResidual + m_cbSize; // Make sure we have at least dwMinBufferSize bytes of data. // We really should - the input buffer alone ought to be at // least that big. assert(pBuffer->cbSize > m_dwMinBufferSize); // Is the residual buffer big enough to hold the residual plus // all of the new buffer ? if (pBuffer->cbSize <= m_cbResidualBuffer) { // Yes - wonderful, we can use the residual buffer memcpy(m_pbResidual + m_cbResidual, m_pData, m_cbSize); pBuffer->pData = m_pbResidual; } else { // No - allocate a sufficiently large temporary buffer. // This is supposed to be a rare case. m_pbTemp = new BYTE[pBuffer->cbSize]; if (m_pbTemp == NULL) return E_OUTOFMEMORY; // copy the residual memcpy(m_pbTemp, m_pbResidual, m_cbResidual); // append the new buffer memcpy(m_pbTemp + m_cbResidual, m_pData, m_cbSize); // set the buffer pointer to our temp buffer pBuffer->pData = m_pbTemp; } // is this the correct way to handle timestamps & // discontinuities when handling a residual ? pBuffer->dwFlags = 0; } else { // no residual pBuffer->pData = m_pData + m_cbUsed; pBuffer->cbSize = m_cbSize - m_cbUsed; pBuffer->dwFlags = m_dwFlags; pBuffer->rtTimestamp = m_rtTimestamp; pBuffer->rtTimelength= m_rtTimelength; } pBuffer->cbUsed = 0; // derived class should set this pBuffer->dwStatus = 0; // derived class should set this } else { pBuffer->pData = NULL; pBuffer->cbSize = 0; } return NOERROR; } // // Save any residual and release the IMediaBuffer as appropriate. // Returns TRUE if there is enough data left to call ProcesInput again. // BOOL PostProcessInputBuffer(INPUTBUFFER *pBuffer) { BOOL bRet = FALSE; // did we even give this stream anything ? if (m_pMediaBuffer) { // Yes, but did it eat any of it ? if (pBuffer->cbUsed) { // Did we even get past the residual if (pBuffer->cbUsed > m_cbResidual) { // Yes - reflect this in the current buffer's cbUsed. m_cbUsed += (pBuffer->cbUsed - m_cbResidual); m_cbResidual = 0; } else { // No - just subtract from the residual. // This is a rather silly case. m_cbResidual -= pBuffer->cbUsed; memmove(m_pbResidual, m_pbResidual + pBuffer->cbUsed, m_cbResidual); } } // Is there enough left to feed again the next time ? if ((m_cbSize - m_cbUsed < m_dwMinBufferSize) || (pBuffer->dwStatus & INPUT_STATUSF_RESIDUAL)) { // No - copy the residual and release the buffer memcpy(m_pbResidual, m_pData + m_cbUsed, m_cbSize - m_cbUsed); m_cbResidual = pBuffer->cbSize - pBuffer->cbUsed; m_pMediaBuffer->Release(); m_pMediaBuffer = NULL; } else { // Yes - need another Process call to eat remaining input bRet = TRUE; } // Free any temporary buffer we may have used - rare case if (m_pbTemp) { delete[] m_pbTemp; m_pbTemp = NULL; } } return bRet; } HRESULT Discontinuity() { // implement // m_bDiscontinuity = TRUE; return NOERROR; } HRESULT SizeInfo(ULONG *pulSize, ULONG *pulMaxLookahead, ULONG *pulAlignment) { HRESULT hr = CStream::SizeInfo(pulSize, pulAlignment); if (FAILED(hr)) return hr; if (m_bHoldsBuffers) *pulMaxLookahead = m_dwMaxLookahead; else *pulMaxLookahead = *pulSize; return NOERROR; } }; // Output stream specific stuff class COutputStream : public CStream { public: BOOL m_bIncomplete; DWORD m_cbAlreadyUsed; // temp per-stream variable used during Process HRESULT Init(OUTPUTSTREAMDESCRIPTOR *pDescriptor) { m_cFormats = pDescriptor->cFormats; m_pFormats = pDescriptor->pFormats; m_dwMinBufferSize = pDescriptor->dwMinBufferSize; return NOERROR; } // // Initialize the OUTPUTBUFFER struct with info from the IMediaBuffer // HRESULT PrepareOutputBuffer(OUTPUTBUFFER *pBuffer, IMediaBuffer *pMediaBuffer, BOOL bNewInput) { // // See if the caller supplied an output buffer // if (pMediaBuffer == NULL) { // This is allowed to be NULL only if (1) the object did not set // the INCOMPLETE flag for this stream during the last Process // call, and (2) no new input data has been supplied to the object // since the last Process call. if (bNewInput) return E_POINTER; if (m_bIncomplete) return E_POINTER; // ok - initialize assuming no buffer pBuffer->cbSize = 0; pBuffer->pData = NULL; } else { // the IMediaBuffer is not NULL - deal with it HRESULT hr; hr = pMediaBuffer->GetMaxLength(&pBuffer->cbSize); if (FAILED(hr)) return hr; hr = pMediaBuffer->GetBufferAndLength( &(pBuffer->pData), &(m_cbAlreadyUsed)); if (FAILED(hr)) return hr; // Check current size - should we even bother with this ? if (m_cbAlreadyUsed) { if (m_cbAlreadyUsed >= pBuffer->cbSize) return E_INVALIDARG; // buffer already full ?!? pBuffer->cbSize -= m_cbAlreadyUsed; pBuffer->pData += m_cbAlreadyUsed; } } // It is really the derived class's job to set these, but we // will be nice to it and initialize them anyway just in case. pBuffer->cbUsed = 0; pBuffer->dwFlags = 0; return NOERROR; } // // Copy the OUTPUTBUFFER back into the DMO_OUTPUT_DATA_BUFFER (yawn) // void PostProcessOutputBuffer(OUTPUTBUFFER *pBuffer, DMO_OUTPUT_DATA_BUFFER *pDMOBuffer, BOOL bForceIncomplete) { assert(pBuffer->cbUsed <= pBuffer->cbSize); if (pDMOBuffer->pBuffer) pDMOBuffer->pBuffer->SetLength(pBuffer->cbUsed + m_cbAlreadyUsed); pDMOBuffer->dwStatus = pBuffer->dwFlags; pDMOBuffer->rtTimestamp = pBuffer->rtTimestamp; pDMOBuffer->rtTimelength = pBuffer->rtTimelength; // Even if the derived class did not set INCOMPLETE, we may need to // set it anyway if some input buffer we are holding still has // enough data to call Process() again. if (bForceIncomplete) pDMOBuffer->dwStatus |= DMO_OUTPUT_DATA_BUFFERF_INCOMPLETE; // remember this output stream's INCOMPLETE state if (pDMOBuffer->dwStatus & DMO_OUTPUT_DATA_BUFFERF_INCOMPLETE) m_bIncomplete = TRUE; else m_bIncomplete = FALSE; } }; // Code that goes at the beginning of every IMediaObject method #define INPUT_STREAM_PROLOGUE \ CDMOAutoLock l(&m_cs); \ if (ulInputStreamIndex >= m_nInputStreams) \ return DMO_E_INVALIDSTREAMINDEX; \ CInputStream *pStream = &m_pInputStreams[ulInputStreamIndex] #define OUTPUT_STREAM_PROLOGUE \ CDMOAutoLock l(&m_cs); \ if (ulOutputStreamIndex >= m_nOutputStreams) \ return DMO_E_INVALIDSTREAMINDEX; \ COutputStream *pStream = &m_pOutputStreams[ulOutputStreamIndex] class CGenericDMO : public IMediaObject { public: CGenericDMO() { #ifdef DMO_NOATL InitializeCriticalSection(&m_cs); #endif m_nInputStreams = 0; m_nOutputStreams = 0; } #ifdef DMO_NOATL ~CGenericDMO() { DeleteCriticalSection(&m_cs); } #endif public: // // Implement IMediaObject methods // STDMETHODIMP GetInputStreamInfo(ULONG ulInputStreamIndex, DWORD *pdwFlags) { INPUT_STREAM_PROLOGUE; return pStream->StreamInfo(pdwFlags); } STDMETHODIMP GetOutputStreamInfo(ULONG ulOutputStreamIndex, DWORD *pdwFlags) { OUTPUT_STREAM_PROLOGUE; return pStream->StreamInfo(pdwFlags); } STDMETHODIMP GetInputType(ULONG ulInputStreamIndex, ULONG ulTypeIndex, DMO_MEDIA_TYPE *pmt) { INPUT_STREAM_PROLOGUE; return pStream->GetType(ulTypeIndex, pmt); } STDMETHODIMP GetOutputType(ULONG ulOutputStreamIndex, ULONG ulTypeIndex, DMO_MEDIA_TYPE *pmt) { OUTPUT_STREAM_PROLOGUE; return pStream->GetType(ulTypeIndex, pmt); } STDMETHODIMP GetInputCurrentType(ULONG ulInputStreamIndex, DMO_MEDIA_TYPE *pmt) { INPUT_STREAM_PROLOGUE; return pStream->GetCurrentType(pmt); } STDMETHODIMP GetOutputCurrentType(ULONG ulOutputStreamIndex, DMO_MEDIA_TYPE *pmt) { OUTPUT_STREAM_PROLOGUE; return pStream->GetCurrentType(pmt); } STDMETHODIMP GetInputSizeInfo(ULONG ulInputStreamIndex, ULONG *pulSize, ULONG *pcbMaxLookahead, ULONG *pulAlignment) { INPUT_STREAM_PROLOGUE; return pStream->SizeInfo(pulSize, pcbMaxLookahead, pulAlignment); } STDMETHODIMP GetOutputSizeInfo(ULONG ulOutputStreamIndex, ULONG *pulSize, ULONG *pulAlignment) { OUTPUT_STREAM_PROLOGUE; return pStream->SizeInfo(pulSize, pulAlignment); } STDMETHODIMP SetInputType(ULONG ulInputStreamIndex, const DMO_MEDIA_TYPE *pmt, DWORD dwFlags) { INPUT_STREAM_PROLOGUE; return pStream->SetType(pmt, dwFlags); } STDMETHODIMP SetOutputType(ULONG ulOutputStreamIndex, const DMO_MEDIA_TYPE *pmt, DWORD dwFlags) { OUTPUT_STREAM_PROLOGUE; return pStream->SetType(pmt, dwFlags); } STDMETHODIMP GetInputStatus( ULONG ulInputStreamIndex, DWORD *pdwStatus ) { INPUT_STREAM_PROLOGUE; return pStream->InputStatus(pdwStatus); } STDMETHODIMP GetInputMaxLatency(unsigned long ulInputStreamIndex, REFERENCE_TIME *prtLatency) { // I don't know what to do with this right now. // Punt to the derived class ? return E_NOTIMPL; } STDMETHODIMP SetInputMaxLatency(unsigned long ulInputStreamIndex, REFERENCE_TIME rtLatency) { return E_NOTIMPL; } STDMETHODIMP ProcessInput( DWORD ulInputStreamIndex, IMediaBuffer *pBuffer, // [in], must not be NULL DWORD dwFlags, // [in] - discontinuity, timestamp, etc. REFERENCE_TIME rtTimestamp, // [in], valid if flag set REFERENCE_TIME rtTimelength // [in], valid if flag set ) { INPUT_STREAM_PROLOGUE; return pStream->Deliver(pBuffer, dwFlags, rtTimestamp, rtTimelength); } STDMETHODIMP Discontinuity(ULONG ulInputStreamIndex) { INPUT_STREAM_PROLOGUE; return pStream->Discontinuity(); } STDMETHODIMP Flush() { CDMOAutoLock l(&m_cs); // Flush all the streams ULONG i; for (i = 0; i < m_nInputStreams; i++) { m_pInputStreams[i].Flush(); } for (i = 0; i < m_nOutputStreams; i++) { m_pOutputStreams[i].Flush(); } return S_OK; } STDMETHODIMP AllocateStreamingResources() {return S_OK;} STDMETHODIMP FreeStreamingResources() {return S_OK;} STDMETHODIMP GetStreamCount(unsigned long *pulNumberOfInputStreams, unsigned long *pulNumberOfOutputStreams) { CDMOAutoLock l(&m_cs); if (pulNumberOfInputStreams == NULL || pulNumberOfOutputStreams == NULL) { return E_POINTER; } *pulNumberOfInputStreams = m_nInputStreams; *pulNumberOfOutputStreams = m_nOutputStreams; return S_OK; } STDMETHODIMP ProcessOutput( DWORD dwReserved, DWORD ulOutputBufferCount, DMO_OUTPUT_DATA_BUFFER *pOutputBuffers, DWORD *pdwStatus) { CDMOAutoLock l(&m_cs); if (ulOutputBufferCount != m_nOutputStreams) return E_INVALIDARG; HRESULT hr; DWORD c; // Prepare the input buffers for (c = 0; c < m_nInputStreams; c++) { // objects that hold buffers must implement Process themselves assert(!m_pInputStreams[c].m_bHoldsBuffers); hr = m_pInputStreams[c].PrepareInputBuffer(&m_pInputBuffers[c]); if (FAILED(hr)) return hr; } // // Prepare the output buffers // for (c = 0; c < m_nOutputStreams; c++) { hr = m_pOutputStreams[c].PrepareOutputBuffer(&m_pOutputBuffers[c], pOutputBuffers[c].pBuffer, m_bNewInput); if (FAILED(hr)) return hr; } hr = DoProcess(m_pInputBuffers,m_pOutputBuffers); if (FAILED(hr)) return hr; // don't just "return hr", do something ! // post-process input buffers BOOL bSomeInputStillHasData = FALSE; for (c = 0; c < m_nInputStreams; c++) { if (m_pInputStreams[c].PostProcessInputBuffer(&m_pInputBuffers[c])) bSomeInputStillHasData = TRUE; } // post-process output buffers for (c = 0; c < m_nOutputStreams; c++) { m_pOutputStreams[c].PostProcessOutputBuffer(&m_pOutputBuffers[c], &pOutputBuffers[c], bSomeInputStillHasData); } m_bNewInput = FALSE; return NOERROR; } protected: // // These are called by the derived class at initialization time // HRESULT CreateInputStreams(INPUTSTREAMDESCRIPTOR *pStreams, DWORD cStreams) { CDMOAutoLock l(&m_cs); if (pStreams == NULL) { return E_POINTER; } m_pInputStreams = new CInputStream[cStreams]; if (m_pInputStreams == NULL) { return E_OUTOFMEMORY; } DWORD c; for (c = 0; c < cStreams; c++) { HRESULT hr = m_pInputStreams[c].Init(&(pStreams[c])); if (FAILED(hr)) { delete[] m_pInputStreams; return hr; } } m_pInputBuffers = new INPUTBUFFER[cStreams]; if (!m_pInputBuffers) { delete[] m_pInputStreams; return E_OUTOFMEMORY; } m_nInputStreams = cStreams; return NOERROR; } HRESULT CreateOutputStreams(OUTPUTSTREAMDESCRIPTOR *pStreams, DWORD cStreams) { CDMOAutoLock l(&m_cs); if (pStreams == NULL) { return E_POINTER; } m_pOutputStreams = new COutputStream[cStreams]; if (m_pOutputStreams == NULL) { return E_OUTOFMEMORY; } DWORD c; for (c = 0; c < cStreams; c++) { HRESULT hr = m_pOutputStreams[c].Init(&(pStreams[c])); if (FAILED(hr)) { delete[] m_pOutputStreams; return hr; } } m_pOutputBuffers = new OUTPUTBUFFER[cStreams]; if (!m_pOutputBuffers) { delete[] m_pOutputStreams; return E_OUTOFMEMORY; } m_nOutputStreams = cStreams; return NOERROR; } virtual HRESULT DoProcess(INPUTBUFFER*, OUTPUTBUFFER *) = 0; private: ULONG m_nInputStreams; CInputStream* m_pInputStreams; ULONG m_nOutputStreams; COutputStream* m_pOutputStreams; INPUTBUFFER* m_pInputBuffers; OUTPUTBUFFER* m_pOutputBuffers; BOOL m_bNewInput; #ifdef DMO_NOATL CRITICAL_SECTION m_cs; #else CComAutoCriticalSection m_cs; #endif }; #endif // __DMOBASE_H__