1162 lines
30 KiB
C
1162 lines
30 KiB
C
|
/*
|
||
|
|
||
|
Copyright (c) 1998-1999 Microsoft Corporation
|
||
|
|
||
|
*/
|
||
|
|
||
|
#ifndef __MEDIA_TERMINAL_FILTER__
|
||
|
#define __MEDIA_TERMINAL_FILTER__
|
||
|
|
||
|
// include header files for the amovie types
|
||
|
#include "Stream.h"
|
||
|
#include "Sample.h"
|
||
|
|
||
|
// number of internal buffers allocated by default
|
||
|
// (for write terminal)
|
||
|
const DWORD DEFAULT_AM_MST_NUM_BUFFERS = 5;
|
||
|
|
||
|
// while this is a LONG, it should actually be a positive value that'll
|
||
|
// fit in a LONG (the buffer size and data size variables of the sample)
|
||
|
// are LONG, so this is long as well
|
||
|
const LONG DEFAULT_AM_MST_SAMPLE_SIZE = 640;
|
||
|
|
||
|
// alignment of buffers allocated
|
||
|
const LONG DEFAULT_AM_MST_BUFFER_ALIGNMENT = 1;
|
||
|
|
||
|
// number of prefix bytes in buffers allocated
|
||
|
const LONG DEFAULT_AM_MST_BUFFER_PREFIX = 0;
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// CNBQueue
|
||
|
//
|
||
|
// Non blocking version of active movie queue class. Very basic Q built
|
||
|
// entirely on Win32.
|
||
|
//
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
template <class T> class CNBQueue {
|
||
|
private:
|
||
|
HANDLE hSemPut; // Semaphore controlling queue "putting"
|
||
|
HANDLE hSemGet; // Semaphore controlling queue "getting"
|
||
|
CRITICAL_SECTION CritSect; // Thread seriallization
|
||
|
int nMax; // Max objects allowed in queue
|
||
|
int iNextPut; // Array index of next "PutMsg"
|
||
|
int iNextGet; // Array index of next "GetMsg"
|
||
|
T **QueueObjects; // Array of objects (ptr's to void)
|
||
|
|
||
|
public:
|
||
|
|
||
|
|
||
|
BOOL InitializeQ(int n)
|
||
|
{
|
||
|
|
||
|
LOG((MSP_TRACE, "CNBQueue::InitializeQ[%p] - enter", this));
|
||
|
|
||
|
//
|
||
|
// the argument had better be valid
|
||
|
//
|
||
|
|
||
|
if (0 > n)
|
||
|
{
|
||
|
TM_ASSERT(FALSE);
|
||
|
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
|
||
|
if (QueueObjects != NULL)
|
||
|
{
|
||
|
|
||
|
//
|
||
|
// already initialized. this is a bug.
|
||
|
//
|
||
|
|
||
|
TM_ASSERT(FALSE);
|
||
|
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
|
||
|
iNextPut = 0;
|
||
|
iNextGet = 0;
|
||
|
|
||
|
|
||
|
//
|
||
|
// attempt to create critical section
|
||
|
//
|
||
|
|
||
|
try
|
||
|
{
|
||
|
|
||
|
InitializeCriticalSection(&CritSect);
|
||
|
}
|
||
|
catch(...)
|
||
|
{
|
||
|
|
||
|
//
|
||
|
// failed to create critical section
|
||
|
//
|
||
|
|
||
|
LOG((MSP_ERROR, "CNBQueue::InitializeQ - failed to initialize critical section"));
|
||
|
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
|
||
|
//
|
||
|
// attempt to create a semaphore
|
||
|
//
|
||
|
|
||
|
TCHAR *ptczSemaphoreName = NULL;
|
||
|
|
||
|
#if DBG
|
||
|
|
||
|
//
|
||
|
// in debug build, use named semaphores.
|
||
|
//
|
||
|
|
||
|
TCHAR tszPutSemaphoreName[MAX_PATH];
|
||
|
|
||
|
_stprintf(tszPutSemaphoreName,
|
||
|
_T("CNBQueuePutSemaphore_pid[0x%lx]_CNBQueue[%p]_"),
|
||
|
GetCurrentProcessId(), this);
|
||
|
|
||
|
|
||
|
LOG((MSP_TRACE, "CNBQueue::InitializeQ - creating put semaphore [%S]",
|
||
|
tszPutSemaphoreName));
|
||
|
|
||
|
|
||
|
ptczSemaphoreName = &tszPutSemaphoreName[0];
|
||
|
|
||
|
|
||
|
#endif
|
||
|
|
||
|
hSemPut = CreateSemaphore(NULL, n, n, ptczSemaphoreName);
|
||
|
|
||
|
if (NULL == hSemPut)
|
||
|
{
|
||
|
//
|
||
|
// cleanup and exit
|
||
|
//
|
||
|
|
||
|
DeleteCriticalSection(&CritSect);
|
||
|
|
||
|
LOG((MSP_ERROR, "CNBQueue::InitializeQ - failed to create put semaphore"));
|
||
|
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
#if DBG
|
||
|
|
||
|
//
|
||
|
// in debug build, use named semaphores.
|
||
|
//
|
||
|
|
||
|
TCHAR tszGetSemaphoreName[MAX_PATH];
|
||
|
|
||
|
_stprintf(tszGetSemaphoreName,
|
||
|
_T("CNBQueueGetSemaphore_pid[0x%lx]_CNBQueue[%p]_"),
|
||
|
GetCurrentProcessId(), this);
|
||
|
|
||
|
|
||
|
LOG((MSP_TRACE, "CNBQueue::InitializeQ - creating get semaphore [%S]",
|
||
|
tszGetSemaphoreName));
|
||
|
|
||
|
|
||
|
ptczSemaphoreName = &tszGetSemaphoreName[0];
|
||
|
|
||
|
|
||
|
#endif
|
||
|
|
||
|
|
||
|
hSemGet = CreateSemaphore(NULL, 0, n, ptczSemaphoreName);
|
||
|
|
||
|
if (NULL == hSemGet)
|
||
|
{
|
||
|
//
|
||
|
// cleanup and exit
|
||
|
//
|
||
|
|
||
|
CloseHandle(hSemPut);
|
||
|
hSemPut = NULL;
|
||
|
|
||
|
|
||
|
DeleteCriticalSection(&CritSect);
|
||
|
|
||
|
|
||
|
LOG((MSP_ERROR, "CNBQueue::InitializeQ - failed to create get semaphore"));
|
||
|
|
||
|
return FALSE;
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
//
|
||
|
// attempt to allocate queue
|
||
|
//
|
||
|
|
||
|
QueueObjects = new T*[n];
|
||
|
|
||
|
if (NULL == QueueObjects)
|
||
|
{
|
||
|
|
||
|
//
|
||
|
// cleanup and exit
|
||
|
//
|
||
|
|
||
|
CloseHandle(hSemPut);
|
||
|
hSemPut = NULL;
|
||
|
|
||
|
|
||
|
CloseHandle(hSemGet);
|
||
|
hSemGet = NULL;
|
||
|
|
||
|
|
||
|
DeleteCriticalSection(&CritSect);
|
||
|
|
||
|
LOG((MSP_ERROR, "CNBQueue::InitializeQ - failed to allocate queue objects"));
|
||
|
|
||
|
return FALSE;
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
nMax = n;
|
||
|
|
||
|
LOG((MSP_TRACE, "CNBQueue::InitializeQ - exit"));
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
void ShutdownQ()
|
||
|
{
|
||
|
//
|
||
|
// QueueObjects also doubles as "Object Initialized" flag
|
||
|
//
|
||
|
// if object is initialized, _all_ its resource data members must
|
||
|
// be released
|
||
|
//
|
||
|
|
||
|
if (NULL != QueueObjects)
|
||
|
{
|
||
|
delete [] QueueObjects;
|
||
|
QueueObjects = NULL;
|
||
|
|
||
|
DeleteCriticalSection(&CritSect);
|
||
|
|
||
|
CloseHandle(hSemPut);
|
||
|
hSemPut = NULL;
|
||
|
|
||
|
CloseHandle(hSemGet);
|
||
|
hSemGet = NULL;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
public:
|
||
|
|
||
|
CNBQueue()
|
||
|
: QueueObjects(NULL),
|
||
|
hSemPut(NULL),
|
||
|
hSemGet(NULL),
|
||
|
iNextPut(0),
|
||
|
iNextGet(0),
|
||
|
nMax(0)
|
||
|
{}
|
||
|
|
||
|
~CNBQueue()
|
||
|
{
|
||
|
|
||
|
//
|
||
|
// deallocate resources if needed
|
||
|
//
|
||
|
|
||
|
ShutdownQ();
|
||
|
}
|
||
|
|
||
|
|
||
|
T *DeQueue(BOOL fBlock = TRUE)
|
||
|
{
|
||
|
|
||
|
|
||
|
if (NULL == QueueObjects)
|
||
|
{
|
||
|
|
||
|
//
|
||
|
// the queue is not initialized
|
||
|
//
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
//
|
||
|
// block as needed
|
||
|
//
|
||
|
|
||
|
if (fBlock)
|
||
|
{
|
||
|
DWORD dwr = WaitForSingleObject(hSemGet, INFINITE);
|
||
|
|
||
|
if ( WAIT_OBJECT_0 != dwr)
|
||
|
{
|
||
|
//
|
||
|
// something's wrong
|
||
|
//
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//
|
||
|
// Check for something on the queue but don't wait. If there
|
||
|
// is nothing in the queue then we'll let the caller deal with
|
||
|
// it.
|
||
|
//
|
||
|
DWORD dwr = WaitForSingleObject(hSemGet, 0);
|
||
|
|
||
|
if (dwr == WAIT_TIMEOUT)
|
||
|
{
|
||
|
return NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//
|
||
|
// get an object from the queue
|
||
|
//
|
||
|
|
||
|
EnterCriticalSection(&CritSect);
|
||
|
|
||
|
int iSlot = iNextGet++ % nMax;
|
||
|
T *pObject = QueueObjects[iSlot];
|
||
|
|
||
|
LeaveCriticalSection(&CritSect);
|
||
|
|
||
|
// Release anyone waiting to put an object onto our queue as there
|
||
|
// is now space available in the queue.
|
||
|
//
|
||
|
|
||
|
ReleaseSemaphore(hSemPut, 1L, NULL);
|
||
|
return pObject;
|
||
|
}
|
||
|
|
||
|
BOOL EnQueue(T *pObject)
|
||
|
{
|
||
|
|
||
|
|
||
|
if (NULL == QueueObjects)
|
||
|
{
|
||
|
|
||
|
//
|
||
|
// the queue is not initialized
|
||
|
//
|
||
|
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
|
||
|
// Wait for someone to get something from our queue, returns straight
|
||
|
// away is there is already an empty slot on the queue.
|
||
|
//
|
||
|
DWORD dwr = WaitForSingleObject(hSemPut, INFINITE);
|
||
|
|
||
|
if ( WAIT_OBJECT_0 != dwr)
|
||
|
{
|
||
|
//
|
||
|
// something's wrong
|
||
|
//
|
||
|
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
|
||
|
EnterCriticalSection(&CritSect);
|
||
|
int iSlot = iNextPut++ % nMax;
|
||
|
QueueObjects[iSlot] = pObject;
|
||
|
LeaveCriticalSection(&CritSect);
|
||
|
|
||
|
// Release anyone waiting to remove an object from our queue as there
|
||
|
// is now an object available to be removed.
|
||
|
//
|
||
|
ReleaseSemaphore(hSemGet, 1L, NULL);
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// define class CTMStreamSample - this is used by CMediaTerminalFilter
|
||
|
// currently, the actual buffer used by the sample is created dynamically on
|
||
|
// the heap and when the sample is destroyed the buffer is also destroyed
|
||
|
// this may be changed to using a fixed size buffer pool in future
|
||
|
//
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
///////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
class CTMStreamSample : public CSample
|
||
|
{
|
||
|
friend class CMediaTerminalFilter;
|
||
|
public:
|
||
|
|
||
|
inline CTMStreamSample();
|
||
|
|
||
|
// needs to be virtual, or the derived classes' destructor may not
|
||
|
// be called when a CTMStreamSample * is deleted
|
||
|
virtual ~CTMStreamSample()
|
||
|
{}
|
||
|
|
||
|
// calls CSample::InitSample(pStream, bIsInternalSample)
|
||
|
// sets member variables
|
||
|
HRESULT Init(
|
||
|
CStream &Stream,
|
||
|
bool bIsInternalSample,
|
||
|
PBYTE pBuffer,
|
||
|
LONG BufferSize
|
||
|
);
|
||
|
|
||
|
inline void SetBufferInfo(
|
||
|
DWORD BufferSize,
|
||
|
BYTE *pBuffer,
|
||
|
DWORD DataSize
|
||
|
);
|
||
|
|
||
|
|
||
|
inline void GetBufferInfo(
|
||
|
DWORD &BufferSize,
|
||
|
BYTE *&pBuffer,
|
||
|
DWORD &DataSize
|
||
|
);
|
||
|
|
||
|
// copy the contents of the src media sample into this instance
|
||
|
// CSample::CopyFrom doesn't set time (start/stop) valid flags
|
||
|
// this fixes the problem.
|
||
|
void CopyFrom(
|
||
|
IN IMediaSample *pSrcMediaSample
|
||
|
);
|
||
|
|
||
|
protected:
|
||
|
|
||
|
PBYTE m_pBuffer;
|
||
|
LONG m_BufferSize;
|
||
|
LONG m_DataSize;
|
||
|
|
||
|
private:
|
||
|
|
||
|
// Methods forwarded from MediaSample object.
|
||
|
|
||
|
HRESULT MSCallback_GetPointer(BYTE ** ppBuffer) { *ppBuffer = m_pBuffer; return NOERROR; }
|
||
|
|
||
|
LONG MSCallback_GetSize(void) { return m_BufferSize; }
|
||
|
|
||
|
LONG MSCallback_GetActualDataLength(void) { return m_DataSize; }
|
||
|
|
||
|
HRESULT MSCallback_SetActualDataLength(LONG lActual)
|
||
|
{
|
||
|
if (lActual <= m_BufferSize) {
|
||
|
m_DataSize = lActual;
|
||
|
return NOERROR;
|
||
|
}
|
||
|
return E_INVALIDARG;
|
||
|
};
|
||
|
};
|
||
|
|
||
|
inline
|
||
|
CTMStreamSample::CTMStreamSample(
|
||
|
)
|
||
|
: m_pBuffer(NULL),
|
||
|
m_BufferSize(0),
|
||
|
m_DataSize(0)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
|
||
|
inline void
|
||
|
CTMStreamSample::SetBufferInfo(
|
||
|
DWORD BufferSize,
|
||
|
BYTE *pBuffer,
|
||
|
DWORD DataSize
|
||
|
)
|
||
|
{
|
||
|
m_BufferSize = BufferSize;
|
||
|
m_pBuffer = pBuffer;
|
||
|
m_DataSize = DataSize;
|
||
|
}
|
||
|
|
||
|
|
||
|
inline void
|
||
|
CTMStreamSample::GetBufferInfo(
|
||
|
DWORD &BufferSize,
|
||
|
BYTE *&pBuffer,
|
||
|
DWORD &DataSize
|
||
|
)
|
||
|
{
|
||
|
BufferSize = m_BufferSize;
|
||
|
pBuffer = m_pBuffer;
|
||
|
DataSize = m_DataSize;
|
||
|
}
|
||
|
|
||
|
|
||
|
class CQueueMediaSample : public CTMStreamSample
|
||
|
{
|
||
|
public:
|
||
|
|
||
|
inline CQueueMediaSample();
|
||
|
|
||
|
#if DBG
|
||
|
virtual ~CQueueMediaSample();
|
||
|
#endif // DBG
|
||
|
|
||
|
// calls CTMStreamSample::Init, sets members
|
||
|
HRESULT Init(
|
||
|
IN CStream &pStream,
|
||
|
IN CNBQueue<CQueueMediaSample> &pQueue
|
||
|
);
|
||
|
|
||
|
void HoldFragment(
|
||
|
IN DWORD FragSize,
|
||
|
IN BYTE *pbData,
|
||
|
IN IMediaSample &FragMediaSample
|
||
|
);
|
||
|
|
||
|
inline DWORD GetDataSize() { return m_DataSize; }
|
||
|
|
||
|
protected:
|
||
|
|
||
|
// pointer to a queue that contains us!
|
||
|
CNBQueue<CQueueMediaSample> *m_pSampleQueue;
|
||
|
|
||
|
// ptr to the sample being fragmented
|
||
|
CComPtr<IMediaSample> m_pFragMediaSample;
|
||
|
|
||
|
// Overridden to provide different behavior
|
||
|
void FinalMediaSampleRelease();
|
||
|
|
||
|
};
|
||
|
|
||
|
|
||
|
inline
|
||
|
CQueueMediaSample::CQueueMediaSample(
|
||
|
)
|
||
|
: m_pSampleQueue(NULL)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
|
||
|
class CUserMediaSample :
|
||
|
protected CTMStreamSample,
|
||
|
public IMemoryData,
|
||
|
public ITAMMediaFormat
|
||
|
{
|
||
|
public:
|
||
|
|
||
|
BEGIN_COM_MAP(CUserMediaSample)
|
||
|
COM_INTERFACE_ENTRY2(IUnknown, IStreamSample)
|
||
|
COM_INTERFACE_ENTRY(IStreamSample)
|
||
|
COM_INTERFACE_ENTRY(IMemoryData)
|
||
|
COM_INTERFACE_ENTRY(ITAMMediaFormat)
|
||
|
COM_INTERFACE_ENTRY_AGGREGATE(IID_IMarshal, m_pFTM)
|
||
|
END_COM_MAP()
|
||
|
|
||
|
inline CUserMediaSample();
|
||
|
|
||
|
virtual ~CUserMediaSample();
|
||
|
|
||
|
// if asked to allocate buffers, verify allocator properties
|
||
|
static BOOL VerifyAllocatorProperties(
|
||
|
IN BOOL bAllocateBuffers,
|
||
|
IN const ALLOCATOR_PROPERTIES &AllocProps
|
||
|
);
|
||
|
|
||
|
// calls CTMStreamSample::Init, sets members
|
||
|
HRESULT Init(
|
||
|
IN CStream &Stream,
|
||
|
IN BOOL bAllocateBuffer,
|
||
|
IN DWORD ReqdBufferSize,
|
||
|
IN const ALLOCATOR_PROPERTIES &AllocProps
|
||
|
);
|
||
|
|
||
|
void BeginFragment(
|
||
|
IN BOOL bNoteCurrentTime
|
||
|
);
|
||
|
|
||
|
// assign fragment to CQueueMediaSample
|
||
|
void Fragment(
|
||
|
IN BOOL bFragment,
|
||
|
IN LONG AllocBufferSize,
|
||
|
IN OUT CQueueMediaSample &QueueMediaSample,
|
||
|
OUT BOOL &bDone
|
||
|
);
|
||
|
|
||
|
// copy fragment to downstream allocator's IMediaSample
|
||
|
HRESULT CopyFragment(
|
||
|
IN BOOL bFragment,
|
||
|
IN LONG AllocBufferSize,
|
||
|
IN OUT IMediaSample * pDestMediaSample,
|
||
|
OUT BOOL & bDone
|
||
|
);
|
||
|
|
||
|
// computes the time to wait. it checks the time at which the last
|
||
|
// fragmented byte would be due and determines the time to wait using
|
||
|
// the time delay since the beginning of fragmentation
|
||
|
DWORD GetTimeToWait(
|
||
|
IN DOUBLE DelayPerByte
|
||
|
);
|
||
|
|
||
|
// 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 AbortDuringFragmentation();
|
||
|
|
||
|
// copy the contents of the src media sample into this instance
|
||
|
HRESULT CopyFrom(
|
||
|
IN IMediaSample *pSrcMediaSample
|
||
|
);
|
||
|
|
||
|
HRESULT CopyFrom(
|
||
|
IN IMediaSample *pSrcMediaSample,
|
||
|
IN OUT BYTE *&pBuffer,
|
||
|
IN OUT LONG &DataLength
|
||
|
);
|
||
|
|
||
|
// over-ridden to check if the instance is committed before
|
||
|
// adding the sample to the CStream buffer pool
|
||
|
virtual HRESULT SetCompletionStatus(HRESULT hrCompletionStatus);
|
||
|
|
||
|
// IStreamSample
|
||
|
|
||
|
// this method is over-ridden from the base class so that we can
|
||
|
// decrement the refcnt on a sample if stealing it from the CStream
|
||
|
// free buffer pool is successful
|
||
|
STDMETHODIMP CompletionStatus(
|
||
|
IN DWORD dwFlags,
|
||
|
IN /* [optional] */ DWORD dwMilliseconds
|
||
|
);
|
||
|
|
||
|
// IMemoryData
|
||
|
|
||
|
STDMETHOD(SetBuffer)(
|
||
|
IN DWORD cbSize,
|
||
|
IN BYTE * pbData,
|
||
|
IN DWORD dwFlags
|
||
|
);
|
||
|
|
||
|
|
||
|
STDMETHOD(GetInfo)(
|
||
|
OUT DWORD *pdwLength,
|
||
|
OUT BYTE **ppbData,
|
||
|
OUT DWORD *pcbActualData
|
||
|
);
|
||
|
|
||
|
|
||
|
STDMETHOD(SetActual)(
|
||
|
IN DWORD cbDataValid
|
||
|
);
|
||
|
|
||
|
// ITAMMediaFormat
|
||
|
|
||
|
// redirect this call to ((CMediaTerminalFilter *)m_pStream)
|
||
|
STDMETHOD(get_MediaFormat)(
|
||
|
OUT /* [optional] */ AM_MEDIA_TYPE **ppFormat
|
||
|
);
|
||
|
|
||
|
// this is not allowed
|
||
|
STDMETHOD(put_MediaFormat)(
|
||
|
IN const AM_MEDIA_TYPE *pFormat
|
||
|
);
|
||
|
|
||
|
protected:
|
||
|
|
||
|
// marshaller
|
||
|
IUnknown *m_pFTM;
|
||
|
|
||
|
// TRUE if we allocated the buffer (then, we need to destroy it too)
|
||
|
BOOL m_bWeAllocatedBuffer;
|
||
|
|
||
|
// time at which BeginFragment was called (value returned
|
||
|
// by timeGetTime)
|
||
|
DWORD m_BeginFragmentTime;
|
||
|
|
||
|
// these many bytes of the buffer have already been fragmented
|
||
|
LONG m_NumBytesFragmented;
|
||
|
|
||
|
// TRUE if being fragmented
|
||
|
BOOL m_bBeingFragmented;
|
||
|
|
||
|
|
||
|
// size of the buffer that the application will have to provide, if app
|
||
|
// does its own memory allocation
|
||
|
|
||
|
DWORD m_dwRequiredBufferSize;
|
||
|
|
||
|
|
||
|
// this calls the base class FinalMediaSampleRelease and
|
||
|
// then releases reference to self obtained in BeginFragment
|
||
|
virtual void FinalMediaSampleRelease();
|
||
|
|
||
|
private:
|
||
|
|
||
|
virtual HRESULT InternalUpdate(
|
||
|
DWORD dwFlags,
|
||
|
HANDLE hEvent,
|
||
|
PAPCFUNC pfnAPC,
|
||
|
DWORD_PTR dwptrAPCData
|
||
|
);
|
||
|
};
|
||
|
|
||
|
|
||
|
inline
|
||
|
CUserMediaSample::CUserMediaSample(
|
||
|
)
|
||
|
: m_bWeAllocatedBuffer(FALSE),
|
||
|
m_NumBytesFragmented(0),
|
||
|
m_bBeingFragmented(FALSE),
|
||
|
m_BeginFragmentTime(0),
|
||
|
m_dwRequiredBufferSize(0)
|
||
|
{
|
||
|
// can fail
|
||
|
CoCreateFreeThreadedMarshaler(
|
||
|
GetControllingUnknown(),
|
||
|
&m_pFTM
|
||
|
);
|
||
|
}
|
||
|
|
||
|
|
||
|
inline
|
||
|
CUserMediaSample::~CUserMediaSample(
|
||
|
)
|
||
|
{
|
||
|
if (m_bWeAllocatedBuffer)
|
||
|
{
|
||
|
if (NULL != m_pBuffer)
|
||
|
{
|
||
|
delete m_pBuffer;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// if there is an outstanding APC call and the user handle
|
||
|
// (the targe thread handle) has not been closed, close it
|
||
|
if ((NULL != m_UserAPC) && (NULL != m_hUserHandle))
|
||
|
{
|
||
|
CloseHandle(m_hUserHandle);
|
||
|
}
|
||
|
|
||
|
if (NULL != m_pFTM)
|
||
|
{
|
||
|
m_pFTM->Release();
|
||
|
m_pFTM = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/* The media stream terminal filter */
|
||
|
|
||
|
// uses class CMediaPumpPool
|
||
|
class CMediaPumpPool;
|
||
|
|
||
|
// friend
|
||
|
class CMediaTerminal;
|
||
|
|
||
|
class CMediaTerminalFilter :
|
||
|
public CStream,
|
||
|
public ITAllocatorProperties
|
||
|
{
|
||
|
friend CMediaTerminal;
|
||
|
|
||
|
public:
|
||
|
|
||
|
DECLARE_AGGREGATABLE(CMediaTerminalFilter)
|
||
|
DECLARE_GET_CONTROLLING_UNKNOWN()
|
||
|
|
||
|
BEGIN_COM_MAP(CMediaTerminalFilter)
|
||
|
COM_INTERFACE_ENTRY(ITAllocatorProperties)
|
||
|
COM_INTERFACE_ENTRY_CHAIN(CStream)
|
||
|
END_COM_MAP()
|
||
|
|
||
|
// set the member variables
|
||
|
inline CMediaTerminalFilter();
|
||
|
|
||
|
virtual ~CMediaTerminalFilter();
|
||
|
|
||
|
// calls the IAMMediaStream::Initialize(NULL, 0, PurposeId, StreamType),
|
||
|
// sets certain member variables
|
||
|
// ex. m_pAmovieMajorType
|
||
|
virtual HRESULT Init(
|
||
|
IN REFMSPID PurposeId,
|
||
|
IN const STREAM_TYPE StreamType,
|
||
|
IN const GUID &AmovieMajorType
|
||
|
);
|
||
|
|
||
|
// 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 SignalRegisteredAtPump();
|
||
|
|
||
|
// this method only makes sense for a write terminal and is used by CMediaPump
|
||
|
// to obtain a filled buffer for passing downstream
|
||
|
virtual HRESULT GetFilledBuffer(
|
||
|
OUT IMediaSample *&pMediaSample,
|
||
|
OUT DWORD &WaitTime
|
||
|
);
|
||
|
|
||
|
// the caller is supposed to call DeleteMediaType(*ppmt) (on success)
|
||
|
HRESULT GetFormat(
|
||
|
OUT AM_MEDIA_TYPE **ppmt
|
||
|
);
|
||
|
|
||
|
// This method can only be called after initialization when the stream
|
||
|
// is not connected. It can only be called if the stream is writeable.
|
||
|
// it is used in writeable filters to set the media format to negotiate
|
||
|
// when connected to the filter graph.
|
||
|
HRESULT SetFormat(
|
||
|
IN AM_MEDIA_TYPE *pmt
|
||
|
);
|
||
|
|
||
|
// checks if the filter is committed before adding the sample
|
||
|
// to the CStream buffer pool
|
||
|
HRESULT AddToPoolIfCommitted(
|
||
|
IN CSample *pSample
|
||
|
);
|
||
|
|
||
|
// first check if this sample is the one being fragmented currently,
|
||
|
// then check the free pool
|
||
|
BOOL StealSample(
|
||
|
IN CSample *pSample
|
||
|
);
|
||
|
|
||
|
// ITAllocatorProperties -
|
||
|
// exposes the allocator properties of the Media Streaming Terminal
|
||
|
// (MST) to a user. A user only needs to use this interface when he
|
||
|
// needs to use his own buffers or needs to operate with a fixed set
|
||
|
// of samples
|
||
|
|
||
|
// this method may only be called before connection and will
|
||
|
// force the MST to use these values during filter negotiation
|
||
|
// if the connecting filter doesn't accept these, the connection
|
||
|
// shall not be established
|
||
|
STDMETHOD(SetAllocatorProperties)(
|
||
|
IN ALLOCATOR_PROPERTIES *pAllocProperties
|
||
|
);
|
||
|
|
||
|
// 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
|
||
|
STDMETHOD(GetAllocatorProperties)(
|
||
|
OUT ALLOCATOR_PROPERTIES *pAllocProperties
|
||
|
);
|
||
|
|
||
|
// 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
|
||
|
STDMETHOD(SetAllocateBuffers)(
|
||
|
IN BOOL bAllocBuffers
|
||
|
);
|
||
|
|
||
|
// returns the current value of this boolean configuration parameter
|
||
|
STDMETHOD(GetAllocateBuffers)(
|
||
|
OUT BOOL *pbAllocBuffers
|
||
|
);
|
||
|
|
||
|
// this size is used for allocating buffers when AllocateSample is
|
||
|
// called. this is only valid when we have been told to allocate buffers
|
||
|
STDMETHOD(SetBufferSize)(
|
||
|
IN DWORD BufferSize
|
||
|
);
|
||
|
|
||
|
// returns the value used to allocate buffers when AllocateSample is
|
||
|
// called. this is only valid when we have been told to allocate buffers
|
||
|
STDMETHOD(GetBufferSize)(
|
||
|
OUT DWORD *pBufferSize
|
||
|
);
|
||
|
|
||
|
// over-ridden base class methods
|
||
|
|
||
|
// CStream
|
||
|
// IAMMediaStream
|
||
|
|
||
|
// 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
|
||
|
STDMETHOD(JoinAMMultiMediaStream)(
|
||
|
IN IAMMultiMediaStream *pAMMultiMediaStream
|
||
|
);
|
||
|
|
||
|
// over-ride this to return failure if the filter is anything other than the internally
|
||
|
// created filter. The internally created media stream filter has only one IAMMediaStream
|
||
|
// (this one) in it
|
||
|
STDMETHOD(JoinFilter)(
|
||
|
IN IMediaStreamFilter *pMediaStreamFilter
|
||
|
);
|
||
|
|
||
|
STDMETHOD(AllocateSample)(
|
||
|
IN DWORD dwFlags,
|
||
|
OUT IStreamSample **ppSample
|
||
|
);
|
||
|
|
||
|
STDMETHOD(CreateSharedSample)(
|
||
|
IN IStreamSample *pExistingSample,
|
||
|
IN DWORD dwFlags,
|
||
|
OUT IStreamSample **ppNewSample
|
||
|
);
|
||
|
|
||
|
STDMETHOD(SetSameFormat)(
|
||
|
IN IMediaStream *pStream,
|
||
|
IN DWORD dwFlags
|
||
|
);
|
||
|
|
||
|
// CStream over-ride - this method had to be replaced because
|
||
|
// of references to CPump which itself is being replaced by CMediaPump
|
||
|
STDMETHODIMP SetState(
|
||
|
IN FILTER_STATE State
|
||
|
);
|
||
|
|
||
|
// CStream - end
|
||
|
|
||
|
// IMemInputPin
|
||
|
|
||
|
STDMETHOD(GetAllocatorRequirements)(
|
||
|
IN ALLOCATOR_PROPERTIES*pProps
|
||
|
);
|
||
|
|
||
|
STDMETHOD(Receive)(
|
||
|
IN IMediaSample *pSample
|
||
|
);
|
||
|
|
||
|
|
||
|
|
||
|
// supports IAMBufferNegotiation interface on TERMINAL
|
||
|
// this is necessary because ITAllocatorProperties also
|
||
|
// has an identical GetAllocatorProperties method!
|
||
|
|
||
|
STDMETHOD(SuggestAllocatorProperties)(
|
||
|
IN const ALLOCATOR_PROPERTIES *pProperties
|
||
|
);
|
||
|
|
||
|
// IMemAllocator
|
||
|
|
||
|
STDMETHOD(GetBuffer)(IMediaSample **ppBuffer, REFERENCE_TIME * pStartTime,
|
||
|
REFERENCE_TIME * pEndTime, DWORD dwFlags);
|
||
|
|
||
|
// ** figure out what needs to be done for this allocator interface
|
||
|
// since the number of buffers that can be created is unbounded
|
||
|
STDMETHOD(SetProperties)(ALLOCATOR_PROPERTIES* pRequest, ALLOCATOR_PROPERTIES* pActual);
|
||
|
|
||
|
STDMETHOD(GetProperties)(ALLOCATOR_PROPERTIES* pProps);
|
||
|
|
||
|
STDMETHOD(Commit)();
|
||
|
|
||
|
STDMETHOD(Decommit)();
|
||
|
|
||
|
// IPin
|
||
|
|
||
|
STDMETHOD(Connect)(IPin * pReceivePin, const AM_MEDIA_TYPE *pmt);
|
||
|
|
||
|
STDMETHOD(ReceiveConnection)(IPin * pConnector, const AM_MEDIA_TYPE *pmt);
|
||
|
|
||
|
// the base class implementation doesn't validate the parameter
|
||
|
STDMETHOD(ConnectionMediaType)(AM_MEDIA_TYPE *pmt);
|
||
|
|
||
|
// should accept all media types which match the major type corresponding to the purpose id
|
||
|
STDMETHOD(QueryAccept)(const AM_MEDIA_TYPE *pmt);
|
||
|
|
||
|
// over-ridden from CStream to set the end of stream flag to false
|
||
|
// this is done instead of setting it in Connect and ReceiveConnection
|
||
|
STDMETHODIMP Disconnect();
|
||
|
|
||
|
|
||
|
|
||
|
//
|
||
|
// this is called by media pump when it has a sample for us to process
|
||
|
//
|
||
|
|
||
|
STDMETHODIMP ProcessSample(IMediaSample *pSample);
|
||
|
|
||
|
protected:
|
||
|
|
||
|
// last sample ended at this (calculated) time
|
||
|
|
||
|
REFERENCE_TIME m_rtLastSampleEndedAt;
|
||
|
|
||
|
//
|
||
|
// calculated duration of the sample that was last submitted
|
||
|
//
|
||
|
|
||
|
REFERENCE_TIME m_rtLastSampleDuration;
|
||
|
|
||
|
|
||
|
//
|
||
|
// real (measured) time of submission of the last sample
|
||
|
//
|
||
|
|
||
|
REFERENCE_TIME m_rtRealTimeOfLastSample;
|
||
|
|
||
|
|
||
|
// flag to check if this is an audio filter, the CStream member
|
||
|
// m_PurposeId is a guiid and this just provides a less expensive
|
||
|
// way of checking the same thing
|
||
|
BOOL m_bIsAudio;
|
||
|
|
||
|
// contains the samples that will be passed to downstream filters.
|
||
|
CNBQueue<CQueueMediaSample> m_SampleQueue;
|
||
|
|
||
|
// These datamembers provide some fragmentation support
|
||
|
// for buffers going downstream
|
||
|
CUserMediaSample *m_pSampleBeingFragmented;
|
||
|
|
||
|
// flag for allocating buffers for samples when AllocateSample is
|
||
|
// called. Its TRUE by default, but the user can set it before
|
||
|
// connection
|
||
|
BOOL m_bAllocateBuffers;
|
||
|
|
||
|
// size of buffers to allocate in AllocateSample if m_bAllocateBuffers
|
||
|
// is TRUE. if this isn't set (i.e. set to 0), the negotiated
|
||
|
// allocator properties buffer size is used in its place
|
||
|
DWORD m_AllocateSampleBufferSize;
|
||
|
|
||
|
// FALSE by default. This is set to TRUE if the user specifies
|
||
|
// allocator properties for them to see.
|
||
|
// (we used to insist on our own allocator properties when this
|
||
|
// was TRUE, but now this just means that we need to translate
|
||
|
// between disjoint buffer sizes if needed)
|
||
|
BOOL m_bUserAllocProps;
|
||
|
ALLOCATOR_PROPERTIES m_UserAllocProps;
|
||
|
|
||
|
// allocator properties negotiated -- if none suggested (by msp) and
|
||
|
// none requested by user, we use whatever the other filter has
|
||
|
BOOL m_bSuggestedAllocProps;
|
||
|
ALLOCATOR_PROPERTIES m_AllocProps;
|
||
|
|
||
|
// per byte delay for audio samples - only valid for write filter
|
||
|
DOUBLE m_AudioDelayPerByte;
|
||
|
|
||
|
// per frame delay for video samples - only valid for write filter
|
||
|
DWORD m_VideoDelayPerFrame;
|
||
|
|
||
|
// the filter restricts the acceptable media types to those that match the major type
|
||
|
// this corresponds to the purpose id of the IAMMediaStream (set in Init)
|
||
|
const GUID *m_pAmovieMajorType;
|
||
|
|
||
|
// this is the media type suggested by a user of the terminal
|
||
|
// it is only valid for writeable streams if put_MediaType was called
|
||
|
// (i.e. not valid for readable streams)
|
||
|
// this needs to be freed in the destructor
|
||
|
// cstream - cbaseterm gets\sets it through methods
|
||
|
AM_MEDIA_TYPE *m_pSuggestedMediaType;
|
||
|
|
||
|
// this pump replaces the CStream related implementation of CPump
|
||
|
// CPump uses a separate thread for each write terminal,
|
||
|
// it uses IMemAllocator::GetBuffer to get a user written media
|
||
|
// sample (for passing on downstream). This method should only be
|
||
|
// used to get the next free buffer to write into.
|
||
|
|
||
|
// CStream methods
|
||
|
|
||
|
// this is only used during Connect and ReceiveConnect to supply the optional media type
|
||
|
// since we over-ride Connect and ReceiveConnection methods, this should never get called
|
||
|
virtual HRESULT GetMediaType(ULONG Index, AM_MEDIA_TYPE **ppMediaType);
|
||
|
|
||
|
// others
|
||
|
|
||
|
// sets the time to delay - per byte for audio, per frame for video
|
||
|
void GetTimingInfo(
|
||
|
IN const AM_MEDIA_TYPE &MediaType
|
||
|
);
|
||
|
|
||
|
// timestamps the sample
|
||
|
HRESULT SetTime(
|
||
|
IN IMediaSample *pMediaSample
|
||
|
);
|
||
|
|
||
|
|
||
|
// set discontinuity flag on the sample it the sample came too late -- we
|
||
|
// assume that if the application stopped feeding mst with data, this is
|
||
|
// because there was a gap in the actual data flow
|
||
|
|
||
|
HRESULT SetDiscontinuityIfNeeded(
|
||
|
IN IMediaSample *pMediaSample
|
||
|
);
|
||
|
|
||
|
// set the default allocator properties
|
||
|
void SetDefaultAllocatorProperties();
|
||
|
|
||
|
//
|
||
|
// Helper methods for GetFilledBuffer.
|
||
|
//
|
||
|
|
||
|
virtual HRESULT FillDownstreamAllocatorBuffer(
|
||
|
OUT IMediaSample *& pMediaSample,
|
||
|
OUT DWORD & WaitTime,
|
||
|
OUT BOOL * pfDone
|
||
|
);
|
||
|
|
||
|
virtual HRESULT FillMyBuffer(
|
||
|
OUT IMediaSample *& pMediaSample,
|
||
|
OUT DWORD & WaitTime,
|
||
|
OUT BOOL * pfDone
|
||
|
);
|
||
|
|
||
|
|
||
|
private :
|
||
|
|
||
|
// this is a weak reference and should not be a CComPtr
|
||
|
// this tells us that we should only accept this media stream filter
|
||
|
// when a non-null value is proposed in JoinFilter
|
||
|
IMediaStreamFilter *m_pMediaStreamFilterToAccept;
|
||
|
|
||
|
// sets the media stream filter that may be acceptable
|
||
|
inline void SetMediaStreamFilter(
|
||
|
IN IMediaStreamFilter *pMediaStreamFilter
|
||
|
)
|
||
|
{
|
||
|
m_pMediaStreamFilterToAccept = pMediaStreamFilter;
|
||
|
}
|
||
|
|
||
|
public:
|
||
|
// implements single thread pump for all write terminal filters
|
||
|
// it uses GetFilledBuffer to obtain filled samples to write downstream
|
||
|
// and to detect when to remove this filter from its list of filters to
|
||
|
// service
|
||
|
// ZoltanS: must be public so we can access it in DllMain
|
||
|
// ZoltanS: no longer single thread pump; it is a wrapper which delegated
|
||
|
// to one or more single thread pumps
|
||
|
|
||
|
static CMediaPumpPool ms_MediaPumpPool;
|
||
|
|
||
|
};
|
||
|
|
||
|
|
||
|
// set the member variables
|
||
|
inline
|
||
|
CMediaTerminalFilter::CMediaTerminalFilter(
|
||
|
)
|
||
|
: m_bIsAudio(TRUE),
|
||
|
m_bAllocateBuffers(TRUE),
|
||
|
m_AllocateSampleBufferSize(0),
|
||
|
m_bUserAllocProps(FALSE),
|
||
|
m_bSuggestedAllocProps(FALSE),
|
||
|
m_AudioDelayPerByte(0),
|
||
|
m_VideoDelayPerFrame(0),
|
||
|
m_pAmovieMajorType(NULL),
|
||
|
m_pSuggestedMediaType(NULL),
|
||
|
m_pSampleBeingFragmented(NULL),
|
||
|
m_pMediaStreamFilterToAccept(NULL),
|
||
|
m_rtLastSampleEndedAt(0),
|
||
|
m_rtLastSampleDuration(0),
|
||
|
m_rtRealTimeOfLastSample(0)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
|
||
|
#endif // __MEDIA_TERMINAL_FILTER__
|