|
|
//------------------------------------------------------------------------------
// File: OutputQ.cpp
//
// Desc: DirectShow base classes - implements COutputQueue class used by an
// output pin which may sometimes want to queue output samples on a
// separate thread and sometimes call Receive() directly on the input
// pin.
//
// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved.
//------------------------------------------------------------------------------
#include <streams.h>
//
// COutputQueue Constructor :
//
// Determines if a thread is to be created and creates resources
//
// pInputPin - the downstream input pin we're queueing samples to
//
// phr - changed to a failure code if this function fails
// (otherwise unchanges)
//
// bAuto - Ask pInputPin if it can block in Receive by calling
// its ReceiveCanBlock method and create a thread if
// it can block, otherwise not.
//
// bQueue - if bAuto == FALSE then we create a thread if and only
// if bQueue == TRUE
//
// lBatchSize - work in batches of lBatchSize
//
// bBatchEact - Use exact batch sizes so don't send until the
// batch is full or SendAnyway() is called
//
// lListSize - If we create a thread make the list of samples queued
// to the thread have this size cache
//
// dwPriority - If we create a thread set its priority to this
//
COutputQueue::COutputQueue( IPin *pInputPin, // Pin to send stuff to
HRESULT *phr, // 'Return code'
BOOL bAuto, // Ask pin if queue or not
BOOL bQueue, // Send through queue
LONG lBatchSize, // Batch
BOOL bBatchExact, // Batch exactly to BatchSize
LONG lListSize, DWORD dwPriority, bool bFlushingOpt // flushing optimization
) : m_lBatchSize(lBatchSize), m_bBatchExact(bBatchExact && (lBatchSize > 1)), m_hThread(NULL), m_hSem(NULL), m_List(NULL), m_pPin(pInputPin), m_ppSamples(NULL), m_lWaiting(0), m_pInputPin(NULL), m_bSendAnyway(FALSE), m_nBatched(0), m_bFlushing(FALSE), m_bFlushed(TRUE), m_bFlushingOpt(bFlushingOpt), m_bTerminate(FALSE), m_hEventPop(NULL), m_hr(S_OK) { ASSERT(m_lBatchSize > 0);
if (FAILED(*phr)) { return; }
// Check the input pin is OK and cache its IMemInputPin interface
*phr = pInputPin->QueryInterface(IID_IMemInputPin, (void **)&m_pInputPin); if (FAILED(*phr)) { return; }
// See if we should ask the downstream pin
if (bAuto) { HRESULT hr = m_pInputPin->ReceiveCanBlock(); if (SUCCEEDED(hr)) { bQueue = hr == S_OK; } }
// Create our sample batch
m_ppSamples = new PMEDIASAMPLE[m_lBatchSize]; if (m_ppSamples == NULL) { *phr = E_OUTOFMEMORY; return; }
// If we're queueing allocate resources
if (bQueue) { DbgLog((LOG_TRACE, 2, TEXT("Creating thread for output pin"))); m_hSem = CreateSemaphore(NULL, 0, 0x7FFFFFFF, NULL); if (m_hSem == NULL) { DWORD dwError = GetLastError(); *phr = AmHresultFromWin32(dwError); return; } m_List = new CSampleList(NAME("Sample Queue List"), lListSize, FALSE // No lock
); if (m_List == NULL) { *phr = E_OUTOFMEMORY; return; }
DWORD dwThreadId; m_hThread = CreateThread(NULL, 0, InitialThreadProc, (LPVOID)this, 0, &dwThreadId); if (m_hThread == NULL) { DWORD dwError = GetLastError(); *phr = AmHresultFromWin32(dwError); return; } SetThreadPriority(m_hThread, dwPriority); } else { DbgLog((LOG_TRACE, 2, TEXT("Calling input pin directly - no thread"))); } }
//
// COutputQueuee Destructor :
//
// Free all resources -
//
// Thread,
// Batched samples
//
COutputQueue::~COutputQueue() { DbgLog((LOG_TRACE, 3, TEXT("COutputQueue::~COutputQueue"))); /* Free our pointer */ if (m_pInputPin != NULL) { m_pInputPin->Release(); } if (m_hThread != NULL) { { CAutoLock lck(this); m_bTerminate = TRUE; m_hr = S_FALSE; NotifyThread(); } DbgWaitForSingleObject(m_hThread); EXECUTE_ASSERT(CloseHandle(m_hThread));
// The thread frees the samples when asked to terminate
ASSERT(m_List->GetCount() == 0); delete m_List; } else { FreeSamples(); } if (m_hSem != NULL) { EXECUTE_ASSERT(CloseHandle(m_hSem)); } delete [] m_ppSamples; }
//
// Call the real thread proc as a member function
//
DWORD WINAPI COutputQueue::InitialThreadProc(LPVOID pv) { HRESULT hrCoInit = CAMThread::CoInitializeHelper(); COutputQueue *pSampleQueue = (COutputQueue *)pv; DWORD dwReturn = pSampleQueue->ThreadProc();
if(hrCoInit == S_OK) { CoUninitialize(); } return dwReturn; }
//
// Thread sending the samples downstream :
//
// When there is nothing to do the thread sets m_lWaiting (while
// holding the critical section) and then waits for m_hSem to be
// set (not holding the critical section)
//
DWORD COutputQueue::ThreadProc() { while (TRUE) { BOOL bWait = FALSE; IMediaSample *pSample; LONG lNumberToSend; // Local copy
NewSegmentPacket* ppacket;
//
// Get a batch of samples and send it if possible
// In any case exit the loop if there is a control action
// requested
//
{ CAutoLock lck(this); while (TRUE) {
if (m_bTerminate) { FreeSamples(); return 0; } if (m_bFlushing) { FreeSamples(); SetEvent(m_evFlushComplete); }
// Get a sample off the list
pSample = m_List->RemoveHead(); // inform derived class we took something off the queue
if (m_hEventPop) { //DbgLog((LOG_TRACE,3,TEXT("Queue: Delivered SET EVENT")));
SetEvent(m_hEventPop); }
if (pSample != NULL && !IsSpecialSample(pSample)) {
// If its just a regular sample just add it to the batch
// and exit the loop if the batch is full
m_ppSamples[m_nBatched++] = pSample; if (m_nBatched == m_lBatchSize) { break; } } else {
// If there was nothing in the queue and there's nothing
// to send (either because there's nothing or the batch
// isn't full) then prepare to wait
if (pSample == NULL && (m_bBatchExact || m_nBatched == 0)) {
// Tell other thread to set the event when there's
// something do to
ASSERT(m_lWaiting == 0); m_lWaiting++; bWait = TRUE; } else {
// We break out of the loop on SEND_PACKET unless
// there's nothing to send
if (pSample == SEND_PACKET && m_nBatched == 0) { continue; }
if (pSample == NEW_SEGMENT) { // now we need the parameters - we are
// guaranteed that the next packet contains them
ppacket = (NewSegmentPacket *) m_List->RemoveHead(); // we took something off the queue
if (m_hEventPop) { //DbgLog((LOG_TRACE,3,TEXT("Queue: Delivered SET EVENT")));
SetEvent(m_hEventPop); }
ASSERT(ppacket); } // EOS_PACKET falls through here and we exit the loop
// In this way it acts like SEND_PACKET
} break; } } if (!bWait) { // We look at m_nBatched from the client side so keep
// it up to date inside the critical section
lNumberToSend = m_nBatched; // Local copy
m_nBatched = 0; } }
// Wait for some more data
if (bWait) { DbgWaitForSingleObject(m_hSem); continue; }
// OK - send it if there's anything to send
// We DON'T check m_bBatchExact here because either we've got
// a full batch or we dropped through because we got
// SEND_PACKET or EOS_PACKET - both of which imply we should
// flush our batch
if (lNumberToSend != 0) { long nProcessed; if (m_hr == S_OK) { ASSERT(!m_bFlushed); HRESULT hr = m_pInputPin->ReceiveMultiple(m_ppSamples, lNumberToSend, &nProcessed); /* Don't overwrite a flushing state HRESULT */ CAutoLock lck(this); if (m_hr == S_OK) { m_hr = hr; } ASSERT(!m_bFlushed); } while (lNumberToSend != 0) { m_ppSamples[--lNumberToSend]->Release(); } if (m_hr != S_OK) {
// In any case wait for more data - S_OK just
// means there wasn't an error
DbgLog((LOG_ERROR, 2, TEXT("ReceiveMultiple returned %8.8X"), m_hr)); } }
// Check for end of stream
if (pSample == EOS_PACKET) {
// We don't send even end of stream on if we've previously
// returned something other than S_OK
// This is because in that case the pin which returned
// something other than S_OK should have either sent
// EndOfStream() or notified the filter graph
if (m_hr == S_OK) { DbgLog((LOG_TRACE, 2, TEXT("COutputQueue sending EndOfStream()"))); HRESULT hr = m_pPin->EndOfStream(); if (FAILED(hr)) { DbgLog((LOG_ERROR, 2, TEXT("COutputQueue got code 0x%8.8X from EndOfStream()"))); } } }
// Data from a new source
if (pSample == RESET_PACKET) { m_hr = S_OK; SetEvent(m_evFlushComplete); }
if (pSample == NEW_SEGMENT) { m_pPin->NewSegment(ppacket->tStart, ppacket->tStop, ppacket->dRate); delete ppacket; } } }
// Send batched stuff anyway
void COutputQueue::SendAnyway() { if (!IsQueued()) {
// m_bSendAnyway is a private parameter checked in ReceiveMultiple
m_bSendAnyway = TRUE; LONG nProcessed; ReceiveMultiple(NULL, 0, &nProcessed); m_bSendAnyway = FALSE;
} else { CAutoLock lck(this); QueueSample(SEND_PACKET); NotifyThread(); } }
void COutputQueue::NewSegment( REFERENCE_TIME tStart, REFERENCE_TIME tStop, double dRate) { if (!IsQueued()) { if (S_OK == m_hr) { if (m_bBatchExact) { SendAnyway(); } m_pPin->NewSegment(tStart, tStop, dRate); } } else { if (m_hr == S_OK) { //
// we need to queue the new segment to appear in order in the
// data, but we need to pass parameters to it. Rather than
// take the hit of wrapping every single sample so we can tell
// special ones apart, we queue special pointers to indicate
// special packets, and we guarantee (by holding the
// critical section) that the packet immediately following a
// NEW_SEGMENT value is a NewSegmentPacket containing the
// parameters.
NewSegmentPacket * ppack = new NewSegmentPacket; if (ppack == NULL) { return; } ppack->tStart = tStart; ppack->tStop = tStop; ppack->dRate = dRate;
CAutoLock lck(this); QueueSample(NEW_SEGMENT); QueueSample( (IMediaSample*) ppack); NotifyThread(); } } }
//
// End of Stream is queued to output device
//
void COutputQueue::EOS() { CAutoLock lck(this); if (!IsQueued()) { if (m_bBatchExact) { SendAnyway(); } if (m_hr == S_OK) { DbgLog((LOG_TRACE, 2, TEXT("COutputQueue sending EndOfStream()"))); m_bFlushed = FALSE; HRESULT hr = m_pPin->EndOfStream(); if (FAILED(hr)) { DbgLog((LOG_ERROR, 2, TEXT("COutputQueue got code 0x%8.8X from EndOfStream()"))); } } } else { if (m_hr == S_OK) { m_bFlushed = FALSE; QueueSample(EOS_PACKET); NotifyThread(); } } }
//
// Flush all the samples in the queue
//
void COutputQueue::BeginFlush() { if (IsQueued()) { { CAutoLock lck(this);
// block receives -- we assume this is done by the
// filter in which we are a component
// discard all queued data
m_bFlushing = TRUE;
// Make sure we discard all samples from now on
if (m_hr == S_OK) { m_hr = S_FALSE; }
// Optimize so we don't keep calling downstream all the time
if (m_bFlushed && m_bFlushingOpt) { return; }
// Make sure we really wait for the flush to complete
m_evFlushComplete.Reset();
NotifyThread(); }
// pass this downstream
m_pPin->BeginFlush(); } else { // pass downstream first to avoid deadlocks
m_pPin->BeginFlush(); CAutoLock lck(this); // discard all queued data
m_bFlushing = TRUE;
// Make sure we discard all samples from now on
if (m_hr == S_OK) { m_hr = S_FALSE; } }
}
//
// leave flush mode - pass this downstream
void COutputQueue::EndFlush() { { CAutoLock lck(this); ASSERT(m_bFlushing); if (m_bFlushingOpt && m_bFlushed && IsQueued()) { m_bFlushing = FALSE; m_hr = S_OK; return; } }
// sync with pushing thread -- done in BeginFlush
// ensure no more data to go downstream -- done in BeginFlush
//
// Because we are synching here there is no need to hold the critical
// section (in fact we'd deadlock if we did!)
if (IsQueued()) { m_evFlushComplete.Wait(); } else { FreeSamples(); }
// Be daring - the caller has guaranteed no samples will arrive
// before EndFlush() returns
m_bFlushing = FALSE; m_bFlushed = TRUE;
// call EndFlush on downstream pins
m_pPin->EndFlush();
m_hr = S_OK; }
// COutputQueue::QueueSample
//
// private method to Send a sample to the output queue
// The critical section MUST be held when this is called
void COutputQueue::QueueSample(IMediaSample *pSample) { if (NULL == m_List->AddTail(pSample)) { if (!IsSpecialSample(pSample)) { pSample->Release(); } } }
//
// COutputQueue::Receive()
//
// Send a single sample by the multiple sample route
// (NOTE - this could be optimized if necessary)
//
// On return the sample will have been Release()'d
//
HRESULT COutputQueue::Receive(IMediaSample *pSample) { LONG nProcessed; return ReceiveMultiple(&pSample, 1, &nProcessed); }
//
// COutputQueue::ReceiveMultiple()
//
// Send a set of samples to the downstream pin
//
// ppSamples - array of samples
// nSamples - how many
// nSamplesProcessed - How many were processed
//
// On return all samples will have been Release()'d
//
HRESULT COutputQueue::ReceiveMultiple ( IMediaSample **ppSamples, long nSamples, long *nSamplesProcessed) { CAutoLock lck(this); // Either call directly or queue up the samples
if (!IsQueued()) {
// If we already had a bad return code then just return
if (S_OK != m_hr) {
// If we've never received anything since the last Flush()
// and the sticky return code is not S_OK we must be
// flushing
// ((!A || B) is equivalent to A implies B)
ASSERT(!m_bFlushed || m_bFlushing);
// We're supposed to Release() them anyway!
*nSamplesProcessed = 0; for (int i = 0; i < nSamples; i++) { DbgLog((LOG_TRACE, 3, TEXT("COutputQueue (direct) : Discarding %d samples code 0x%8.8X"), nSamples, m_hr)); ppSamples[i]->Release(); }
return m_hr; } //
// If we're flushing the sticky return code should be S_FALSE
//
ASSERT(!m_bFlushing); m_bFlushed = FALSE;
ASSERT(m_nBatched < m_lBatchSize); ASSERT(m_nBatched == 0 || m_bBatchExact);
// Loop processing the samples in batches
LONG iLost = 0; for (long iDone = 0; iDone < nSamples || (m_nBatched != 0 && m_bSendAnyway); ) {
//pragma message (REMIND("Implement threshold scheme"))
ASSERT(m_nBatched < m_lBatchSize); if (iDone < nSamples) { m_ppSamples[m_nBatched++] = ppSamples[iDone++]; } if (m_nBatched == m_lBatchSize || nSamples == 0 && (m_bSendAnyway || !m_bBatchExact)) { LONG nDone; DbgLog((LOG_TRACE, 4, TEXT("Batching %d samples"), m_nBatched));
if (m_hr == S_OK) { m_hr = m_pInputPin->ReceiveMultiple(m_ppSamples, m_nBatched, &nDone); } else { nDone = 0; } iLost += m_nBatched - nDone; for (LONG i = 0; i < m_nBatched; i++) { m_ppSamples[i]->Release(); } m_nBatched = 0; } } *nSamplesProcessed = iDone - iLost; if (*nSamplesProcessed < 0) { *nSamplesProcessed = 0; } return m_hr; } else { /* We're sending to our thread */
if (m_hr != S_OK) { *nSamplesProcessed = 0; DbgLog((LOG_TRACE, 3, TEXT("COutputQueue (queued) : Discarding %d samples code 0x%8.8X"), nSamples, m_hr)); for (int i = 0; i < nSamples; i++) { ppSamples[i]->Release(); } return m_hr; } m_bFlushed = FALSE; for (long i = 0; i < nSamples; i++) { QueueSample(ppSamples[i]); } *nSamplesProcessed = nSamples; if (!m_bBatchExact || m_nBatched + m_List->GetCount() >= m_lBatchSize) { NotifyThread(); } return S_OK; } }
// Get ready for new data - cancels sticky m_hr
void COutputQueue::Reset() { if (!IsQueued()) { m_hr = S_OK; } else { CAutoLock lck(this); QueueSample(RESET_PACKET); NotifyThread(); m_evFlushComplete.Wait(); } }
// Remove and Release() all queued and Batched samples
void COutputQueue::FreeSamples() { CAutoLock lck(this); if (IsQueued()) { while (TRUE) { IMediaSample *pSample = m_List->RemoveHead(); // inform derived class we took something off the queue
if (m_hEventPop) { //DbgLog((LOG_TRACE,3,TEXT("Queue: Delivered SET EVENT")));
SetEvent(m_hEventPop); }
if (pSample == NULL) { break; } if (!IsSpecialSample(pSample)) { pSample->Release(); } else { if (pSample == NEW_SEGMENT) { // Free NEW_SEGMENT packet
NewSegmentPacket *ppacket = (NewSegmentPacket *) m_List->RemoveHead(); // inform derived class we took something off the queue
if (m_hEventPop) { //DbgLog((LOG_TRACE,3,TEXT("Queue: Delivered SET EVENT")));
SetEvent(m_hEventPop); }
ASSERT(ppacket != NULL); delete ppacket; } } } } for (int i = 0; i < m_nBatched; i++) { m_ppSamples[i]->Release(); } m_nBatched = 0; }
// Notify the thread if there is something to do
//
// The critical section MUST be held when this is called
void COutputQueue::NotifyThread() { // Optimize - no need to signal if it's not waiting
ASSERT(IsQueued()); if (m_lWaiting) { ReleaseSemaphore(m_hSem, m_lWaiting, NULL); m_lWaiting = 0; } }
// See if there's any work to do
// Returns
// TRUE if there is nothing on the queue and nothing in the batch
// and all data has been sent
// FALSE otherwise
//
BOOL COutputQueue::IsIdle() { CAutoLock lck(this);
// We're idle if
// there is no thread (!IsQueued()) OR
// the thread is waiting for more work (m_lWaiting != 0)
// AND
// there's nothing in the current batch (m_nBatched == 0)
if (IsQueued() && m_lWaiting == 0 || m_nBatched != 0) { return FALSE; } else {
// If we're idle it shouldn't be possible for there
// to be anything on the work queue
ASSERT(!IsQueued() || m_List->GetCount() == 0); return TRUE; } }
void COutputQueue::SetPopEvent(HANDLE hEvent) { m_hEventPop = hEvent; }
|