/* Copyright (c) 1998-1999 Microsoft Corporation */ /* NOTE: regarding AMOVIE code 1. Timing info is messed up (as noted by the owner, robin speed himself) 2. Atleast a few obvious bugs - mostly in the derived classes, but a few in the base classes as well. The current approach is to try and cover it up by over-riding the method ZoltanS note: and now that the amovie code is folded into this directory, I am trying to collapse the humongous class hierarchy and just replace the original methods with the fixed overrides */ #include "stdafx.h" #include "atlconv.h" #include "termmgr.h" #include "medpump.h" #include "meterf.h" // for some reason ddstream.lib requires this .. TO DO ** (get rid of it) #ifdef DBG BOOL bDbgTraceFunctions; BOOL bDbgTraceInterfaces; BOOL bDbgTraceTimes; #endif // DBG #ifdef DBG #include #endif // DBG // static variables // implements single thread pump for the write media streaming terminal // filters. it creates a thread if necessary when a write terminal registers // itself (in commit). the filter signals its wait handle in decommit, // causing the thread to wake up and remove the filter from its data // structures. the thread returns when there are no more filters to service // ZoltanS: now a pool of pump threads CMediaPumpPool CMediaTerminalFilter::ms_MediaPumpPool; // checks if the two am media type structs are same // a simple equality of struct won't do here BOOL IsSameAMMediaType( IN const AM_MEDIA_TYPE *pmt1, IN const AM_MEDIA_TYPE *pmt2 ) { // we don't expect the the two pointers to be null TM_ASSERT(NULL != pmt1); TM_ASSERT(NULL != pmt2); // if the two pointer values are same, there is nothing more // to check if (pmt1 == pmt2) return TRUE; // each of the members of the AM_MEDIA_TYPE struct must be // the same (majortype, subtype, formattype // and (cbFormat, pbFormat)) if ( (pmt1->majortype != pmt2->majortype) || (pmt1->subtype != pmt2->subtype) || (pmt1->formattype != pmt2->formattype) ) // || (pmt1->cbFormat != pmt2->cbFormat) ) { return FALSE; } // if the pbFormat pointer is null for either, they can't be // the same if ( (NULL == pmt1->pbFormat) || (NULL == pmt2->pbFormat) ) { return FALSE; } DWORD dwSize = ( pmt1->cbFormat < pmt2->cbFormat ) ? pmt1->cbFormat : pmt2->cbFormat; // we don't handle anything other than waveformatex or videoinfo, // and as these two don't have any member pointers, a bitwise // comparison is sufficient to check for equality if ( (FORMAT_WaveFormatEx == pmt1->formattype) || (FORMAT_VideoInfo == pmt1->formattype) ) { return !memcmp( pmt1->pbFormat, pmt2->pbFormat, dwSize ); } return FALSE; } ///////////////////////////////////////////////////////////////////////////// // // Equal // // this is a helper method that returns TRUE if the properties are identical // BOOL Equal(const ALLOCATOR_PROPERTIES *pA, const ALLOCATOR_PROPERTIES *pB) { if ( pA->cBuffers != pB->cBuffers ) { return FALSE; } if ( pA->cbBuffer != pB->cbBuffer ) { return FALSE; } if ( pA->cbPrefix != pB->cbPrefix ) { return FALSE; } if ( pA->cbPrefix != pB->cbPrefix ) { return FALSE; } if ( pA->cbAlign != pB->cbAlign ) { return FALSE; } return TRUE; } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// // // Helper function to test if two sets of allocator properties are // significantly different. // BOOL AllocatorPropertiesDifferSignificantly( const ALLOCATOR_PROPERTIES * pRequested, const ALLOCATOR_PROPERTIES * pActual ) { if ( pActual->cBuffers != pRequested->cBuffers ) { return TRUE; } if ( pActual->cbBuffer != pRequested->cbBuffer ) { return TRUE; } // // we do not care about alignment - cbAlign // if ( pActual->cbPrefix != pRequested->cbPrefix ) { return TRUE; } return FALSE; } // free the allocated member variables // virtual CMediaTerminalFilter::~CMediaTerminalFilter( ) { LOG((MSP_TRACE, "CMediaTerminalFilter::~CMediaTerminalFilter called")); // if memory was allocated for the media type, release it // it checks for NULL == m_pSuggestedMediaType DeleteMediaType(m_pSuggestedMediaType); // Moved from base class: SetState(State_Stopped); // Make sure we're decommitted and pump is dead } // calls the IAMMediaStream::Initialize(NULL, 0, PurposeId, StreamType), // sets certain member variables // ex. m_pAmovieMajorType HRESULT CMediaTerminalFilter::Init( IN REFMSPID PurposeId, IN const STREAM_TYPE StreamType, IN const GUID &AmovieMajorType ) { LOG((MSP_TRACE, "CMediaTerminalFilter::Init[%p] (%p, %p, %p) called", this, &PurposeId, &StreamType, &AmovieMajorType)); HRESULT hr; // initialize the CStream by calling IAMMediaStream::Initialize hr = Initialize( NULL, (StreamType == STREAMTYPE_READ) ? AMMSF_STOPIFNOSAMPLES : 0, PurposeId, StreamType ); BAIL_ON_FAILURE(hr); // set member variables m_bIsAudio = (MSPID_PrimaryAudio == PurposeId) ? TRUE : FALSE; m_pAmovieMajorType = &AmovieMajorType; SetDefaultAllocatorProperties(); LOG((MSP_TRACE, "CMediaTerminalFilter::Init - succeeded")); return S_OK; } // CMediaPump calls this to get the next filled buffer to pass downstream // for audio filters, this method is also responsible for waiting // for 20ms and sending the data in one filled buffer HRESULT CMediaTerminalFilter::GetFilledBuffer( OUT IMediaSample *&pMediaSample, OUT DWORD &WaitTime ) { LOG((MSP_TRACE, "CMediaTerminalFilter::GetFilledBuffer[%p] ([out]pMediaSample=%p, [out]WaitTime=%lx) called", this, pMediaSample, WaitTime)); HRESULT hr = S_OK; Lock(); if ( ! m_bCommitted ) { Unlock(); return VFW_E_NOT_COMMITTED; } if ( m_pSampleBeingFragmented == NULL ) { // get a sample CSample *pSample = m_pFirstFree; // if no sample, someone must have forced termination // of the sample(s) which caused signaling of the wait // event, or it must have decommitted and committed again if (NULL == pSample) { // we'll wait for someone to add a sample to the pool m_lWaiting = 1; Unlock(); return S_FALSE; } m_pFirstFree = pSample->m_pNextFree; if (m_pFirstFree) m_pFirstFree->m_pPrevFree = NULL; else m_pLastFree = NULL; pSample->m_pNextFree = NULL; // Just to be tidy TM_ASSERT(pSample->m_Status == MS_S_PENDING); CHECKSAMPLELIST // all samples in the pool have a refcnt on them, this // must be released before m_pSampleBeingFragmented is set to NULL m_pSampleBeingFragmented = (CUserMediaSample *)pSample; // note the current time only for audio samples m_pSampleBeingFragmented->BeginFragment(m_bIsAudio); } // // Implementations diverge here depending on whether we are using the // sample queue (CNBQueue; m_bUsingMyAllocator == FALSE) and fragmenting // samples by reference, or using a downstream allocator and copying // samples. // BOOL fDone; if ( m_bUsingMyAllocator ) { hr = FillMyBuffer( pMediaSample, // OUT WaitTime, // OUT & fDone // OUT ); } else { hr = FillDownstreamAllocatorBuffer( pMediaSample, // OUT WaitTime, // OUT & fDone // OUT ); } // // S_OK means everything is ok, and we need to return the wait time, // update m_pSampleBeingFragmented, and addref the IMediaSample. // other success codes (or failure codes) mean return immediately. // if ( hr != S_OK ) { Unlock(); return hr; } // return the time to wait in milliseconds if (m_bIsAudio) { WaitTime = m_pSampleBeingFragmented->GetTimeToWait(m_AudioDelayPerByte); } else { WaitTime = m_VideoDelayPerFrame; } // // ZoltanS: make the second sample after we start playing // a little early, to account for a bit of jitter in delivery to // the waveout filter, and also to "prime" buggy wave drivers // such as Dialogic, which need a backlog to operate properly. // This is fine for IP as long as the timestamps are set correctly // (note that the most advancement we ever do is one packet's worth // on the second packet only). // // This needs to be done before SetTime, because SetTime // changes m_rtLastSampleEndedAt. // const DWORD SECOND_SAMPLE_EARLINESS = 500; if ( m_rtLastSampleEndedAt == 0 ) { LOG((MSP_TRACE, "CMediaTerminalFilter::GetFilledBuffer - " "this is the first sample; making the next sample %d ms early", SECOND_SAMPLE_EARLINESS)); if ( WaitTime < SECOND_SAMPLE_EARLINESS ) { WaitTime = 0; } else { WaitTime -= SECOND_SAMPLE_EARLINESS; } } // // if the sample came in late, set discontinuity flag. this flag may be // used by the downstream filters in their dejitter algorithms. // // this needs to be called before settime (because settime will reset // m_rtLastSampleDuration to duration of the _current_ sample // hr = SetDiscontinuityIfNeeded(pMediaSample); if ( FAILED(hr) ) { // // not fatal, log and continue // LOG((MSP_ERROR, "CMediaTerminalFilter::GetFilledBuffer - SetDiscontinuityIfNeeded failed. " "hr = 0x%lx", hr)); } // // put a timestamp on the sample // hr = SetTime( pMediaSample ); if ( FAILED(hr) ) { LOG((MSP_ERROR, "CMediaTerminalFilter::GetFilledBuffer() " "Failed putting timestamp on the sample; hr = 0x%lx", hr)); } // if fDone, we reached the end of the buffer we were fragmenting if ( fDone ) { ((IStreamSample *)m_pSampleBeingFragmented)->Release(); m_pSampleBeingFragmented = NULL; } Unlock(); LOG((MSP_TRACE, "CMediaTerminalFilter::GetFilledBuffer(%p, %u) succeeded", pMediaSample, WaitTime)); return S_OK; } ////////////////////////////////////////////////////////////////////////////// // // FillDownstreamAllocatorBuffer // // This is called in GetFilledBuffer when we are using the downstream // allocator. It gets a sample from our outgoing sample pool and copies the // data // // Note that GetBuffer can block here, which means we are just as hosed as if // Receive blocks (but there is no deadlock situation possible as is remedied // in FillMyBuffer). // HRESULT CMediaTerminalFilter::FillDownstreamAllocatorBuffer( OUT IMediaSample *& pMediaSample, OUT DWORD & WaitTime, OUT BOOL * pfDone ) { // // Get a buffer from the downstream allocator. // TM_ASSERT( ! m_bUsingMyAllocator ); HRESULT hr; hr = m_pAllocator->GetBuffer( & pMediaSample, NULL, // no start time (used by video renderer only) NULL, // no no end time (used by video renderer only) 0 // no flags (could be: not a sync point, prev frame skipped) ); if ( FAILED(hr) ) { return hr; } // // Got a free output buffer, so put some data in it. // // if audio, fragment the buffer else, pass the whole buffer // (no fragmentation for video as video data is frame based) // // CUserMediaSample::CopyFragment is just like CUserMediaSample::Fragment // except that the outgoing sample is an IMediaSample interface instead // of our own CQueueMediaSample. // hr = m_pSampleBeingFragmented->CopyFragment( m_bIsAudio, // allow fragmentation if audio, not if video (IN) m_AllocProps.cbBuffer, // outgoing buffer size (IN) pMediaSample, // outgoing sample (IN) *pfDone // done with user sample? reference (OUT) ); // // We've filled out pMediaSample. The caller fills out the wait time if the // result of this method is S_OK. // return hr; } ////////////////////////////////////////////////////////////////////////////// // // FillMyBuffer // // This is called in GetFilledBuffer when we are using our own allocator. It // gets a sample from our outgoing sample pool. If no sample is available // right now, it sets the wait time and returns a special success code. // If a sample is available, it sets it up to point to an appropriate chunk // of the "fragmented" source buffer. // HRESULT CMediaTerminalFilter::FillMyBuffer( OUT IMediaSample *& pMediaSample, OUT DWORD & WaitTime, OUT BOOL * pfDone ) { // // Try to dequeue an output sample to send to. FALSE tells it to return NULL // rather than blocking if there is nothing on the queue. If there is nothing // on the queue, that means that the fact that we are holding the lock is // preventing the sample from being returned to the queue. We've only seen // this happen when ksproxy is used as the MSP's transport, since ksproxy // releases its samples asynchronously (on a separate Io completion thread). // With other transports, samples are released asynchronously each time, // and so we never get this situation. // // If the transport is truly messed up and no samples are completing, then // there is the additional consideration that we don't want to use up 100% // CPU and we want to be able to service other filters (this is on the // pump thread, serving up to 63 filters per thread). So rather than // retrying immediately we need to set a short dormancy time (for the // PumpMainLoop wait) before we try again. // CQueueMediaSample * pQueueSample; pQueueSample = m_SampleQueue.DeQueue( FALSE ); if ( pQueueSample == NULL ) { // // After return we will unlock, allowing async // FinalMediaSampleRelease to release sample. // // // Let's try again in three milliseconds. This is short enough not // to cause a noticeable quality degradation, and long enough to // prevent eating 100% CPU when the transport is broken and does not // return samples. // WaitTime = 3; LOG((MSP_TRACE, "CMediaTerminalFilter::FillMyBuffer - no available " "output samples in queue; returning " "VFW_S_NO_MORE_ITEMS")); return VFW_S_NO_MORE_ITEMS; } // // Got a free output buffer, so put some data in it. // // if audio, fragment the buffer else, pass the whole buffer // (no fragmentation for video as video data is frame based) // m_pSampleBeingFragmented->Fragment( m_bIsAudio, // allow fragmentation if audio, not if video m_AllocProps.cbBuffer, // outgoing buffer size *pQueueSample, // outgoing sample -- reference (IN parameter) *pfDone // done with user sample? -- reference (OUT parameter) ); // // CODEWORK: to support combining as well as fragmentation, we would need to // (1) modify CUserMediaSample::Fragment and code underneath to append to outgoing // buffer (copy into it) -- this may get interesting considering we are dealing // with CQueueMediaSample as the outgoing samples! // (2) introduce a loop here in GetFilledBuffer -- keep getting more input samples // until the output sample is full or the input queue has been exhausted. // Interesting case is what to do if the input queue has been exhausted -- we // can perhaps just send the outgoing sample at that point. NOTE that this is // always going to happen on the last sample in a long stretch, and it will happen // a lot if the app is written to not submit all the samples at once (need to // document the latter) // // // Fill out pMediaSample. The caller fills out the wait time if the // result of this method is S_OK. // pMediaSample = (IMediaSample *)(pQueueSample->m_pMediaSample); pMediaSample->AddRef(); return S_OK; } ////////////////////////////////////////////////////////////////////////////// // // SetTime // // This is called from GetFilledBuffer() to set the timestamp on the sample // before sending it down the filter graph. // // The timestamp is determined based on the duration of the sample and the // timestamp of the last sample. // ////////////////////////////////////////////////////////////////////////////// HRESULT CMediaTerminalFilter::SetTime(IMediaSample *pMediaSample) { HRESULT hr = S_OK; // the sample starts when the previous sample ended REFERENCE_TIME rtStartTime = m_rtLastSampleEndedAt; REFERENCE_TIME rtEndTime = rtStartTime; // calculate sample's duration if (m_bIsAudio) { HRESULT nSampleSize = pMediaSample->GetSize(); m_rtLastSampleDuration = (REFERENCE_TIME)((double)nSampleSize * m_AudioDelayPerByte) * 10000; } else { // NOTE: assumption is that if not audio, it is video. // another assumption is that one media sample is one frame m_rtLastSampleDuration = m_VideoDelayPerFrame * 10000; } // when does the sample end? rtEndTime += m_rtLastSampleDuration; LOG((MSP_TRACE, "CMediaTerminal::SetTime setting timestamp to (%lu, %lu) ", (DWORD)(rtStartTime/10000), (DWORD)(rtEndTime/10000))); // we know when it started and when it ended. set the timestamp hr = pMediaSample->SetTime(&rtStartTime, &rtEndTime); m_rtLastSampleEndedAt = rtEndTime; return hr; } ////////////////////////////////////////////////////////////////////////////// // // CMediaTerminalFilter::SetDiscontinuityIfNeeded // // this function sets discontinuity flag if the sample came too late to // smoothly continue the data flow. the assumption is that if the app did not // feed us with data for some time, then that means there was no data, and the // new data coming after the pause is a new part of the data stream, with a new // timeline // HRESULT CMediaTerminalFilter::SetDiscontinuityIfNeeded(IMediaSample *pMediaSample) { // // have a filter? (need it to get clock to get real time) // if ( NULL == m_pBaseFilter ) { LOG((MSP_ERROR, "CMediaTerminalFilter::SetDiscontinuityIfNeeded() - no filter")); return E_UNEXPECTED; } // // ask filter for clock // IReferenceClock *pClock = NULL; HRESULT hr = m_pBaseFilter->GetSyncSource(&pClock); if (FAILED(hr)) { // // no clock... // LOG((MSP_ERROR, "CMediaTerminalFilter::SetDiscontinuityIfNeeded() - no clock. hr = %lx", hr)); return hr; } // // try to get real time // REFERENCE_TIME rtRealTimeNow = 0; hr = pClock->GetTime(&rtRealTimeNow); pClock->Release(); pClock = NULL; if (FAILED(hr)) { LOG((MSP_ERROR, "CMediaTerminalFilter::SetDiscontinuityIfNeeded() - failed to get time. " "hr = %lx", hr)); return hr; } // // how much time passed since the last sample was sent? // REFERENCE_TIME rtRealTimeDelta = rtRealTimeNow - m_rtRealTimeOfLastSample; // // keep current real time as the "last sample's" real time, to be used for // in continuity determination for the next sample // m_rtRealTimeOfLastSample = rtRealTimeNow; // // how long was it supposed to take for the last sample to play? if too // much time passed since the sample was supposed to be done, this is // a discontinuity. // // note that SetTime on this sample should be called _after_ this method so // it does not set m_rtLastSampleDuration to duration of the current // sample before we figure out if this is a discontinuity or not // REFERENCE_TIME rtMaximumAllowedJitter = m_rtLastSampleDuration * 2; if ( rtRealTimeDelta > rtMaximumAllowedJitter ) { // // too much real time passed since the last sample. discontinuity. // LOG((MSP_TRACE, "CMediaTerminalFilter::SetDiscontinuityIfNeeded - late sample. setting discontinuity")); hr = pMediaSample->SetDiscontinuity(TRUE); // // did we fail to set the discontinuity flag? propagate error to the caller // if (FAILED(hr)) { LOG((MSP_ERROR, "CMediaTerminalFilter::SetDiscontinuityIfNeeded() - pMediaSample->SetTime failed. " "hr = 0x%lx", hr)); return hr; } } // late sample return S_OK; } // the application is supposed to call DeleteMediaType(*ppmt) (on success) // if the pin is not connected, return the suggested media type if // one exists, else return error // else return the media format of the connected pin - this is stored in // m_ConnectedMediaType during Connect or ReceiveConnection HRESULT CMediaTerminalFilter::GetFormat( OUT AM_MEDIA_TYPE **ppmt ) { AUTO_CRIT_LOCK; LOG((MSP_TRACE, "CMediaTerminal::GetFormat(%p) called", ppmt)); // validate the parameter BAIL_IF_NULL(ppmt, E_POINTER); // the operator == is defined on CComPtr, hence, NULL comes second if (m_pConnectedPin == NULL) { // if a media type was suggested by the user before connection // create and return a media type structure with those values. The pin need not // be connected if (NULL != m_pSuggestedMediaType) { // create and copy the media type *ppmt = CreateMediaType(m_pSuggestedMediaType); return S_OK; } return VFW_E_NOT_CONNECTED; } // create and copy the media type *ppmt = CreateMediaType(&m_ConnectedMediaType); LOG((MSP_TRACE, "CMediaTerminal::GetFormat(%p) succeeded", ppmt)); return S_OK; } // if this is called when the stream is connected, // an error value is returned. // it is only used in unconnected terminals to set the media format to negotiate // when connected to the filter graph. HRESULT CMediaTerminalFilter::SetFormat( IN AM_MEDIA_TYPE *pmt ) { AUTO_CRIT_LOCK; LOG((MSP_TRACE, "CMediaTerminalFilter::SetFormat(%p) - enter", pmt)); // check if already connected if (m_pConnectedPin != NULL) { LOG((MSP_ERROR, "CMediaTerminalFilter::SetFormat(%p) - " "already connected - exit VFW_E_ALREADY_CONNECTED", pmt)); return VFW_E_ALREADY_CONNECTED; } // // ZoltanS: To aid the MSPs in conveying to the application the media type // being used on a stream, SetFormat can no longer be successfully called // more than once with different media types. // // if pmt == NULL and m_psmt == NULL then do nothing, return S_OK // if pmt == NULL and m_psmt != NULL then do nothing, return error // if pmt != NULL and m_psmt != NULL then // if media types are the same then do nothing, return S_OK // if media types differ then do nothing, return error // // only if pmt != NULL and m_psmt == NULL then try to set the media type // if ( pmt == NULL ) { if ( m_pSuggestedMediaType == NULL ) { LOG((MSP_WARN, "CMediaTerminalFilter::SetFormat(%p) - " "was NULL, set to NULL - this does nothing - exit S_OK", pmt)); return S_OK; } else { LOG((MSP_ERROR, "CMediaTerminalFilter::SetFormat(%p) - " "was non-NULL, tried to set to NULL - rejected because once " "a type is set it is permanent - exit VFW_E_TYPE_NOT_ACCEPTED", pmt)); return VFW_E_TYPE_NOT_ACCEPTED; } } else if ( m_pSuggestedMediaType != NULL ) { if ( IsSameAMMediaType(pmt, m_pSuggestedMediaType) ) { LOG((MSP_WARN, "CMediaTerminalFilter::SetFormat(%p) - " "was non-NULL, set same type again - this does nothing - " "exit S_OK", pmt)); return S_OK; } else { LOG((MSP_ERROR, "CMediaTerminalFilter::SetFormat(%p) - " "was non-NULL, tried to set to new, different type - " "rejected because once a type is set it is permanent - " "exit VFW_E_TYPE_NOT_ACCEPTED", pmt)); return VFW_E_TYPE_NOT_ACCEPTED; } } LOG((MSP_TRACE, "CMediaTerminalFilter::SetFormat(%p) - OK to try setting " "format - calling QueryAccept", pmt)); // // check if the media type is acceptable for the terminal // return VFW_E_INVALIDMEDIATYPE if we can't accept it // HRESULT hr = QueryAccept(pmt); if ( hr != S_OK ) // NOTE: S_FALSE from QueryAccept indicates rejection. { LOG((MSP_ERROR, "CMediaTerminalFilter::SetFormat(%p) - " "QueryAccept rejected type - exit VFW_E_INVALIDMEDIATYPE", pmt)); return VFW_E_INVALIDMEDIATYPE; } // // Accepted. Create an am media type initialized with the pmt value. // m_pSuggestedMediaType = CreateMediaType(pmt); if ( m_pSuggestedMediaType == NULL ) { LOG((MSP_ERROR, "CMediaTerminalFilter::SetFormat(%p) - " "out of memory in CreateMediaType - exit E_OUTOFMEMORY", pmt)); return E_OUTOFMEMORY; } LOG((MSP_TRACE, "CMediaTerminalFilter::SetFormat succeeded - new media " "type (%p) set", pmt)); return S_OK; } // This method may only be called before connection and will // force the MST to convert buffers to this buffer size. // If this is set then we try these values during filter negotiation // and if the connecting filter doesn't accept these, then we convert STDMETHODIMP CMediaTerminalFilter::SetAllocatorProperties( IN ALLOCATOR_PROPERTIES *pAllocProperties ) { LOG((MSP_TRACE, "CMediaTerminal::SetAllocatorProperties[%p] - enter. pAllocProperties[%p]", this, pAllocProperties)); AUTO_CRIT_LOCK; // // check if already connected // if (m_pConnectedPin != NULL) { LOG((MSP_WARN, "CMediaTerminal::SetAllocatorProperties - VFW_E_ALREADY_CONNECTED")); return VFW_E_ALREADY_CONNECTED; } if (NULL == pAllocProperties) { m_bUserAllocProps = FALSE; if ( ! m_bSuggestedAllocProps ) { SetDefaultAllocatorProperties(); } return S_OK; } if (!CUserMediaSample::VerifyAllocatorProperties( m_bAllocateBuffers, *pAllocProperties )) { return E_FAIL; } DUMP_ALLOC_PROPS("CMediaTerminal::SetAllocatorProperties - new properties:", pAllocProperties); // // the user wants to use these properties on their samples // m_bUserAllocProps = TRUE; m_UserAllocProps = *pAllocProperties; m_AllocProps = m_UserAllocProps; LOG((MSP_TRACE, "CMediaTerminal::SetAllocatorProperties - succeeded")); return S_OK; } // an ITAllocatorProperties method // calls to IAMBufferNegotiation::GetAllocatorProperties are also forwarded to // this method by the terminal // // gets current values for the allocator properties // after connection, this provides the negotiated values // it is invalid before connection. The MST will accept // any values suggested by the filters it connects to STDMETHODIMP CMediaTerminalFilter::GetAllocatorProperties( OUT ALLOCATOR_PROPERTIES *pAllocProperties ) { LOG((MSP_TRACE, "CMediaTerminalFilter::GetAllocatorProperties(%p) called", pAllocProperties)); BAIL_IF_NULL(pAllocProperties, E_POINTER); AUTO_CRIT_LOCK; *pAllocProperties = m_AllocProps; DUMP_ALLOC_PROPS("CMediaTerminalFilter::GetAllocatorProperties", pAllocProperties); LOG((MSP_TRACE, "CMediaTerminalFilter::GetAllocatorProperties - succeeded")); return S_OK; } // an IAMBufferNegotiation method, forwarded to us by the terminal STDMETHODIMP CMediaTerminalFilter::SuggestAllocatorProperties( IN const ALLOCATOR_PROPERTIES *pAllocProperties ) { LOG((MSP_TRACE, "CMediaTerminal::SuggestAllocatorProperties(%p) called", pAllocProperties)); AUTO_CRIT_LOCK; // check if already connected if (m_pConnectedPin != NULL) { return VFW_E_ALREADY_CONNECTED; } // // Passing in NULL sets us back to our defaults. This would seem to make // sense, but it's nowhere to be found in the spec for the interface. // if (NULL == pAllocProperties) { m_bSuggestedAllocProps = FALSE; if ( m_bUserAllocProps ) { m_AllocProps = m_UserAllocProps; } else { SetDefaultAllocatorProperties(); } return S_OK; } // // If any of the fields in the suggested allocator properties // structure is negative, then we use the current values for those // fields. This is as per the interface definition. We can't just // change pAllocProperties because it's const. // ALLOCATOR_PROPERTIES FinalProps = * pAllocProperties; if ( FinalProps.cbAlign < 0 ) { FinalProps.cbAlign = DEFAULT_AM_MST_BUFFER_ALIGNMENT; } if ( FinalProps.cbBuffer < 0 ) { FinalProps.cbBuffer = DEFAULT_AM_MST_SAMPLE_SIZE; } if ( FinalProps.cbPrefix < 0 ) { FinalProps.cbPrefix = DEFAULT_AM_MST_BUFFER_PREFIX; } if ( FinalProps.cBuffers < 0 ) { FinalProps.cBuffers = DEFAULT_AM_MST_NUM_BUFFERS; } // // Sanity-check the resulting properties. // if (!CUserMediaSample::VerifyAllocatorProperties( m_bAllocateBuffers, FinalProps )) { return E_FAIL; } DUMP_ALLOC_PROPS("CMediaTerminalFilter::SuggestAllocatorProperties - suggested:", &FinalProps); // // if allocator properties were already set using SetAllocatorProperties, // fail -- suggesting does not override the value that has been set. // if (m_bUserAllocProps) { // // the properties have been Set by SetAllocatorProperties, whoever is // suggesting new properties had better be suggesting exactly same set, // otherwise we will fail the call. // if ( !Equal(&m_UserAllocProps, pAllocProperties) ) { // // the application has requested specific allocator properties. // but someone is now suggesting a different set of properties. // the properties that have been set can only be re-set. // LOG((MSP_WARN, "CMediaTerminal::SuggestAllocatorProperties " "- can't override SetAllocatorProperties settings. VFW_E_WRONG_STATE")); return VFW_E_WRONG_STATE; } } // the MSP wants us to try these properties m_bSuggestedAllocProps = TRUE; m_AllocProps = FinalProps; DUMP_ALLOC_PROPS("CMediaTerminalFilter::SuggestAllocatorProperties - kept:", &m_AllocProps); LOG((MSP_TRACE, "CMediaTerminal::SuggestAllocatorProperties - finish")); return S_OK; } // TRUE by default. when set to FALSE, the sample allocated // by the MST don't have any buffers and they must be supplied // before Update is called on the samples STDMETHODIMP CMediaTerminalFilter::SetAllocateBuffers( IN BOOL bAllocBuffers ) { LOG((MSP_TRACE, "CMediaTerminal::SetAllocateBuffers(%u) called", bAllocBuffers)); AUTO_CRIT_LOCK; // check if already connected if (m_pConnectedPin != NULL) return VFW_E_ALREADY_CONNECTED; if (!CUserMediaSample::VerifyAllocatorProperties( bAllocBuffers, m_AllocProps )) { return E_FAIL; } // set flag for allocating buffers for samples m_bAllocateBuffers = bAllocBuffers; LOG((MSP_TRACE, "CMediaTerminal::SetAllocateBuffers(%u) succeeded", bAllocBuffers)); return S_OK; } // returns the current value of this boolean configuration parameter STDMETHODIMP CMediaTerminalFilter::GetAllocateBuffers( OUT BOOL *pbAllocBuffers ) { LOG((MSP_TRACE, "CMediaTerminal::GetAllocateBuffers(%p) called", pbAllocBuffers)); BAIL_IF_NULL(pbAllocBuffers, E_POINTER); AUTO_CRIT_LOCK; *pbAllocBuffers = m_bAllocateBuffers; LOG((MSP_TRACE, "CMediaTerminal::GetAllocateBuffers(*%p = %u) succeeded", pbAllocBuffers, *pbAllocBuffers)); return S_OK; } // this size is used for allocating buffers when AllocateSample is // called (if 0, the negotiated allocator properties' buffer size is // used). this is only valid when we have been told to allocate buffers STDMETHODIMP CMediaTerminalFilter::SetBufferSize( IN DWORD BufferSize ) { AUTO_CRIT_LOCK; m_AllocateSampleBufferSize = BufferSize; return S_OK; } // returns the value used to allocate buffers when AllocateSample is // called. this is only valid when we have been told to allocate buffers STDMETHODIMP CMediaTerminalFilter::GetBufferSize( OUT DWORD *pBufferSize ) { BAIL_IF_NULL(pBufferSize, E_POINTER); AUTO_CRIT_LOCK; *pBufferSize = m_AllocateSampleBufferSize; return S_OK; } // over-ride this to return failure. we don't allow it to join a multi-media // stream because the multi-media stream thinks it owns the stream STDMETHODIMP CMediaTerminalFilter::JoinAMMultiMediaStream( IN IAMMultiMediaStream *pAMMultiMediaStream ) { AUTO_CRIT_LOCK; LOG((MSP_TRACE, "CMediaTerminalFilter::JoinAMMultiMediaStream(%p) called", pAMMultiMediaStream)); return E_FAIL; } // over-ride this to return failure if a non-null proposed filter is // anything other than the media stream filter that is acceptable to us // This acceptable filter is set in SetMediaStreamFilter() STDMETHODIMP CMediaTerminalFilter::JoinFilter( IN IMediaStreamFilter *pMediaStreamFilter ) { AUTO_CRIT_LOCK; LOG((MSP_TRACE, "CMediaTerminalFilter::JoinFilter(%p) called", pMediaStreamFilter)); // check if the caller is trying to remove references to the media stream filter if (NULL == pMediaStreamFilter) { // null out references to the filter that were set when JoinFilter was called // with a valid media stream filter m_pFilter = NULL; m_pBaseFilter = NULL; return S_OK; } // check if the passed in filter is different from the one // that is acceptable if (pMediaStreamFilter != m_pMediaStreamFilterToAccept) { return E_FAIL; } // if the filter is already set, it must have joined the graph already if (NULL != m_pFilter) { return S_OK; } // save a pointer to the media stream filter m_pFilter = pMediaStreamFilter; // get the base filter i/f HRESULT hr; hr = pMediaStreamFilter->QueryInterface(IID_IBaseFilter, (void **)&m_pBaseFilter); BAIL_ON_FAILURE(hr); // release a reference as this method is not supposed to increase ref count on the filter // NOTE: this is being done in CStream - merely following it. TO DO ** why? m_pBaseFilter->Release(); LOG((MSP_TRACE, "CMediaTerminalFilter::JoinFilter(%p) succeeded", pMediaStreamFilter)); return S_OK; } // create an instance of CUserMediaSample and initialize it STDMETHODIMP CMediaTerminalFilter::AllocateSample( IN DWORD dwFlags, OUT IStreamSample **ppSample ) { LOG((MSP_TRACE, "CMediaTerminalFilter::AllocateSample(dwFlags:%u, ppSample:%p)", dwFlags, ppSample)); // validate parameters // we don't support any flags if (0 != dwFlags) return E_INVALIDARG; BAIL_IF_NULL(ppSample, E_POINTER); AUTO_CRIT_LOCK; // create a sample and initialize it HRESULT hr; CComObject *pUserSample; hr = CComObject::CreateInstance(&pUserSample); BAIL_ON_FAILURE(hr); // uses the allocator properties to allocate a bufer, if the // user has asked for one to be created hr = pUserSample->Init( *this, m_bAllocateBuffers, m_AllocateSampleBufferSize, m_AllocProps ); if (HRESULT_FAILURE(hr)) { delete pUserSample; return hr; } hr = pUserSample->QueryInterface(IID_IStreamSample, (void **)ppSample); if ( FAILED(hr) ) { delete pUserSample; return hr; } LOG((MSP_TRACE, "CMediaTerminalFilter::AllocateSample(dwFlags:%u, ppSample:%p) succeeded", dwFlags, ppSample)); return S_OK; } // return E_NOTIMPL - we don't have a mechanism to share a sample currently STDMETHODIMP CMediaTerminalFilter::CreateSharedSample( IN IStreamSample *pExistingSample, IN DWORD dwFlags, OUT IStreamSample **ppNewSample ) { AUTO_CRIT_LOCK; LOG((MSP_TRACE, "CMediaTerminalFilter::CreateSharedSample called")); return E_NOTIMPL; } // supposed to get the format information for the passed in IMediaStream // set this instance's media format // not implemented currently STDMETHODIMP CMediaTerminalFilter::SetSameFormat( IN IMediaStream *pStream, IN DWORD dwFlags ) { AUTO_CRIT_LOCK; LOG((MSP_TRACE, "CMediaTerminalFilter::SetSameFormat called")); return E_NOTIMPL; } // CMediaTerminalFilter uses CMediaPump ms_MediaPumpPool instead of // CPump *m_WritePump. therefore, CStream::SetState which uses CPump // had to be overridden here // also, it resets the end of stream flag when a connected stream is // told to run STDMETHODIMP CMediaTerminalFilter::SetState( IN FILTER_STATE State ) { LOG((MSP_TRACE, "IMediaStream::SetState(%d) called",State)); Lock(); if (m_pConnectedPin == NULL) { Unlock(); if (State == STREAMSTATE_RUN) { EndOfStream(); } } else { TM_ASSERT(m_pAllocator != NULL); m_FilterState = State; if (State == State_Stopped) { m_pAllocator->Decommit(); if (!m_bUsingMyAllocator) { Decommit(); } Unlock(); } else { // rajeevb - clear the end of stream flag m_bEndOfStream = false; // zoltans - moved Unlock here to avoid deadlock in commit. // some of what's done within Commit needs to have the lock // released. That's to avoid holding the stream lock while // trying to acquire the pump lock. Unlock(); m_pAllocator->Commit(); if (!m_bUsingMyAllocator) { Commit(); } } } if (State == State_Stopped) { LOG((MSP_TRACE, "CMediaTerminalFilter::SetState stopped. ")); m_rtLastSampleEndedAt = 0; } LOG((MSP_TRACE, "IMediaStream::SetState(%d) succeeded",State)); return S_OK; } // if own allocator, just set completion on the sample // NOTE: this is not being done in the derived classes // else, get a stream sample from pool, // copy the media sample onto the stream sample // set completion // NOTE: find out why the quality notifications are being sent in the // derived classes STDMETHODIMP CMediaTerminalFilter::Receive( IN IMediaSample *pSample ) { LOG((MSP_TRACE, "CMediaTerminalFilter::Receive(%p) called", pSample)); COM_LOCAL_CRIT_LOCK LocalLock(this); if (m_bFlushing) return S_FALSE; if (0 > pSample->GetActualDataLength()) return S_FALSE; if (m_bUsingMyAllocator) { CUserMediaSample *pSrcSample = (CUserMediaSample *)((CMediaSampleTM *)pSample)->m_pSample; pSrcSample->m_bReceived = true; // the completion state need not be set because, the caller holds the last reference // on the media sample and when it is released (after the return of this fn), // the completion state gets set return S_OK; } CUserMediaSample *pDestSample; REFERENCE_TIME rtStart, rtEnd; pSample->GetTime(&rtStart, &rtEnd); LOG((MSP_TRACE, "CMediaTerminalFilter::Receive: (start - %l, stop - %l)\n", rtStart, rtEnd)); // unlock to wait for a sample LocalLock.Unlock(); HRESULT hr; // get the buffer ptr BYTE *pSrcBuffer = NULL; // ignore error code, the pointer will be checked in the loop pSample->GetPointer(&pSrcBuffer); // determine the number of bytes to copy LONG SrcDataSize = pSample->GetActualDataLength(); // // allocate samples from the pool, copy the received sample data into // the allocated sample buffer until all of the data has been copied // NOTE: This works for both combining and splitting samples. // * splitting: loop until distributed to enough samples // * combining: stick it in the first sample; SetCompletionStatus // queues it up again for next time // do { // // Get a destination / user / outgoing sample from the pool. // Wait for it if none is available right now. // The obtained CSample has a ref count on it that must be released // after we are done with it. // hr = AllocSampleFromPool(NULL, (CSample **)&pDestSample, 0); BAIL_ON_FAILURE(hr); // // Copy the media sample into the destination sample and // signal destination sample completion. // // CUserMediaSample::CopyFrom returns ERROR_MORE_DATA if there's more // data that can fit into this user sample // CUserMediaSample::SetCompletionStatus passes through the HRESULT // value that's passed into it, unless it encounters an error of // its own // LONG OldSrcDataSize = SrcDataSize; hr = pDestSample->SetCompletionStatus( pDestSample->CopyFrom(pSample, pSrcBuffer, SrcDataSize) ); // // Release the destination sample. // ((IStreamSample *)pDestSample)->Release(); } while(ERROR_MORE_DATA == HRESULT_CODE(hr)); LOG((MSP_TRACE, "CMediaTerminalFilter::Receive(%p) succeeded", pSample)); return S_OK; } STDMETHODIMP CMediaTerminalFilter::GetBuffer( IMediaSample **ppBuffer, REFERENCE_TIME * pStartTime, REFERENCE_TIME * pEndTime, DWORD dwFlags ) { LOG((MSP_TRACE, "CMediaTerminalFilter::GetBuffer(%p, %p, %p, %u) called", ppBuffer, pStartTime, pEndTime, dwFlags)); if (NULL == ppBuffer) return E_POINTER; #ifdef DBG { COM_LOCAL_CRIT_LOCK LocalLock(this); TM_ASSERT(m_bUsingMyAllocator); } #endif // DBG // no lock needed here as AllocSampleFromPool acquires lock. // shouldn't hold lock at this point as the fn waits on a single // event inside *ppBuffer = NULL; CUserMediaSample *pSample; HRESULT hr = AllocSampleFromPool(NULL, (CSample **)&pSample, dwFlags); BAIL_ON_FAILURE(hr); // the sample has a refcnt on it. this will be released after we // signal the user in FinalMediaSampleRelease pSample->m_bReceived = false; pSample->m_bModified = true; *ppBuffer = (IMediaSample *)(pSample->m_pMediaSample); (*ppBuffer)->AddRef(); LOG((MSP_TRACE, "CMediaTerminalFilter::GetBuffer(%p, %p, %p, %u) succeeded", ppBuffer, pStartTime, pEndTime, dwFlags)); return hr; } STDMETHODIMP CMediaTerminalFilter::SetProperties( ALLOCATOR_PROPERTIES* pRequest, ALLOCATOR_PROPERTIES* pActual ) { LOG((MSP_TRACE, "CMediaTerminalFilter::SetProperties[%p] - enter. requested[%p] actual[%p]", this, pRequest, pActual)); // // check pRequest argument // if (IsBadReadPtr(pRequest, sizeof(ALLOCATOR_PROPERTIES))) { LOG((MSP_ERROR, "CMediaTerminalFilter::SetProperties - bad requested [%p] passed in", pRequest)); return E_POINTER; } // // if the caller passed us a non-NULL pointer for allocator properties for us to // return, it'd better be good. // if ( (NULL != pActual) && IsBadWritePtr(pActual, sizeof(ALLOCATOR_PROPERTIES))) { LOG((MSP_ERROR, "CMediaTerminalFilter::SetProperties - bad actual [%p] passed in", pRequest)); return E_POINTER; } // this critical section is needed for the allocator AUTO_CRIT_LOCK; if (m_bCommitted) { LOG((MSP_WARN, "CMediaTerminalFilter::SetProperties - already commited")); return VFW_E_ALREADY_COMMITTED; } if (!CUserMediaSample::VerifyAllocatorProperties( m_bAllocateBuffers, *pRequest )) { LOG((MSP_ERROR, "CMediaTerminalFilter::SetProperties - requested properties failed verification")); return E_FAIL; } DUMP_ALLOC_PROPS("CMediaTerminalFilter::SetProperties. Requested:", pRequest); // // if the app set alloc properties by calling SetAllocatorProperties, we // can only use those properties, and no others // if (m_bUserAllocProps) { // // properties were already set // LOG((MSP_TRACE, "CMediaTerminalFilter::SetProperties - properties were configured through SetAllocatorProperties")); } else { // // no one has asked us for specific properties before, so // we can accept the properties that we are given now. // LOG((MSP_TRACE, "CMediaTerminalFilter::SetProperties - accepting requested properties")); m_AllocProps = *pRequest; } // // tell the caller what properties we can actually provide // if (NULL != pActual) { *pActual = m_AllocProps; } DUMP_ALLOC_PROPS("CMediaTerminalFilter::SetProperties - ours:", &m_AllocProps); LOG((MSP_TRACE, "CMediaTerminalFilter::SetProperties - succeeded")); return S_OK; } STDMETHODIMP CMediaTerminalFilter::GetProperties( ALLOCATOR_PROPERTIES* pProps ) { LOG((MSP_TRACE, "CMediaTerminalFilter::GetProperties(%p) called", pProps)); BAIL_IF_NULL(pProps, E_POINTER); // this critical section is required for the allocator AUTO_CRIT_LOCK; *pProps = m_AllocProps; DUMP_ALLOC_PROPS("CMediaTerminalFilter::GetProperties - our properties:", pProps); LOG((MSP_TRACE, "CMediaTerminalFilter::GetProperties - succeeded")); return NOERROR; } // the thread pump calls the filter back during the registration // to tell it that registration succeeded and that the pump will be // waiting on the m_hWaitFreeSem handle HRESULT CMediaTerminalFilter::SignalRegisteredAtPump( ) { LOG((MSP_TRACE, "CMediaTerminalFilter::SignalRegisteredAtPump[%p] - started", this)); AUTO_CRIT_LOCK; TM_ASSERT(PINDIR_OUTPUT == m_Direction); // check if not committed if (!m_bCommitted) { // if we missed a decommit in between, // signal the thread that we have been decommited ReleaseSemaphore(m_hWaitFreeSem, 1, 0); return VFW_E_NOT_COMMITTED; } TM_ASSERT(0 == m_lWaiting); m_lWaiting = 1; LOG((MSP_TRACE, "CMediaTerminalFilter::SignalRegisteredAtPump - completed, m_lWaiting = 1")); return S_OK; } // // Override Commit to provide the number of buffers promised in negotiation // as well as to register with the media pump thread. // no need to call the adapted base class Commit STDMETHODIMP CMediaTerminalFilter::Commit() { LOG((MSP_TRACE, "CMediaTerminalFilter[0x%p]::Commit - entered", this)); HRESULT hr = E_FAIL; COM_LOCAL_CRIT_LOCK LocalLock(this); if (m_bCommitted) return S_OK; m_bCommitted = true; // Now actually allocate whatever number of buffers we've promised. // We need to do this only if we are a WRITE terminal, the read // terminal doesn't expose an allocator and thus hasn't promised any // samples // NOTE: the direction only changes in Init so we can safely access // m_Direction without the lock if (PINDIR_OUTPUT == m_Direction) { LOG((MSP_TRACE, "CMediaTerminalFilter::Commit pindir_output")); // // If we are a WRITE MST but we are using a downstream allocator, // then we do not use the NBQueue; instead we copy into the // downstream allocator's samples. // if ( m_bUsingMyAllocator ) { LOG((MSP_TRACE, "CMediaTerminalFilter::Commit using myallocator")); // // initialize sample queue // BOOL bQueueInitialized = m_SampleQueue.InitializeQ(m_AllocProps.cBuffers); if ( ! bQueueInitialized ) { LOG((MSP_ERROR, "CMediaTerminalFilter::Commit - failed to initialize sample queue.")); return Decommit(); } // // allocate samples and put them into the queue // for (LONG i = 0; i < m_AllocProps.cBuffers; i++) { // // create a sample // CComObject *pQueueSample = NULL; hr = CComObject::CreateInstance(&pQueueSample); if (FAILED(hr)) { LOG((MSP_ERROR, "CMediaTerminalFilter::Commit - failed to create queue sample")); return Decommit(); } // // initialize the sample // hr = pQueueSample->Init(*this, m_SampleQueue); if (FAILED(hr)) { LOG((MSP_ERROR, "CMediaTerminalFilter::Commit - failed to initialize queue sample")); // // failed to initializ. cleanup. // delete pQueueSample; return Decommit(); } // // put sample into the queue // BOOL QnQSuccess = m_SampleQueue.EnQueue(pQueueSample); if ( ! QnQSuccess ) { LOG((MSP_ERROR, "CMediaTerminalFilter::Commit - failed to enqueue queue sample")); // // failed to put sample into the queue. cleanup. // delete pQueueSample; return Decommit(); } // failed to enqueue sample } // for ( allocate and enqueue samples ) } // if m_bUsingMyAllocator // register this write filter with the thread pump. // we have to release our own lock - scenario - if we had a prior // registration which was decommitted, but the thread pump didn't // remove our entry (to be done when it waits on the wait event), the // thread could be trying to get the filter lock while holding its own // we should not try to get the pump lock while holding our own HANDLE hWaitEvent = m_hWaitFreeSem; LocalLock.Unlock(); hr = ms_MediaPumpPool.Register(this, hWaitEvent); if ( HRESULT_FAILURE(hr) ) { return Decommit(); } } LOG((MSP_TRACE, "CMediaTerminalFilter::Commit - completed")); return S_OK; } STDMETHODIMP CMediaTerminalFilter::ProcessSample(IMediaSample *pSample) { LOG((MSP_TRACE, "CMediaTerminalFilter[%p]::ProcessSample - entered", this)); Lock(); // // in a lock, get a reference to the connected pin. // IMemInputPin *pConnectedPin = m_pConnectedMemInputPin; // // not connected? do nothing // if ( NULL == pConnectedPin ) { Unlock(); LOG((MSP_TRACE, "CMediaTerminalFilter::ProcessSample - not connected. dropping sample. " "VFW_E_NOT_CONNECTED")); return VFW_E_NOT_CONNECTED; } // // addref so we can safely use this outside the lock // pConnectedPin->AddRef(); // // receive may take a while, do not call inside a lock... // Unlock(); // // pass the sample along to the connected pin // HRESULT hr = pConnectedPin->Receive(pSample); // // done, release the outstanding reference we requested. // pConnectedPin->Release(); pConnectedPin = NULL; LOG((MSP_(hr), "CMediaTerminalFilter::ProcessSample - finish. hr = %lx", hr)); return hr; } // release any waiting threads, dump the samples we allocated, // abort the sample being fragmented and all the samples in the pool STDMETHODIMP CMediaTerminalFilter::Decommit() { LOG((MSP_TRACE, "CMediaTerminalFilter[%p]::Decommit - entered", this)); Lock(); // if not committed, do nothing if ( !m_bCommitted ) { Unlock(); return S_OK; } m_bCommitted = false; if ( m_lWaiting > 0 ) { LOG((MSP_TRACE, "CMediaTerminalFilter::Decommit - releasing m_hWaitFreeSem by %ld", m_lWaiting)); ReleaseSemaphore(m_hWaitFreeSem, m_lWaiting, 0); m_lWaiting = 0; } // // unlock while calling into Unregister, to avoid a deadlock on trying to lock the pump // while another thread has pump locked in CMediaPump::ServiceFilter while waiting to get // to filter's lock which we are holding // Unlock(); // // unregister filter from media pump // ms_MediaPumpPool.UnRegister(m_hWaitFreeSem); // // lock the object again // Lock(); // // If we are a write filter and we are using our own allocator, then // we have internal samples to destroy. // The read filter doesn't maintain a queue. // if ( ( PINDIR_OUTPUT == m_Direction ) && m_bUsingMyAllocator ) { // don't wait for the samples, return if there are no more CQueueMediaSample *pSamp = NULL; while ((pSamp = m_SampleQueue.DeQueue(FALSE)) != NULL) { delete pSamp; } m_SampleQueue.ShutdownQ(); } if (NULL != m_pSampleBeingFragmented) { // abort the sample when the last refcnt to its internal // IMediaStream is released m_pSampleBeingFragmented->AbortDuringFragmentation(); ((IStreamSample *)m_pSampleBeingFragmented)->Release(); m_pSampleBeingFragmented = NULL; } // all the threads waiting for a CStream pool buffer // have been woken up by now // NOTE: no more samples can be added to the CStream pool // from now as we have decommitted (so no race conditions // possible if other threads are trying to add buffers to it) // go through the CStream sample q and for each sample // remove sample, unlock, abort the sample CSample *pSample = m_pFirstFree; while (NULL != pSample) { // remove sample from q m_pFirstFree = pSample->m_pNextFree; if (NULL != m_pFirstFree) m_pFirstFree->m_pPrevFree = NULL; else m_pLastFree = NULL; pSample->m_pNextFree = NULL; TM_ASSERT(pSample->m_Status == MS_S_PENDING); CHECKSAMPLELIST // unlock so that we don't have a deadlock due to the sample // trying to access the stream Unlock(); // we know that the sample must be alive now because // we have a reference to it // abort the sample, ignore error code (it'll return E_ABORT) pSample->SetCompletionStatus(E_ABORT); pSample->Release(); // obtain lock Lock(); // reset pSample to the top of the q pSample = m_pFirstFree; } Unlock(); LOG((MSP_TRACE, "CMediaTerminalFilter::Decommit - finish")); // at this point, we hold a lock // return the result of PumpOverrideDecommit return S_OK; } // try to follow the cstream code when possible for Connect // the cstream implementation calls ReceiveConnection on self! hence need // to over-ride it // use the pmt parameter or the suggested media type if not null?, else // enumerate the input pin's media types and save a ptr to the first // media type (to be written into the m_ConnectedMediaType, m_ActualMediaType // on success). we accept any media type and we want to use our own allocator // so, NotifyAllocator() and on success, set the allocator and copy the // media type STDMETHODIMP CMediaTerminalFilter::Connect( IPin * pReceivePin, const AM_MEDIA_TYPE *pmt ) { AUTO_CRIT_LOCK; LOG((MSP_TRACE, "CMediaTerminalFilter::Connect(%p, %p) called", pReceivePin, pmt)); // if there is a suggested media type, we can't accept any // suggested media type that is not same as it if ( (NULL != pmt) && (NULL != m_pSuggestedMediaType) && (!IsSameAMMediaType(pmt, m_pSuggestedMediaType)) ) { return VFW_E_TYPE_NOT_ACCEPTED; } // get IMemInputPin i/f on the pReceivePin CComQIPtr pConnectedMemInputPin = pReceivePin; BAIL_IF_NULL(pConnectedMemInputPin, VFW_E_TYPE_NOT_ACCEPTED); HRESULT hr; const AM_MEDIA_TYPE *pMediaType; // check if the passed in media type is non-null if (NULL != pmt) { // we have already checked it to be same as the suggested // media type, so no more checks are required pMediaType = pmt; } else if (NULL != m_pSuggestedMediaType) // try the suggested terminal media type { // we verified that the suggested media type was acceptable in put_MediaType pMediaType = m_pSuggestedMediaType; } else // NOTE: we still try if the passed in media type (non-null) is not acceptable { // else, enumerate the input pin for media types and call QueryAccept for each to // check if the media type is acceptable // get the enumerator IEnumMediaTypes *pEnum; hr = EnumMediaTypes(&pEnum); BAIL_ON_FAILURE(hr); TM_ASSERT(NULL != pEnum); // for each media type, call QueryAccept. we are looking for the first acceptable media type DWORD NumObtained; while (S_OK == (hr = pEnum->Next(1, (AM_MEDIA_TYPE **)&pMediaType, &NumObtained))) { hr = QueryAccept(pMediaType); if (HRESULT_FAILURE(hr)) { break; } } BAIL_ON_FAILURE(hr); // found an acceptable media type } if (NULL == pMediaType->pbFormat) { return VFW_E_INVALIDMEDIATYPE; } // call the input pin with the media type hr = pReceivePin->ReceiveConnection(this, pMediaType); BAIL_ON_FAILURE(hr); ////////////////////////////////////////////////////////////////////////// // // Formats negotiated. Now deal with allocator properties. // // if ( other pin has allocator requirements: ) // if they exist and are usable then we must use them, overriding our // own wishes. If they exist and are unusable then we fail to connect. // if we use these, we do m_AllocProps = // // // The rest of the logic is not done here, but rather in the two // methods: SuggestAllocatorProperties (msp method) and // SetAllocatorProperties (user method). The net effect is: // // else if ( m_bSuggestedAllocProps == TRUE ) // MSP has set properties that they want us to use on the graph. If // this is true then we try to use these properties. // these are in m_AllocProps // else if ( m_bUserAllocProps == TRUE ) // user has properties they want us to convert to. if the suggested // (MSP) setting hasn't happened, it'll be most efficient if we use // these. these are in both m_AllocProps and m_UserAllocProps so that // if the MSP un-suggests then we can go back to the user's settings // else // just use m_AllocProps (these are set to default values on creation) // // in any of these cases we just use m_AllocProps as it already is filled // in with the correct values. // ////////////////////////////////////////////////////////////////////////// // // So first of all, try to get the other pin's requirements. // ALLOCATOR_PROPERTIES DownstreamPreferred; // // downstream pin hints us of its preferences. we don't have to use them hr = pConnectedMemInputPin->GetAllocatorRequirements(&DownstreamPreferred); if ( ( hr != S_OK ) && ( hr != E_NOTIMPL ) ) { // strange failure -- something's very wrong Disconnect(); pReceivePin->Disconnect(); return hr; } else if ( hr == S_OK ) { // // This means that the downstream filter has allocator requirements. // if (!CUserMediaSample::VerifyAllocatorProperties( m_bAllocateBuffers, DownstreamPreferred )) { Disconnect(); pReceivePin->Disconnect(); return E_FAIL; } DUMP_ALLOC_PROPS("CMediaTerminalFilter::Connect - downstream preferences:", &DownstreamPreferred); // // see if the application asked for or suggested specific allocator properties // if (m_bUserAllocProps || m_bSuggestedAllocProps ) { LOG((MSP_WARN, "CMediaTerminalFilter::Connect " "- connected pin wants allocator props different from set or suggested")); } else { // // accept allocator properties asked by the downstream pin // m_AllocProps = DownstreamPreferred; } } DUMP_ALLOC_PROPS("CMediaTerminalFilter::Connect - properties to use:", &m_AllocProps); // // At this point, we know what allocator properties we are going to // use -- it's in m_AllocProps. // // Next we determine if the downstream filter has an allocator that // we can use. // IMemAllocator * pAllocatorToUse = NULL; // // Don't bother using a downstream allocator if the buffers are less than // a minimum size. // if ( m_AllocProps.cbBuffer < 2000 ) { LOG((MSP_TRACE, "CMediaTerminalFilter::Connect - " "small buffers - using our allocator")); hr = E_FAIL; // don't use downstream allocator } else { hr = pConnectedMemInputPin->GetAllocator( & pAllocatorToUse ); if ( SUCCEEDED(hr) ) { LOG((MSP_TRACE, "CMediaTerminalFilter::Connect - " "downstream filter has an allocator")); // // The input pin on the downstream filter has its own allocator. // See if it will accept our allocator properties. // ALLOCATOR_PROPERTIES ActualAllocProps; hr = pAllocatorToUse->SetProperties( & m_AllocProps, & ActualAllocProps ); if ( FAILED(hr) ) { LOG((MSP_TRACE, "CMediaTerminalFilter::Connect - " "downstream allocator did not allow us to SetProperties - " "0x%08x", hr)); pAllocatorToUse->Release(); pAllocatorToUse = NULL; } else if ( AllocatorPropertiesDifferSignificantly( & ActualAllocProps, & m_AllocProps ) ) { LOG((MSP_TRACE, "CMediaTerminalFilter::Connect - " "downstream allocator did allow us to SetProperties " "but it changed the properties rather than accepting them")); // It succeeded but it modified the allocator properties // we gave it. To be safe, we use our own allocator instead. // // Note: The waveout filter enforces minimum sizes when its // allocator is used. This means that we only use the waveout // filter's allocator when our buffer size is sufficiently large // that the waveout filter doesn't tamper with them. hr = E_FAIL; pAllocatorToUse->Release(); pAllocatorToUse = NULL; } else { LOG((MSP_TRACE, "CMediaTerminalFilter::Connect - " "downstream allocator accepted our allocator properties")); } } // if downstream filter has an allocator } // if large buffers // // At this point we have hr (success or failure) and a reference to the // downstream filter's alloactor if hr is success. // // If hr is success, then the downstream filter has an allocator that we can // use, and pAllocatorToUse points to that allocator. If hr is failure, then // we use our own allocator. // if ( FAILED(hr) ) { LOG((MSP_TRACE, "CMediaTerminalFilter::Connect - " "using our own allocator")); pAllocatorToUse = this; pAllocatorToUse->AddRef(); // to match Release below m_bUsingMyAllocator = true; } else { LOG((MSP_TRACE, "CMediaTerminalFilter::Connect - " "using downstream allocator")); m_bUsingMyAllocator = false; } // // Notify the downstream input pin of the allocator we decided to use. // hr = pConnectedMemInputPin->NotifyAllocator( pAllocatorToUse, m_bUsingMyAllocator // if user's buffers, then read-only ); if ( FAILED(hr) ) { LOG((MSP_ERROR, "CMediaTerminalFilter::Connect - " "downstream filter rejected our allocator choice - exit 0x%08x", hr)); pAllocatorToUse->Release(); Disconnect(); pReceivePin->Disconnect(); return hr; } // // Connection has succeeded. // Make sure we let ourselves know that this is our allocator! // NOTE: m_pAllocator is a CComPtr!!! It holds a reference to // the object after the assignment, so we need to release our // local reference now. // m_pAllocator = pAllocatorToUse; pAllocatorToUse->Release(); // copy the media type into m_ConnectedMediaType CopyMediaType(&m_ConnectedMediaType, pMediaType); // member ptr to IMemInputPin i/f of the input pin m_pConnectedMemInputPin = pConnectedMemInputPin; // save a pointer to the connected pin m_pConnectedPin = pReceivePin; // get time to delay samples GetTimingInfo(m_ConnectedMediaType); LOG((MSP_TRACE, "CMediaTerminalFilter::Connect(%p, %p) succeeded", pReceivePin, pmt)); return hr; } // Return E_NOTIMPL to indicate that we have no requirements, // since even if the user has specified properties, we now // accept connections with other propertoes. STDMETHODIMP CMediaTerminalFilter::GetAllocatorRequirements( OUT ALLOCATOR_PROPERTIES *pProps ) { LOG((MSP_TRACE, "CMediaTerminalFilter::GetAllocatorRequirements[%p] - enter", this)); // // make sure some filter did not pass us a bad pointer // if (IsBadWritePtr(pProps, sizeof(ALLOCATOR_PROPERTIES))) { LOG((MSP_ERROR, "CMediaTerminalFilter::GetAllocatorRequirements - bad pointer [%p]", pProps)); return E_POINTER; } AUTO_CRIT_LOCK; // // were allocator properties set or suggested? // // fail if not -- this will indicate that we don't have a preference for // specific allocator properties // if ( !m_bUserAllocProps && !m_bSuggestedAllocProps ) { LOG((MSP_TRACE, "CMediaTerminalFilter::GetAllocatorRequirements - allocator properties were not set.")); // // E_NOTIMPL is the base class' way to show that we don't care about allocator properties. // return E_NOTIMPL so we don't break callers that depend on this error as a sign that // allocator properties make not difference to us. // return E_NOTIMPL; } // // allocator properties were set -- return them. // *pProps = m_AllocProps; DUMP_ALLOC_PROPS("CMediaTerminalFilter::GetAllocatorRequirements - ours:", pProps); LOG((MSP_TRACE, "CMediaTerminalFilter::GetAllocatorRequirements - exit. " "returning previously set allocator properties.")); return S_OK; } // we accept any media type // call CheckReceiveConnectionPin to verify the pin and if successful // copy media type and save the connector in m_pConnectedPin STDMETHODIMP CMediaTerminalFilter::ReceiveConnection( IPin * pConnector, const AM_MEDIA_TYPE *pmt ) { LOG((MSP_TRACE, "CMediaTerminalFilter::ReceiveConnection(%p, %p) called", pConnector, pmt)); // validate the passed-in media type pointer BAIL_IF_NULL(pmt, E_POINTER); BAIL_IF_NULL(pmt->pbFormat, VFW_E_INVALIDMEDIATYPE); AUTO_CRIT_LOCK; // // This helper function in CStream checks basic parameters for the Pin such as // the connecting pin's direction (we need to check this -- Sometimes the filter // graph will try to connect us to ourselves!) and other errors like already being // connected, etc. // HRESULT hr; hr= CheckReceiveConnectionPin(pConnector); BAIL_ON_FAILURE(hr); // if there is a suggested media type, we can't accept any // suggested media type that is not same as it if ( (NULL != m_pSuggestedMediaType) && (!IsSameAMMediaType(pmt, m_pSuggestedMediaType)) ) { return VFW_E_TYPE_NOT_ACCEPTED; } // copy media type and save the connector in m_pConnectedPin CopyMediaType(&m_ConnectedMediaType, pmt); m_pConnectedPin = pConnector; // get time to delay samples GetTimingInfo(m_ConnectedMediaType); LOG((MSP_TRACE, "CMediaTerminalFilter::ReceiveConnection(%p, %p) succeeded", pConnector, pmt)); return S_OK; } // the base class implementation doesn't validate the parameter // validate the parameter and call base class STDMETHODIMP CMediaTerminalFilter::ConnectionMediaType( AM_MEDIA_TYPE *pmt ) { AUTO_CRIT_LOCK; LOG((MSP_TRACE, "CMediaTerminalFilter::ConnectionMediaType(%p) called", pmt)); BAIL_IF_NULL(pmt, E_POINTER); return CStream::ConnectionMediaType(pmt); } // should accept all media types which match the major type corresponding to the purpose id STDMETHODIMP CMediaTerminalFilter::QueryAccept( const AM_MEDIA_TYPE *pmt ) { AUTO_CRIT_LOCK; LOG((MSP_TRACE, "CMediaTerminalFilter::QueryAccept(%p) called", pmt)); BAIL_IF_NULL(pmt, E_POINTER); BAIL_IF_NULL(m_pAmovieMajorType, MS_E_NOTINIT); // compare the filter major type with the queried AM_MEDIA_TYPE's major type if (0 != memcmp(&pmt->majortype, m_pAmovieMajorType, sizeof(GUID))) return S_FALSE; // if read side, return S_OK as we accepts any format // NOTE: FOR THE READ SIDE, QueryAccept is only called in SetFormat. // In ReceiveConnect, it checks against any user set properties // directly instead of calling QueryAccept if (PINDIR_INPUT == m_Direction) return S_OK; TM_ASSERT(NULL != pmt->pbFormat); if (NULL == pmt->pbFormat) { LOG((MSP_ERROR, "CMediaTerminalFilter::QueryAccept(%p) - returning S_FALSE, \ pbFormat = NULL", pmt)); return S_FALSE; } if (m_bIsAudio) { // if not the acceptable media type, return error TM_ASSERT(FORMAT_WaveFormatEx == pmt->formattype); if (FORMAT_WaveFormatEx != pmt->formattype) { LOG((MSP_ERROR, "CMediaTerminalFilter::QueryAccept(%p) - returning S_FALSE, \ audio format is not WaveFormatEx", pmt)); return S_FALSE; } if (0 == ((WAVEFORMATEX *)pmt->pbFormat)->nAvgBytesPerSec) { LOG((MSP_ERROR, "CMediaTerminalFilter::QueryAccept(%p) - returning S_FALSE, \ nAvgBytesPerSec = 0", pmt)); return S_FALSE; } } else { TM_ASSERT(MSPID_PrimaryVideo == m_PurposeId); TM_ASSERT(FORMAT_VideoInfo == pmt->formattype); if (FORMAT_VideoInfo != pmt->formattype) { LOG((MSP_ERROR, "CMediaTerminalFilter::QueryAccept(%p) - returning S_FALSE, \ video format is not VideoInfo", pmt)); return S_FALSE; } TM_ASSERT(0 != ((VIDEOINFO *)pmt->pbFormat)->AvgTimePerFrame); if (0 == ((VIDEOINFO *)pmt->pbFormat)->AvgTimePerFrame) { LOG((MSP_ERROR, "CMediaTerminalFilter::QueryAccept(%p) - returning S_FALSE, \ AvgTimePerFrame = 0", pmt)); return S_FALSE; } } LOG((MSP_TRACE, "CMediaTerminalFilter::QueryAccept(%p) succeeded", pmt)); return S_OK; } // CStream doesn't reset the end of stream flag, so over-ride STDMETHODIMP CMediaTerminalFilter::Disconnect( ) { LOG((MSP_TRACE, "CMediaTerminalFilter::Disconnect[%p] - enter", this)); Lock(); m_bEndOfStream = false; // this is a bool value // // If the MSP suggested allocator properties, then those // are never touched. // If the user has provided the allocator properties, then // reset modified allocator properties to the user's values. // If the user hasn't provided the allocator properties // reset modified allocator properties to default values. // if ( ! m_bSuggestedAllocProps ) { if ( m_bUserAllocProps ) { m_AllocProps = m_UserAllocProps; } else { SetDefaultAllocatorProperties(); } } HRESULT hr = CStream::Disconnect(); Unlock(); LOG((MSP_(hr), "CMediaTerminalFilter::Disconnect- finish. hr = %lx", hr)); return hr; } // if the ds queries us for our preferred media types // - we return the user suggested media type (suggested in SetFormat) if // there is one HRESULT CMediaTerminalFilter::GetMediaType( ULONG Index, AM_MEDIA_TYPE **ppMediaType ) { LOG((MSP_TRACE, "CMediaTerminalFilter::GetMediaType(%u, %p) called", Index, ppMediaType)); // we can have atmost one user suggested media type if (0 != Index) { LOG((MSP_ERROR, "CMediaTerminalFilter::GetMediaType(%u, %p) - invalid index,\ returning S_FALSE", Index, ppMediaType)); return S_FALSE; } // // ppMediaType must point to a pointer to AM_MEDIA_TYPE // if (TM_IsBadWritePtr(ppMediaType, sizeof(AM_MEDIA_TYPE *))) { LOG((MSP_ERROR, "CMediaTerminalFilter::GetMediaType(%u, %p) - bad input pointer " "returning E_POINTER", Index, ppMediaType)); return E_POINTER; } AUTO_CRIT_LOCK; // if no user suggested media type, return error if (NULL == m_pSuggestedMediaType) { LOG((MSP_ERROR, "CMediaTerminalFilter::GetMediaType(%u, %p) - \ no suggested media type, returning S_FALSE", Index, ppMediaType)); return S_FALSE; } // copy the user suggested media type into the passed in ppMediaType // create media type if necessary TM_ASSERT(NULL != m_pSuggestedMediaType); // creates an am media type initialized with the pmt value *ppMediaType = CreateMediaType(m_pSuggestedMediaType); BAIL_IF_NULL(*ppMediaType, E_OUTOFMEMORY); LOG((MSP_TRACE, "CMediaTerminalFilter::GetMediaType(%u, %p) succeeded", Index, ppMediaType)); return S_OK; } HRESULT CMediaTerminalFilter::AddToPoolIfCommitted( IN CSample *pSample ) { TM_ASSERT(NULL != pSample); AUTO_CRIT_LOCK; // check if committed if (!m_bCommitted) return VFW_E_NOT_COMMITTED; // addref the sample and // call AddSampleToFreePool pSample->AddRef(); AddSampleToFreePool(pSample); // we must have atleast one item in our queue TM_ASSERT(m_pFirstFree != NULL); return S_OK; } // first check if this sample is the one being fragmented currently, then // check the free pool BOOL CMediaTerminalFilter::StealSample( IN CSample *pSample ) { LOG((MSP_TRACE, "CMediaTerminalFilter::StealSample(%p) called", pSample)); BOOL bWorked = FALSE; AUTO_CRIT_LOCK; // if not committed, do nothing if ( !m_bCommitted ) { LOG((MSP_TRACE, "CMediaTerminalFilter::StealSample(%p) \ not committed - can't find sample", pSample)); return FALSE; } if (pSample == m_pSampleBeingFragmented) { // abort the sample when the last refcnt to its internal // IMediaStream is released m_pSampleBeingFragmented->AbortDuringFragmentation(); ((IStreamSample *)m_pSampleBeingFragmented)->Release(); m_pSampleBeingFragmented = NULL; LOG((MSP_TRACE, "CMediaTerminalFilter::StealSample(%p) \ was being fragmented - aborting", pSample)); // the caller wants to abort this sample immediately. since // we must wait for the last refcnt on IMediaStream to be released // tell the caller that the sample was not found return FALSE; } if (m_pFirstFree) { if (m_pFirstFree == pSample) { m_pFirstFree = pSample->m_pNextFree; if (m_pFirstFree) m_pFirstFree->m_pPrevFree = NULL; else m_pLastFree = NULL; pSample->m_pNextFree = NULL; // We know the prev ptr is already null! TM_ASSERT(pSample->m_pPrevFree == NULL); bWorked = TRUE; } else { if (pSample->m_pPrevFree) { pSample->m_pPrevFree->m_pNextFree = pSample->m_pNextFree; if (pSample->m_pNextFree) pSample->m_pNextFree->m_pPrevFree = pSample->m_pPrevFree; else m_pLastFree = pSample->m_pPrevFree; pSample->m_pNextFree = pSample->m_pPrevFree = NULL; bWorked = TRUE; } } CHECKSAMPLELIST } LOG((MSP_TRACE, "CMediaTerminalFilter::StealSample(%p) returns %d", pSample, bWorked)); return bWorked; } // sets the time to delay - per byte for audio, per frame for video void CMediaTerminalFilter::GetTimingInfo( IN const AM_MEDIA_TYPE &MediaType ) { AUTO_CRIT_LOCK; if (m_bIsAudio) { // assert if not an audio format TM_ASSERT(FORMAT_WaveFormatEx == MediaType.formattype); TM_ASSERT(NULL != MediaType.pbFormat); // number of milliseconds to delay per byte m_AudioDelayPerByte = DOUBLE(1000)/((WAVEFORMATEX *)MediaType.pbFormat)->nAvgBytesPerSec; } else { TM_ASSERT(MSPID_PrimaryVideo == m_PurposeId); TM_ASSERT(FORMAT_VideoInfo == MediaType.formattype); // number of milliseconds to delay per frame // AvgTimePerFrame is in 100ns units - convert to milliseconds m_VideoDelayPerFrame = DWORD(10000*((VIDEOINFO *)MediaType.pbFormat)->AvgTimePerFrame); } } void CMediaTerminalFilter::SetDefaultAllocatorProperties( ) { m_AllocProps.cbBuffer = DEFAULT_AM_MST_SAMPLE_SIZE; m_AllocProps.cBuffers = DEFAULT_AM_MST_NUM_BUFFERS; m_AllocProps.cbAlign = DEFAULT_AM_MST_BUFFER_ALIGNMENT; m_AllocProps.cbPrefix = DEFAULT_AM_MST_BUFFER_PREFIX; } // CTMStreamSample // calls InitSample(pStream, bIsInternalSample) // sets member variables HRESULT CTMStreamSample::Init( CStream &Stream, bool bIsInternalSample, PBYTE pBuffer, LONG BufferSize ) { LOG((MSP_TRACE, "CTMStreamSample::Init(&%p, %d, %p, %d) called", &Stream, bIsInternalSample, pBuffer, BufferSize)); TM_ASSERT(NULL == m_pBuffer); HRESULT hr; hr = InitSample(&Stream, bIsInternalSample); BAIL_ON_FAILURE(hr); m_BufferSize = BufferSize; m_pBuffer = pBuffer; LOG((MSP_TRACE, "CTMStreamSample::Init(&%p, %d, %p, %d) succeeded", &Stream, bIsInternalSample, pBuffer, BufferSize)); return S_OK; } void CTMStreamSample::CopyFrom( IMediaSample *pSrcMediaSample ) { m_bModified = true; HRESULT HResult = pSrcMediaSample->GetTime( &m_pMediaSample->m_rtStartTime, &m_pMediaSample->m_rtEndTime ); m_pMediaSample->m_dwFlags = (!HRESULT_FAILURE(HResult)) ? AM_SAMPLE_TIMEVALID | AM_SAMPLE_STOPVALID : 0; m_pMediaSample->m_dwFlags |= (pSrcMediaSample->IsSyncPoint() == S_OK) ? 0 : AM_GBF_NOTASYNCPOINT; m_pMediaSample->m_dwFlags |= (pSrcMediaSample->IsDiscontinuity() == S_OK) ? AM_GBF_PREVFRAMESKIPPED : 0; m_pMediaSample->m_bIsPreroll = (pSrcMediaSample->IsPreroll() == S_OK); } // calls CTMStreamSample::Init, sets members HRESULT CQueueMediaSample::Init( IN CStream &Stream, IN CNBQueue &Queue ) { m_pSampleQueue = &Queue; return CTMStreamSample::Init(Stream, TRUE, NULL, 0); } // this is used to hold a ptr to a segment of a user sample buffer // it also holds a reference to the user sample's IMediaSample i/f and // releases it when done void CQueueMediaSample::HoldFragment( IN DWORD FragSize, IN BYTE *pbData, IN IMediaSample &FragMediaSample ) { LOG((MSP_TRACE, "CQueueMediaSample::HoldFragment(%u, %p, &%p) called", FragSize, pbData, &FragMediaSample)); AUTO_SAMPLE_LOCK; TM_ASSERT(0 < (LONG) FragSize); TM_ASSERT(NULL != pbData); // // set media sample properties // timestamp is set by the CMediaTerminalFilter // m_pMediaSample->m_dwFlags = 0; m_bReceived = FALSE; m_bModified = TRUE; SetBufferInfo(FragSize, // buffer size pbData, // pointer to buffer FragSize // amount of buffer currently used ); // ref to the user sample's media sample // NOTE: m_pFragMediaSample is a CComPtr m_pFragMediaSample = &FragMediaSample; LOG((MSP_TRACE, "CQueueMediaSample::HoldFragment(%u, %p, &%p) succeeded", FragSize, pbData, &FragMediaSample)); } void CQueueMediaSample::FinalMediaSampleRelease( ) { LOG((MSP_TRACE, "CQueueMediaSample::FinalMediaSampleRelease[%p] - enter", this)); // NOTE : no one holds a reference to the media sample at this point LOCK_SAMPLE; // if we hold a reference to the IMediaSample i/f of a user sample // being fragmented, release it // NOTE: m_pFragMediaSample is a CComPtr if (m_pFragMediaSample != NULL) m_pFragMediaSample = NULL; // check if the stream is still committed, otherwise destroy self if ( m_pStream->m_bCommitted ) { BOOL bNQSuccess = m_pSampleQueue->EnQueue(this); UNLOCK_SAMPLE; // // if failed to enqueue -- kill itself, no one cares. // if (!bNQSuccess) { LOG((MSP_WARN, "CQueueMediaSample::FinalMediaSampleRelease - failed to enqueue. delete this")); delete this; } } else { // this is in case the stream has already been decommitted UNLOCK_SAMPLE; delete this; LOG((MSP_WARN, "CQueueMediaSample::FinalMediaSampleRelease - stream not committed. delete this")); } LOG((MSP_TRACE, "CQueueMediaSample::FinalMediaSampleRelease succeeded")); } #if DBG // virtual CQueueMediaSample::~CQueueMediaSample( ) { } #endif // DBG // if asked to allocate buffers, verify allocator properties /* static */ BOOL CUserMediaSample::VerifyAllocatorProperties( IN BOOL bAllocateBuffers, IN const ALLOCATOR_PROPERTIES &AllocProps ) { if (!bAllocateBuffers) return TRUE; if (0 != AllocProps.cbPrefix) return FALSE; if (0 == AllocProps.cbAlign) return FALSE; return TRUE; } // this is called in AllocateSample (creates an instance and initializes it) // creates a data buffer if none is provided (current behaviour) // also calls CTMStreamSample::InitSample(pStream, bIsInternalSample) HRESULT CUserMediaSample::Init( IN CStream &Stream, IN BOOL bAllocateBuffer, IN DWORD ReqdBufferSize, IN const ALLOCATOR_PROPERTIES &AllocProps ) { LOG((MSP_TRACE, "CUserMediaSample::Init[%p](&%p, %u, &%p) called", this, &Stream, bAllocateBuffer, &AllocProps)); TM_ASSERT(VerifyAllocatorProperties(bAllocateBuffer, AllocProps)); TM_ASSERT(FALSE == m_bWeAllocatedBuffer); TM_ASSERT(NULL == m_pBuffer); HRESULT hr; hr = CTMStreamSample::InitSample(&Stream, FALSE); BAIL_ON_FAILURE(hr); // the caller wants us to create the buffer if (bAllocateBuffer) { // determine size of buffer to allocate // we use the user suggested buffer size (if not 0), else // we use the negotiated allocator properties' buffer size m_BufferSize = (0 != ReqdBufferSize) ? ReqdBufferSize : AllocProps.cbBuffer; LOG((MSP_TRACE, "CUserMediaSample::Init creating buffer buffersize[%d]", m_BufferSize)); m_pBuffer = new BYTE[m_BufferSize]; BAIL_IF_NULL(m_pBuffer, E_OUTOFMEMORY); m_bWeAllocatedBuffer = TRUE; } else // the user will provide the buffer later { // // the user will need to submit buffers of at least this size -- filter // promised this during allocator properties negotiation // m_dwRequiredBufferSize = AllocProps.cbBuffer; LOG((MSP_TRACE, "CUserMediaSample::Init -- the app will need to provide buffers of size 0x%lx", m_dwRequiredBufferSize)); m_BufferSize = 0; m_pBuffer = NULL; TM_ASSERT(!m_bWeAllocatedBuffer); } TM_ASSERT(0 == m_DataSize); LOG((MSP_TRACE, "CUserMediaSample::Init(&%p, %u, &%p) succeeded", &Stream, bAllocateBuffer, &AllocProps)); return S_OK; } void CUserMediaSample::BeginFragment( IN BOOL bNoteCurrentTime ) { LOG((MSP_TRACE, "CUserMediaSample::BeginFragment (frag=%p)", this)); AUTO_SAMPLE_LOCK; // we are being fragmented m_bBeingFragmented = TRUE; // note current time if (bNoteCurrentTime) m_BeginFragmentTime = timeGetTime(); // nothing has been fragmented yet m_NumBytesFragmented = 0; // increment refcnt to the internal media sample. this ensures that // FinalMediaSampleRelease is not called until the last fragment is // completed m_pMediaSample->AddRef(); // increment refcnt to self. this ensures that we'll exist while // the last fragment has not returned ((IStreamSample *)this)->AddRef(); } ////////////////////////////////////////////////////////////////////////////// // // Fragment // // For write -- Assigns a chunk of this user media sample to an outgoing // sample in the filter graph, a CQueueMediaSample. The data is not actually // copied; instead, CQeueMediaSample::HoldFragment is called to set the pointers // to the appropriate pointer of the user media sample. // void CUserMediaSample::Fragment( IN BOOL bFragment, // actually fragment? false if video IN LONG AllocBufferSize, // max amount of data to copy IN OUT CQueueMediaSample &QueueMediaSample, // destination sample OUT BOOL &bDone // out: set to true if no data left in source ) { LOG((MSP_TRACE, "CUserMediaSample::Fragment(%u, %l, &%p, &%p) called (frag=%p)", bFragment, AllocBufferSize, &QueueMediaSample, &bDone, this)); AUTO_SAMPLE_LOCK; TM_ASSERT(m_bBeingFragmented); TM_ASSERT(m_NumBytesFragmented < m_DataSize); // // DestSize = amount of data we are actually going to copy // LONG DestSize; if (bFragment) { DestSize = min(AllocBufferSize, m_DataSize - m_NumBytesFragmented); } else { TM_ASSERT(0 == m_NumBytesFragmented); DestSize = m_DataSize; } // // pass the fragment to the queue sample // QueueMediaSample.HoldFragment( DestSize, m_pBuffer + m_NumBytesFragmented, *m_pMediaSample ); // // increment number of bytes fragmented // m_NumBytesFragmented += DestSize; // // let the caller know if we are done with fragmenting our buffer // bDone = ((m_NumBytesFragmented >= m_DataSize) ? TRUE : FALSE); // // if we are done, we should release our reference to the internal // IMediaSample instance. this was acquired when BeginFragment was called // if (bDone) { m_bReceived = TRUE; // needed for FinalMediaSampleRelease m_pMediaSample->Release(); } LOG((MSP_TRACE, "CUserMediaSample::Fragment(%u, %l, &%p, &%p) succeeded (frag=%p)", bFragment, AllocBufferSize, &QueueMediaSample, &bDone, this)); } ////////////////////////////////////////////////////////////////////////////// // // CopyFragment // // For write -- copies a chunk of this user media sample to an outgoing // sample in the filter graph. This is for when we are using a downstream // allocator. // HRESULT CUserMediaSample::CopyFragment( IN BOOL bFragment, // actually fragment? false if video IN LONG AllocBufferSize, // max amount of data to copy IN OUT IMediaSample * pDestMediaSample, // destination sample OUT BOOL & bDone // out: set to true if no data left in source ) { LOG((MSP_TRACE, "CUserMediaSample::CopyFragment(%u, %ld, &%p, &%p) called (frag=%p)", bFragment, AllocBufferSize, &pDestMediaSample, &bDone, this)); AUTO_SAMPLE_LOCK; TM_ASSERT(m_bBeingFragmented); TM_ASSERT(m_NumBytesFragmented < m_DataSize); // // DestSize = amount of data we are actually going to copy // // IMediaSmaple::GetSize has a weird prototype -- returns HRESULT // but it's actually just the size as a LONG // LONG lDestSize; if ( bFragment ) { // // We copy as much as we have left to copy or as much as will fit in // a sample, whichever is less. // lDestSize = min( AllocBufferSize, m_DataSize - m_NumBytesFragmented ); // // If the sample has less space than the allocator propeties said it // would have, then trim lDestSize to that value. We don't just use // pDestMediaSample->GetSize() instead of AllocBufferSize above because // we want to use the allocator properties size if the sample has *more* // space than the allocator properties specify. // lDestSize = min( pDestMediaSample->GetSize(), lDestSize ); } else { // video case -- copy entire sample // we bail if the destination sample isn't big enough TM_ASSERT(0 == m_NumBytesFragmented); lDestSize = m_DataSize; if ( ( lDestSize > AllocBufferSize ) || ( lDestSize > pDestMediaSample->GetSize() ) ) { return VFW_E_BUFFER_OVERFLOW; } } // // copy the fragment to the destination sample // instead of CQUeueMediaSample::HoldFragment // HRESULT hr; BYTE * pDestBuffer; hr = pDestMediaSample->GetPointer( & pDestBuffer ); if ( FAILED(hr) ) { return hr; } CopyMemory( pDestBuffer, // destination buffer m_pBuffer + m_NumBytesFragmented, // source buffer lDestSize ); hr = pDestMediaSample->SetActualDataLength( lDestSize ); if ( FAILED(hr) ) { return hr; } // // increment number of bytes fragmented // m_NumBytesFragmented += lDestSize; // // let the caller know if we are done with fragmenting our buffer // bDone = ((m_NumBytesFragmented >= m_DataSize) ? TRUE : FALSE); // // if we are done, we should release our reference to the internal // IMediaSample instance. this was acquired when BeginFragment was called // if (bDone) { m_bReceived = TRUE; // needed for FinalMediaSampleRelease m_pMediaSample->Release(); } LOG((MSP_TRACE, "CUserMediaSample::CopyFragment(%u, %ld, &%p, &%p) succeeded (frag=%p)", bFragment, AllocBufferSize, &pDestMediaSample, &bDone, this)); return S_OK; } // computes the time to wait. it checks the time at which the first // byte of the current fragment would be due and subtracts the // time delay since the beginning of fragmentation DWORD CUserMediaSample::GetTimeToWait( IN DOUBLE DelayPerByte ) { LOG((MSP_TRACE, "CUserMediaSample::GetTimeToWait(%f) called", DelayPerByte)); // get current time DWORD CurrentTime = timeGetTime(); AUTO_SAMPLE_LOCK; // calculate the time elapsed since BeginFragment was called, // account for wrap around DWORD TimeSinceBeginFragment = (CurrentTime >= m_BeginFragmentTime) ? (CurrentTime - m_BeginFragmentTime) : (DWORD(-1) - m_BeginFragmentTime + CurrentTime); DWORD DueTime = DWORD(m_NumBytesFragmented*DelayPerByte); LOG((MSP_INFO, "DueTime = %u, TimeSinceBeginFragment = %u", DueTime, TimeSinceBeginFragment)); // if due in future, return the difference, else return 0 DWORD TimeToWait; if (DueTime > TimeSinceBeginFragment) TimeToWait = DueTime - TimeSinceBeginFragment; else TimeToWait = 0; LOG((MSP_INFO, "CUserMediaSample::GetTimeToWait(%f) returns %u successfully", DelayPerByte, TimeToWait)); return TimeToWait; } // when we are decommitted/aborted while being fragmented, we // need to get rid of our refcnt on internal IMediaSample and set // the error code to E_ABORT. this will be signaled to the user only when // the last refcnt on IMediaSample is released (possibly by an outstanding // queue sample) void CUserMediaSample::AbortDuringFragmentation( ) { LOG((MSP_TRACE, "CUserMediaSample::AbortDuringFragmentation (frag=%p)", this)); AUTO_SAMPLE_LOCK; TM_ASSERT(m_bBeingFragmented); m_MediaSampleIoStatus = E_ABORT; // release reference on internal IMediaSample instance // this was acquired when BeginFragment was called m_pMediaSample->Release(); } STDMETHODIMP CUserMediaSample::SetBuffer( IN DWORD cbSize, IN BYTE * pbData, IN DWORD dwFlags ) { LOG((MSP_TRACE, "CUserMediaSample::SetBuffer[%p](%lu, %p, %lu) called", this, cbSize, pbData, dwFlags)); if (dwFlags != 0 || cbSize == 0) { return E_INVALIDARG; } // cannot accept a positive value that doesn't fit in a LONG // currently (based upon CSample implementation) if ((LONG)cbSize < 0) { LOG((MSP_WARN, "CUserMediaSample::SetBuffer - the buffer is too large. " "returning E_FAIL")); return E_FAIL; } // cannot accept null data buffer // // we don't want to do IsBadWritePtr here as this method could be called // on every sample, so veryfying the memory could be expensive // if (NULL == pbData) { LOG((MSP_WARN, "CUserMediaSample::SetBuffer - buffer pointer is NULL " "returning E_POINTER")); return E_POINTER; } // // the app needs to give us at least as much memory as we have promised to // other filters in the graph (this number was specified when the sample was // created and initialized). // // if we don't do this check, a downstream filter may av because it expects // a bigger buffer. // if ( m_dwRequiredBufferSize > cbSize ) { LOG((MSP_WARN, "CUserMediaSample::SetBuffer - the app did not allocate enough memory " "Need 0x%lx bytes, app allocated 0x%lx. returning TAPI_E_NOTENOUGHMEMORY", m_dwRequiredBufferSize, cbSize)); return TAPI_E_NOTENOUGHMEMORY; } AUTO_SAMPLE_LOCK; // Free anything we allocated ourselves // -- We allow multiple calls to this method if (m_bWeAllocatedBuffer) { delete m_pBuffer; m_bWeAllocatedBuffer = FALSE; m_pBuffer = NULL; } m_BufferSize = cbSize; m_DataSize = 0; m_pBuffer = pbData; LOG((MSP_TRACE, "CUserMediaSample::SetBuffer(%u, %p, %u) succeeded", cbSize, pbData, dwFlags)); return S_OK; } STDMETHODIMP CUserMediaSample::GetInfo( OUT DWORD *pdwLength, OUT BYTE **ppbData, OUT DWORD *pcbActualData ) { AUTO_SAMPLE_LOCK; LOG((MSP_TRACE, "CUserMediaSample::GetInfo(%p, %p, %p) called", pdwLength, ppbData, pcbActualData)); if (m_BufferSize == 0) { LOG((MSP_WARN, "CUserMediaSample::GetInfo - sample not initialized")); return MS_E_NOTINIT; } if (NULL != pdwLength) { LOG((MSP_TRACE, "CUserMediaSample::GetInfo - pdwLength is not NULL.")); *pdwLength = m_BufferSize; } if (NULL != ppbData) { LOG((MSP_TRACE, "CUserMediaSample::GetInfo - ppbData is not NULL.")); *ppbData = m_pBuffer; } if (NULL != pcbActualData) { LOG((MSP_TRACE, "CUserMediaSample::GetInfo - pcbActualData is not NULL.")); *pcbActualData = m_DataSize; } LOG((MSP_TRACE, "CUserMediaSample::GetInfo - succeeded. " "m_BufferSize[%lu(decimal)] m_pBuffer[%p] m_DataSize[%lx]", m_BufferSize, m_pBuffer, m_DataSize)); return S_OK; } STDMETHODIMP CUserMediaSample::SetActual( IN DWORD cbDataValid ) { AUTO_SAMPLE_LOCK; LOG((MSP_TRACE, "CUserMediaSample::SetActual(%u) called", cbDataValid)); // cannot accept a positive value that doesn't fit in a LONG // currently (based upon CSample implementation) if ((LONG)cbDataValid < 0) return E_FAIL; if ((LONG)cbDataValid > m_BufferSize) return E_INVALIDARG; m_DataSize = cbDataValid; LOG((MSP_TRACE, "CUserMediaSample::SetActual(%u) succeeded", cbDataValid)); return S_OK; } // redirects this call to ((CMediaTerminalFilter *)m_pStream) STDMETHODIMP CUserMediaSample::get_MediaFormat( /* [optional]??? */ OUT AM_MEDIA_TYPE **ppFormat ) { AUTO_SAMPLE_LOCK; LOG((MSP_TRACE, "CUserMediaSample::get_MediaFormat(%p) called", ppFormat)); return ((CMediaTerminalFilter *)m_pStream)->GetFormat(ppFormat); } // this is not allowed STDMETHODIMP CUserMediaSample::put_MediaFormat( IN const AM_MEDIA_TYPE *pFormat ) { AUTO_SAMPLE_LOCK; LOG((MSP_TRACE, "CUserMediaSample::put_MediaFormat(%p) called", pFormat)); return E_NOTIMPL; } // calls the base class FinalMediaSampleRelease and then releases // self refcnt. this self refcnt ensures that when this method is // called, the sample still exists void CUserMediaSample::FinalMediaSampleRelease( ) { AUTO_SAMPLE_LOCK; // this signals the user that the sample has completed CTMStreamSample::FinalMediaSampleRelease(); // release self reference if we are being fragmented // this is only needed to ensure that we exist when the last // reference to the internal IMediaSample interface is released if (m_bBeingFragmented) m_bBeingFragmented = FALSE; // this self reference was obtained when // when fragmentation began (for write side) or // when the sample refcnt was not released on removal from pool in // GetBuffer (for read side) // NOTE: the sample may go away after this release ((IStreamSample *)this)->Release(); } HRESULT CUserMediaSample::CopyFrom( IN IMediaSample *pSrcMediaSample ) { LOG((MSP_TRACE, "CUserMediaSample::CopyFrom(%p) called", pSrcMediaSample)); AUTO_SAMPLE_LOCK; TM_ASSERT(NULL != m_pBuffer); // sets the "non-data" member values CTMStreamSample::CopyFrom(pSrcMediaSample); // get the buffer ptr BYTE *pBuffer; HRESULT hr; hr = pSrcMediaSample->GetPointer(&pBuffer); BAIL_ON_FAILURE(hr); TM_ASSERT(NULL != pBuffer); // determine the number of bytes to copy LONG lDataSize = pSrcMediaSample->GetActualDataLength(); TM_ASSERT(0 <= lDataSize); if (0 > lDataSize) return E_FAIL; if (lDataSize > m_BufferSize) { hr = HRESULT_FROM_WIN32(ERROR_MORE_DATA); lDataSize = m_BufferSize; } // copy data and set the data size to the number of bytes copied memcpy(m_pBuffer, pBuffer, lDataSize); m_DataSize = lDataSize; LOG((MSP_TRACE, "CUserMediaSample::CopyFrom(%p) returns hr=%u", pSrcMediaSample, hr)); // we may return ERROR_MORE_DATA after copying the buffer, so return hr return hr; } // copies the non-data members of pSrcMediaSample // copies as much as possible of the data buffer into own buffer // and advances pBuffer and DataLength beyond the copied data HRESULT CUserMediaSample::CopyFrom( IN IMediaSample *pSrcMediaSample, IN OUT BYTE *&pBuffer, IN OUT LONG &DataLength ) { LOG((MSP_TRACE, "CUserMediaSample::CopyFrom(%p, &%p, &%l) called", pSrcMediaSample, pBuffer, DataLength)); if (NULL == pBuffer) return E_FAIL; if (0 > DataLength) return E_FAIL; AUTO_SAMPLE_LOCK; TM_ASSERT(NULL != m_pBuffer); TM_ASSERT(NULL != pBuffer); TM_ASSERT(0 <= DataLength); // sets the "non-data" member values CTMStreamSample::CopyFrom(pSrcMediaSample); HRESULT hr = S_OK; LONG lDataSize = DataLength; if (lDataSize > m_BufferSize) { hr = HRESULT_FROM_WIN32(ERROR_MORE_DATA); lDataSize = m_BufferSize; } // copy data and set the data size to the number of bytes copied memcpy(m_pBuffer, pBuffer, lDataSize); m_DataSize = lDataSize; // advance the parameters beyond the copied data pBuffer += lDataSize; DataLength -= lDataSize; LOG((MSP_TRACE, "CUserMediaSample::CopyFrom(&%p, &%p, %l) returns hr=%u", pSrcMediaSample, pBuffer, DataLength, hr)); // we may return ERROR_MORE_DATA after copying the buffer, so return hr return hr; } // NOTE : This has been copied from the CSample base class because // StealSampleFromFreePool doesn't Release the ref count on the stolen // sample // in this implementation, we have made sure that each sample in the // CStream free pool has a refcnt increase. therefore, we need to decrease // it if stealing is successful. Moreover, we also try to steal the // sample being fragmented currently although its not in the free pool STDMETHODIMP CUserMediaSample::CompletionStatus(DWORD dwFlags, DWORD dwMilliseconds) { LOG((MSP_TRACE, "CUserMediaSample::CompletionStatus(0x%8.8X, 0x%8.8X) called", dwFlags, dwMilliseconds)); LOCK_SAMPLE; HRESULT hr = m_Status; if (hr == MS_S_PENDING) { if (dwFlags & (COMPSTAT_NOUPDATEOK | COMPSTAT_ABORT) || (m_bContinuous && m_bModified && (dwFlags & COMPSTAT_WAIT))) { m_bContinuous = false; if (dwFlags & COMPSTAT_ABORT) { m_bWantAbort = true; // Set this so we won't add it back to the free pool if released } if (((CMediaTerminalFilter *)m_pStream)->StealSample(this)) { UNLOCK_SAMPLE; hr = SetCompletionStatus(m_bModified ? S_OK : MS_S_NOUPDATE); ((IStreamSample *)this)->Release(); return hr; } // If doesn't work then return MS_S_PENDING unless we're told to wait! } if (dwFlags & COMPSTAT_WAIT) { m_bContinuous = false; // Make sure it will complete! UNLOCK_SAMPLE; WaitForSingleObject(m_hCompletionEvent, dwMilliseconds); LOCK_SAMPLE; hr = m_Status; } } UNLOCK_SAMPLE; LOG((MSP_TRACE, "CUserMediaSample::CompletionStatus(0x%8.8X, 0x%8.8X) succeeded", dwFlags, dwMilliseconds)); return hr; } // NOTE : This has been copied from the CSample base class // because it calls m_pStream->AddSampleToFreePool to add sample // to the CStream q. Since this shouldn't be happening after Decommit, // m_pStream->AddSampleToFreePool has been replaced by another call // m_pStream->AddToPoolIfCommitted that checks m_bCommitted and if // FALSE, returns error // // Set the sample's status and signal completion if necessary. // // Note that when the application has been signalled by whatever method // the application can immediately turn around on another thread // and Release() the sample. This is most likely when the completion // status is set from the quartz thread that's pushing the data. // HRESULT CUserMediaSample::SetCompletionStatus( IN HRESULT hrStatus ) { LOCK_SAMPLE; TM_ASSERT(m_Status == MS_S_PENDING); if (hrStatus == MS_S_PENDING || (hrStatus == S_OK && m_bContinuous)) { // // We are not done with the sample -- put it back in our pool so that // we can use it again. // HRESULT hr; hr = ((CMediaTerminalFilter *)m_pStream)->AddToPoolIfCommitted(this); // there is an error, so signal this to the user if (HRESULT_FAILURE(hr)) hrStatus = hr; else { UNLOCK_SAMPLE; return hrStatus; } } // // The sample is ready to be returned to the app -- signal comletion. // We still have a lock. // HANDLE handle = m_hUserHandle; PAPCFUNC pfnAPC = m_UserAPC; DWORD_PTR dwptrAPCData = m_dwptrUserAPCData; // win64 fix m_hUserHandle = m_UserAPC = NULL; m_dwptrUserAPCData = 0; m_Status = hrStatus; HANDLE hCompletionEvent = m_hCompletionEvent; UNLOCK_SAMPLE; // DANGER DANGER - sample can go away here SetEvent(hCompletionEvent); if (pfnAPC) { // queue the APC and close the targe thread handle // the calling thread handle was duplicated when Update // was called QueueUserAPC(pfnAPC, handle, dwptrAPCData); CloseHandle(handle); } else { if (handle) { SetEvent(handle); } } return hrStatus; } // this method has been copied from the CSample implementation // Now SetCompletionStatus returns an error code that can be failure // this method didn't check for the error code and hence had to // be over-ridden and modified to do so // we must not reset user event as the user may pass in the same event for // all samples HRESULT CUserMediaSample::InternalUpdate( DWORD dwFlags, HANDLE hEvent, PAPCFUNC pfnAPC, DWORD_PTR dwptrAPCData ) { if ((hEvent && pfnAPC) || (dwFlags & (~(SSUPDATE_ASYNC | SSUPDATE_CONTINUOUS)))) { return E_INVALIDARG; } if (m_Status == MS_S_PENDING) { return MS_E_BUSY; } // if we don't have a buffer to operate upon, return error if (NULL == m_pBuffer) return E_FAIL; if (NULL != m_pStream->m_pMMStream) { STREAM_STATE StreamState; m_pStream->m_pMMStream->GetState(&StreamState); if (StreamState != STREAMSTATE_RUN) { return MS_E_NOTRUNNING; } } ResetEvent(m_hCompletionEvent); m_Status = MS_S_PENDING; m_bWantAbort = false; m_bModified = false; m_bContinuous = (dwFlags & SSUPDATE_CONTINUOUS) != 0; m_UserAPC = pfnAPC; TM_ASSERT(NULL == m_hUserHandle); if (pfnAPC) { BOOL bDuplicated = DuplicateHandle( GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &m_hUserHandle, 0, // ignored TRUE, DUPLICATE_SAME_ACCESS ); if (!bDuplicated) { DWORD LastError = GetLastError(); LOG((MSP_ERROR, "CUserMediaSample::InternalUpdate - \ couldn't duplicate calling thread handle - error %u", LastError)); return HRESULT_FROM_ERROR_CODE(LastError); } m_dwptrUserAPCData = dwptrAPCData; } else { m_hUserHandle = hEvent; // rajeevb - also used to reset the user provided event // this is not being done any more as the user may provide // the same event for more than one sample and we may reset // a signaled event } // // If we're at the end of the stream, wait until this point before punting it // because we need to signal the event or fire the APC. // if (m_pStream->m_bEndOfStream) { // Because this is called synchronously from Update the // application must have a ref count on the sample until we // return so we don't have to worry about it going away here return SetCompletionStatus(MS_S_ENDOFSTREAM); } // rajeevb - need to check for SetCompletionStatus error code HRESULT hr; hr = SetCompletionStatus(MS_S_PENDING); // This adds us to the free pool. BAIL_ON_FAILURE(hr); if (hEvent || pfnAPC || (dwFlags & SSUPDATE_ASYNC)) { return MS_S_PENDING; } else { return S_OK; } }