2018-08-15 02:13:17 +00:00
|
|
|
#include "pacer.h"
|
2018-09-08 22:09:46 +00:00
|
|
|
#include "streaming/streamutils.h"
|
2018-08-15 02:13:17 +00:00
|
|
|
|
2018-08-21 00:53:35 +00:00
|
|
|
#include "nullthreadedvsyncsource.h"
|
|
|
|
|
2018-08-16 05:02:15 +00:00
|
|
|
#ifdef Q_OS_DARWIN
|
|
|
|
#include "displaylinkvsyncsource.h"
|
|
|
|
#endif
|
|
|
|
|
2018-08-16 06:20:56 +00:00
|
|
|
#ifdef Q_OS_WIN32
|
|
|
|
#include "dxvsyncsource.h"
|
|
|
|
#endif
|
|
|
|
|
2018-08-15 02:13:17 +00:00
|
|
|
#define FRAME_HISTORY_ENTRIES 8
|
2019-01-22 01:58:42 +00:00
|
|
|
#define RENDER_TIME_HISTORY_ENTRIES 8
|
2018-08-15 02:13:17 +00:00
|
|
|
|
2018-08-21 00:53:35 +00:00
|
|
|
// We may be woken up slightly late so don't go all the way
|
|
|
|
// up to the next V-sync since we may accidentally step into
|
|
|
|
// the next V-sync period. It also takes some amount of time
|
|
|
|
// to do the render itself, so we can't render right before
|
|
|
|
// V-sync happens.
|
|
|
|
#define TIMER_SLACK_MS 3
|
|
|
|
|
2018-09-25 07:47:59 +00:00
|
|
|
Pacer::Pacer(IFFmpegRenderer* renderer, PVIDEO_STATS videoStats) :
|
2018-08-16 04:10:35 +00:00
|
|
|
m_FrameQueueLock(0),
|
2018-08-16 05:02:15 +00:00
|
|
|
m_VsyncSource(nullptr),
|
|
|
|
m_VsyncRenderer(renderer),
|
2018-08-16 04:10:35 +00:00
|
|
|
m_MaxVideoFps(0),
|
2018-09-25 07:47:59 +00:00
|
|
|
m_DisplayFps(0),
|
|
|
|
m_VideoStats(videoStats)
|
2018-08-15 02:13:17 +00:00
|
|
|
{
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
Pacer::~Pacer()
|
|
|
|
{
|
2018-08-16 07:04:28 +00:00
|
|
|
// Stop V-sync callbacks
|
|
|
|
delete m_VsyncSource;
|
|
|
|
m_VsyncSource = nullptr;
|
|
|
|
|
|
|
|
while (!m_FrameQueue.isEmpty()) {
|
|
|
|
AVFrame* frame = m_FrameQueue.dequeue();
|
|
|
|
av_frame_free(&frame);
|
|
|
|
}
|
2018-08-16 04:10:35 +00:00
|
|
|
}
|
|
|
|
|
2018-08-16 05:02:15 +00:00
|
|
|
// Called in an arbitrary thread by the IVsyncSource on V-sync
|
|
|
|
// or an event synchronized with V-sync
|
2018-08-21 00:53:35 +00:00
|
|
|
void Pacer::vsyncCallback(int timeUntilNextVsyncMillis)
|
2018-08-15 02:13:17 +00:00
|
|
|
{
|
2018-08-16 04:10:35 +00:00
|
|
|
// Make sure initialize() has been called
|
|
|
|
SDL_assert(m_MaxVideoFps != 0);
|
2018-08-15 02:13:17 +00:00
|
|
|
|
2018-08-21 00:53:35 +00:00
|
|
|
SDL_assert(timeUntilNextVsyncMillis >= TIMER_SLACK_MS);
|
|
|
|
|
|
|
|
Uint32 vsyncCallbackStartTime = SDL_GetTicks();
|
|
|
|
|
2018-08-16 04:10:35 +00:00
|
|
|
SDL_AtomicLock(&m_FrameQueueLock);
|
2018-08-15 02:13:17 +00:00
|
|
|
|
|
|
|
// If the queue length history entries are large, be strict
|
|
|
|
// about dropping excess frames.
|
2018-08-16 04:10:35 +00:00
|
|
|
int frameDropTarget = 1;
|
|
|
|
|
|
|
|
// If we may get more frames per second than we can display, use
|
|
|
|
// frame history to drop frames only if consistently above the
|
|
|
|
// one queued frame mark.
|
|
|
|
if (m_MaxVideoFps >= m_DisplayFps) {
|
2019-01-22 01:58:42 +00:00
|
|
|
for (int queueHistoryEntry : m_FrameQueueHistory) {
|
|
|
|
if (queueHistoryEntry <= 1) {
|
2018-08-16 04:10:35 +00:00
|
|
|
// Be lenient as long as the queue length
|
|
|
|
// resolves before the end of frame history
|
|
|
|
frameDropTarget = 3;
|
2018-08-16 05:02:15 +00:00
|
|
|
break;
|
2018-08-16 04:10:35 +00:00
|
|
|
}
|
2018-08-15 02:13:17 +00:00
|
|
|
}
|
|
|
|
|
2018-08-16 04:10:35 +00:00
|
|
|
if (m_FrameQueueHistory.count() == FRAME_HISTORY_ENTRIES) {
|
|
|
|
m_FrameQueueHistory.dequeue();
|
|
|
|
}
|
2018-08-15 02:13:17 +00:00
|
|
|
|
2018-08-16 04:10:35 +00:00
|
|
|
m_FrameQueueHistory.enqueue(m_FrameQueue.count());
|
|
|
|
}
|
2018-08-15 02:13:17 +00:00
|
|
|
|
|
|
|
// Catch up if we're several frames ahead
|
|
|
|
while (m_FrameQueue.count() > frameDropTarget) {
|
|
|
|
AVFrame* frame = m_FrameQueue.dequeue();
|
2018-09-25 07:47:59 +00:00
|
|
|
m_VideoStats->pacerDroppedFrames++;
|
2018-08-15 02:13:17 +00:00
|
|
|
av_frame_free(&frame);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m_FrameQueue.isEmpty()) {
|
|
|
|
SDL_AtomicUnlock(&m_FrameQueueLock);
|
2018-08-16 05:02:15 +00:00
|
|
|
|
2018-08-21 00:53:35 +00:00
|
|
|
while (!SDL_TICKS_PASSED(SDL_GetTicks(),
|
|
|
|
vsyncCallbackStartTime + timeUntilNextVsyncMillis - TIMER_SLACK_MS)) {
|
|
|
|
SDL_Delay(1);
|
|
|
|
|
|
|
|
SDL_AtomicLock(&m_FrameQueueLock);
|
|
|
|
if (!m_FrameQueue.isEmpty()) {
|
|
|
|
// Don't release the lock
|
|
|
|
goto RenderNextFrame;
|
|
|
|
}
|
|
|
|
SDL_AtomicUnlock(&m_FrameQueueLock);
|
|
|
|
}
|
|
|
|
|
2019-01-21 03:59:29 +00:00
|
|
|
// Nothing to render at this time. This counts as a drop.
|
2019-01-22 01:58:42 +00:00
|
|
|
addRenderTimeToHistory(0);
|
2018-08-16 05:02:15 +00:00
|
|
|
return;
|
2018-08-15 02:13:17 +00:00
|
|
|
}
|
|
|
|
|
2018-08-21 00:53:35 +00:00
|
|
|
RenderNextFrame:
|
2018-08-15 02:13:17 +00:00
|
|
|
// Grab the first frame
|
|
|
|
AVFrame* frame = m_FrameQueue.dequeue();
|
|
|
|
SDL_AtomicUnlock(&m_FrameQueueLock);
|
|
|
|
|
2019-01-21 03:59:29 +00:00
|
|
|
renderFrame(frame);
|
2018-08-16 05:02:15 +00:00
|
|
|
}
|
|
|
|
|
2018-12-25 20:57:00 +00:00
|
|
|
bool Pacer::initialize(SDL_Window* window, int maxVideoFps, bool enablePacing)
|
2018-08-16 05:02:15 +00:00
|
|
|
{
|
|
|
|
m_MaxVideoFps = maxVideoFps;
|
2018-09-08 22:09:46 +00:00
|
|
|
m_DisplayFps = StreamUtils::getDisplayRefreshRate(window);
|
2018-09-04 02:54:41 +00:00
|
|
|
|
2018-12-25 20:57:00 +00:00
|
|
|
if (enablePacing) {
|
2018-08-21 01:19:42 +00:00
|
|
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
2018-12-25 20:57:00 +00:00
|
|
|
"Frame pacing active: target %d Hz with %d FPS stream",
|
2018-08-21 01:19:42 +00:00
|
|
|
m_DisplayFps, m_MaxVideoFps);
|
|
|
|
|
|
|
|
#if defined(Q_OS_DARWIN)
|
2018-09-04 00:19:24 +00:00
|
|
|
m_VsyncSource = DisplayLinkVsyncSourceFactory::createVsyncSource(this);
|
2018-08-21 01:19:42 +00:00
|
|
|
#elif defined(Q_OS_WIN32)
|
|
|
|
m_VsyncSource = new DxVsyncSource(this);
|
|
|
|
#else
|
|
|
|
// Platforms without a VsyncSource will just render frames
|
|
|
|
// immediately like they used to.
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (m_VsyncSource != nullptr && !m_VsyncSource->initialize(window, m_DisplayFps)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
2018-12-25 20:57:00 +00:00
|
|
|
"Frame pacing disabled: target %d Hz with %d FPS stream",
|
2018-08-21 01:19:42 +00:00
|
|
|
m_DisplayFps, m_MaxVideoFps);
|
2018-08-16 06:57:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
2018-08-15 02:13:17 +00:00
|
|
|
}
|
|
|
|
|
2019-01-22 01:58:42 +00:00
|
|
|
void Pacer::addRenderTimeToHistory(int renderTime)
|
|
|
|
{
|
|
|
|
if (m_RenderTimeHistory.count() == RENDER_TIME_HISTORY_ENTRIES) {
|
|
|
|
m_RenderTimeHistory.dequeue();
|
|
|
|
}
|
|
|
|
|
|
|
|
m_RenderTimeHistory.enqueue(renderTime);
|
|
|
|
}
|
|
|
|
|
2019-01-21 03:59:29 +00:00
|
|
|
void Pacer::renderFrame(AVFrame* frame)
|
|
|
|
{
|
2019-01-22 01:58:42 +00:00
|
|
|
bool dropFrame = !m_RenderTimeHistory.isEmpty();
|
|
|
|
|
|
|
|
// If rendering consistently takes longer than an entire V-sync interval,
|
|
|
|
// there must have been frames pending in the pipeline that are blocking us.
|
|
|
|
// Drop our this frame to clear the backed up display pipeline.
|
|
|
|
for (int renderTime : m_RenderTimeHistory) {
|
|
|
|
if (renderTime <= 1000 / m_DisplayFps) {
|
|
|
|
dropFrame = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-21 03:59:29 +00:00
|
|
|
// Drop this frame if we believe we've run into V-sync waits
|
2019-01-22 01:58:42 +00:00
|
|
|
if (dropFrame) {
|
|
|
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
|
|
|
"Render time (%d ms) consistently exceeded V-sync period; dropping frame to reduce latency",
|
|
|
|
m_RenderTimeHistory[0]);
|
|
|
|
|
|
|
|
// Add 0 render time to history to account for the drop
|
|
|
|
addRenderTimeToHistory(0);
|
|
|
|
|
2019-01-21 03:59:29 +00:00
|
|
|
m_VideoStats->pacerDroppedFrames++;
|
|
|
|
av_frame_free(&frame);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Count time spent in Pacer's queues
|
|
|
|
Uint32 beforeRender = SDL_GetTicks();
|
|
|
|
m_VideoStats->totalPacerTime += beforeRender - frame->pts;
|
|
|
|
|
|
|
|
// Render it
|
|
|
|
m_VsyncRenderer->renderFrameAtVsync(frame);
|
|
|
|
Uint32 afterRender = SDL_GetTicks();
|
|
|
|
|
2019-01-22 01:58:42 +00:00
|
|
|
addRenderTimeToHistory(afterRender - beforeRender);
|
2019-01-21 03:59:29 +00:00
|
|
|
|
|
|
|
m_VideoStats->totalRenderTime += afterRender - beforeRender;
|
|
|
|
m_VideoStats->renderedFrames++;
|
|
|
|
av_frame_free(&frame);
|
|
|
|
}
|
|
|
|
|
2018-08-15 02:13:17 +00:00
|
|
|
void Pacer::submitFrame(AVFrame* frame)
|
|
|
|
{
|
2018-08-16 04:10:35 +00:00
|
|
|
// Make sure initialize() has been called
|
|
|
|
SDL_assert(m_MaxVideoFps != 0);
|
|
|
|
|
2018-08-16 06:57:03 +00:00
|
|
|
// Queue the frame until the V-sync callback if
|
|
|
|
// we have a V-sync source, otherwise deliver it
|
|
|
|
// immediately and hope for the best.
|
2018-08-17 00:59:33 +00:00
|
|
|
if (isUsingFrameQueue()) {
|
2018-08-16 06:57:03 +00:00
|
|
|
SDL_AtomicLock(&m_FrameQueueLock);
|
|
|
|
m_FrameQueue.enqueue(frame);
|
|
|
|
SDL_AtomicUnlock(&m_FrameQueueLock);
|
|
|
|
}
|
|
|
|
else {
|
2019-01-21 03:59:29 +00:00
|
|
|
renderFrame(frame);
|
2018-08-16 06:57:03 +00:00
|
|
|
}
|
2018-08-15 02:13:17 +00:00
|
|
|
}
|
2018-08-17 00:59:33 +00:00
|
|
|
|
|
|
|
bool Pacer::isUsingFrameQueue()
|
|
|
|
{
|
|
|
|
return m_VsyncSource != nullptr;
|
|
|
|
}
|