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
114 KiB
4018 lines
114 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;
|
|
}
|
|
}
|