Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

4018 lines
110 KiB

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