|
|
/*
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 <stdio.h>
#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<CUserMediaSample> *pUserSample; hr = CComObject<CUserMediaSample>::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<CQueueMediaSample> *pQueueSample = NULL; hr = CComObject<CQueueMediaSample>::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<IMemInputPin, &IID_IMemInputPin> 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 = <required props>
//
//
// 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<CQueueMediaSample> &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; } }
|