2025-11-28 00:35:46 +09:00

489 lines
13 KiB
C++

//////////////////////////////////////////////////////////////////////////
//
// Scheduler.cpp: Schedules when video frames are presented.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//
//////////////////////////////////////////////////////////////////////////
#include "EVRPresenter.h"
// ScheduleEvent
// Messages for the scheduler thread.
enum ScheduleEvent
{
eTerminate = WM_USER,
eSchedule = WM_USER + 1,
eFlush = WM_USER + 2
};
const DWORD SCHEDULER_TIMEOUT = 5000;
//-----------------------------------------------------------------------------
// Constructor
//-----------------------------------------------------------------------------
Scheduler::Scheduler() :
m_pCB(NULL),
m_pClock(NULL),
m_dwThreadID(0),
m_hSchedulerThread(NULL),
m_hThreadReadyEvent(NULL),
m_hFlushEvent(NULL),
m_fRate(1.0f),
m_LastSampleTime(0),
m_PerFrameInterval(0),
m_PerFrame_1_4th(0)
{
}
//-----------------------------------------------------------------------------
// Destructor
//-----------------------------------------------------------------------------
Scheduler::~Scheduler()
{
SAFE_RELEASE(m_pClock);
}
//-----------------------------------------------------------------------------
// SetFrameRate
// Specifies the frame rate of the video, in frames per second.
//-----------------------------------------------------------------------------
void Scheduler::SetFrameRate(const MFRatio& fps)
{
UINT64 AvgTimePerFrame = 0;
// Convert to a duration.
MFFrameRateToAverageTimePerFrame(fps.Numerator, fps.Denominator, &AvgTimePerFrame);
m_PerFrameInterval = (MFTIME)AvgTimePerFrame;
// Calculate 1/4th of this value, because we use it frequently.
m_PerFrame_1_4th = m_PerFrameInterval / 4;
}
//-----------------------------------------------------------------------------
// StartScheduler
// Starts the scheduler's worker thread.
//
// IMFClock: Pointer to the EVR's presentation clock. Can be NULL.
//-----------------------------------------------------------------------------
HRESULT Scheduler::StartScheduler(IMFClock *pClock)
{
if (m_hSchedulerThread != NULL)
{
return E_UNEXPECTED;
}
HRESULT hr = S_OK;
DWORD dwID = 0;
CopyComPointer(m_pClock, pClock);
// Set a high the timer resolution (ie, short timer period).
timeBeginPeriod(1);
// Create an event to wait for the thread to start.
m_hThreadReadyEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
if (m_hThreadReadyEvent == NULL)
{
CHECK_HR(hr = HRESULT_FROM_WIN32(GetLastError()));
}
// Create an event to wait for flush commands to complete.
m_hFlushEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
if (m_hFlushEvent == NULL)
{
CHECK_HR(hr = HRESULT_FROM_WIN32(GetLastError()));
}
// Create the scheduler thread.
m_hSchedulerThread = CreateThread(NULL, 0, SchedulerThreadProc, (LPVOID)this, 0, &dwID);
if (m_hSchedulerThread == NULL)
{
CHECK_HR(hr = HRESULT_FROM_WIN32(GetLastError()));
}
HANDLE hObjects[] = { m_hThreadReadyEvent, m_hSchedulerThread };
DWORD dwWait = 0;
// Wait for the thread to signal the "thread ready" event.
dwWait = WaitForMultipleObjects(2, hObjects, FALSE, INFINITE); // Wait for EITHER of these handles.
if (WAIT_OBJECT_0 != dwWait)
{
// The thread terminated early for some reason. This is an error condition.
CloseHandle(m_hSchedulerThread);
m_hSchedulerThread = NULL;
CHECK_HR(hr = E_UNEXPECTED);
}
m_dwThreadID = dwID;
done:
// Regardless success/failure, we are done using the "thread ready" event.
if (m_hThreadReadyEvent)
{
CloseHandle(m_hThreadReadyEvent);
m_hThreadReadyEvent = NULL;
}
return hr;
}
//-----------------------------------------------------------------------------
// StopScheduler
//
// Stops the scheduler's worker thread.
//-----------------------------------------------------------------------------
HRESULT Scheduler::StopScheduler()
{
if (m_hSchedulerThread == NULL)
{
return S_OK;
}
// Ask the scheduler thread to exit.
PostThreadMessage(m_dwThreadID, eTerminate, 0, 0);
// Wait for the thread to exit.
WaitForSingleObject(m_hSchedulerThread, INFINITE);
// Close handles.
CloseHandle(m_hSchedulerThread);
m_hSchedulerThread = NULL;
CloseHandle(m_hFlushEvent);
m_hFlushEvent = NULL;
// Discard samples.
m_ScheduledSamples.Clear();
// Restore the timer resolution.
timeEndPeriod(1);
return S_OK;
}
//-----------------------------------------------------------------------------
// Flush
//
// Flushes all samples that are queued for presentation.
//
// Note: This method is synchronous; ie., it waits for the flush operation to
// complete on the worker thread.
//-----------------------------------------------------------------------------
HRESULT Scheduler::Flush()
{
TRACE((L"Scheduler::Flush\n"));
if (m_hSchedulerThread == NULL)
{
TRACE((L"No scheduler thread!\n"));
}
if (m_hSchedulerThread)
{
// Ask the scheduler thread to flush.
PostThreadMessage(m_dwThreadID, eFlush, 0 , 0);
// Wait for the scheduler thread to signal the flush event,
// OR for the thread to terminate.
HANDLE objects[] = { m_hFlushEvent, m_hSchedulerThread };
WaitForMultipleObjects(ARRAY_SIZE(objects), objects, FALSE, SCHEDULER_TIMEOUT);
TRACE((L"Scheduler::Flush completed.\n"));
}
return S_OK;
}
//-----------------------------------------------------------------------------
// ScheduleSample
//
// Schedules a new sample for presentation.
//
// pSample: Pointer to the sample.
// bPresentNow: If TRUE, the sample is presented immediately. Otherwise, the
// sample's time stamp is used to schedule the sample.
//-----------------------------------------------------------------------------
HRESULT Scheduler::ScheduleSample(IMFSample *pSample, BOOL bPresentNow)
{
if (m_pCB == NULL)
{
return MF_E_NOT_INITIALIZED;
}
if (m_hSchedulerThread == NULL)
{
return MF_E_NOT_INITIALIZED;
}
HRESULT hr = S_OK;
DWORD dwExitCode = 0;
GetExitCodeThread(m_hSchedulerThread, &dwExitCode);
if (dwExitCode != STILL_ACTIVE)
{
return E_FAIL;
}
if (bPresentNow || (m_pClock == NULL))
{
// Present the sample immediately.
m_pCB->PresentSample(pSample, 0);
}
else
{
// Queue the sample and ask the scheduler thread to wake up.
hr = m_ScheduledSamples.Queue(pSample);
if (SUCCEEDED(hr))
{
PostThreadMessage(m_dwThreadID, eSchedule, 0, 0);
}
}
LOG_MSG_IF_FAILED(L"Scheduler::ScheduleSample failed", hr);
return hr;
}
//-----------------------------------------------------------------------------
// ProcessSamplesInQueue
//
// Processes all the samples in the queue.
//
// plNextSleep: Receives the length of time the scheduler thread should sleep
// before it calls ProcessSamplesInQueue again.
//-----------------------------------------------------------------------------
HRESULT Scheduler::ProcessSamplesInQueue(LONG *plNextSleep)
{
HRESULT hr = S_OK;
LONG lWait = 0;
IMFSample *pSample = NULL;
// Process samples until the queue is empty or until the wait time > 0.
// Note: Dequeue returns S_FALSE when the queue is empty.
while (m_ScheduledSamples.Dequeue(&pSample) == S_OK)
{
// Process the next sample in the queue. If the sample is not ready
// for presentation. the value returned in lWait is > 0, which
// means the scheduler should sleep for that amount of time.
hr = ProcessSample(pSample, &lWait);
SAFE_RELEASE(pSample);
if (FAILED(hr))
{
break;
}
if (lWait > 0)
{
break;
}
}
// If the wait time is zero, it means we stopped because the queue is
// empty (or an error occurred). Set the wait time to infinite; this will
// make the scheduler thread sleep until it gets another thread message.
if (lWait == 0)
{
lWait = INFINITE;
}
*plNextSleep = lWait;
return hr;
}
//-----------------------------------------------------------------------------
// ProcessSample
//
// Processes a sample.
//
// plNextSleep: Receives the length of time the scheduler thread should sleep.
//-----------------------------------------------------------------------------
HRESULT Scheduler::ProcessSample(IMFSample *pSample, LONG *plNextSleep)
{
HRESULT hr = S_OK;
LONGLONG hnsPresentationTime = 0;
LONGLONG hnsTimeNow = 0;
MFTIME hnsSystemTime = 0;
BOOL bPresentNow = TRUE;
LONG lNextSleep = 0;
if (m_pClock)
{
// Get the sample's time stamp. It is valid for a sample to
// have no time stamp.
hr = pSample->GetSampleTime(&hnsPresentationTime);
// Get the clock time. (But if the sample does not have a time stamp,
// we don't need the clock time.)
if (SUCCEEDED(hr))
{
hr = m_pClock->GetCorrelatedTime(0, &hnsTimeNow, &hnsSystemTime);
}
// Calculate the time until the sample's presentation time.
// A negative value means the sample is late.
LONGLONG hnsDelta = hnsPresentationTime - hnsTimeNow;
if (m_fRate < 0)
{
// For reverse playback, the clock runs backward. Therefore the delta is reversed.
hnsDelta = - hnsDelta;
}
if (hnsDelta < - m_PerFrame_1_4th)
{
// This sample is late.
bPresentNow = TRUE;
}
else if (hnsDelta > (3 * m_PerFrame_1_4th))
{
// This sample is still too early. Go to sleep.
lNextSleep = MFTimeToMsec(hnsDelta - (3 * m_PerFrame_1_4th));
// Adjust the sleep time for the clock rate. (The presentation clock runs
// at m_fRate, but sleeping uses the system clock.)
lNextSleep = (LONG)(lNextSleep / fabsf(m_fRate));
// Don't present yet.
bPresentNow = FALSE;
}
}
if (bPresentNow)
{
hr = m_pCB->PresentSample(pSample, hnsPresentationTime);
}
else
{
// The sample is not ready yet. Return it to the queue.
hr = m_ScheduledSamples.PutBack(pSample);
}
*plNextSleep = lNextSleep;
return hr;
}
//-----------------------------------------------------------------------------
// SchedulerThreadProc (static method)
//
// ThreadProc for the scheduler thread.
//-----------------------------------------------------------------------------
DWORD WINAPI Scheduler::SchedulerThreadProc(LPVOID lpParameter)
{
Scheduler* pScheduler = reinterpret_cast<Scheduler*>(lpParameter);
if (pScheduler == NULL)
{
return -1;
}
return pScheduler->SchedulerThreadProcPrivate();
}
//-----------------------------------------------------------------------------
// SchedulerThreadProcPrivate
//
// Non-static version of the ThreadProc.
//-----------------------------------------------------------------------------
DWORD Scheduler::SchedulerThreadProcPrivate()
{
HRESULT hr = S_OK;
MSG msg;
LONG lWait = INFINITE;
BOOL bExitThread = FALSE;
// Force the system to create a message queue for this thread.
// (See MSDN documentation for PostThreadMessage.)
PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);
// Signal to the scheduler that the thread is ready.
SetEvent(m_hThreadReadyEvent);
while( !bExitThread )
{
// Wait for a thread message OR until the wait time expires.
DWORD dwResult = MsgWaitForMultipleObjects(0, NULL, FALSE, lWait, QS_POSTMESSAGE);
if (dwResult == WAIT_TIMEOUT)
{
// If we timed out, then process the samples in the queue
hr = ProcessSamplesInQueue(&lWait);
if (FAILED(hr))
{
bExitThread = TRUE;
}
}
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
BOOL bProcessSamples = TRUE;
switch (msg.message)
{
case eTerminate:
TRACE((L"eTerminate\n"));
bExitThread = TRUE;
break;
case eFlush:
// Flushing: Clear the sample queue and set the event.
m_ScheduledSamples.Clear();
lWait = INFINITE;
SetEvent(m_hFlushEvent);
break;
case eSchedule:
// Process as many samples as we can.
if (bProcessSamples)
{
hr = ProcessSamplesInQueue(&lWait);
if (FAILED(hr))
{
bExitThread = TRUE;
}
bProcessSamples = (lWait != INFINITE);
}
break;
} // switch
} // while PeekMessage
} // while (!bExitThread)
TRACE((L"Exit scheduler thread.\n"));
return (SUCCEEDED(hr) ? 0 : 1);
}