diff --git a/app/app.pro b/app/app.pro index 3df4d7f3..afde7514 100644 --- a/app/app.pro +++ b/app/app.pro @@ -113,10 +113,13 @@ ffmpeg { DEFINES += HAVE_FFMPEG SOURCES += \ streaming/video/ffmpeg.cpp \ - streaming/video/ffmpeg-renderers/sdl.cpp + streaming/video/ffmpeg-renderers/sdl.cpp \ + streaming/video/ffmpeg-renderers/pacer.cpp + HEADERS += \ streaming/video/ffmpeg.h \ - streaming/video/ffmpeg-renderers/renderer.h + streaming/video/ffmpeg-renderers/renderer.h \ + streaming/video/ffmpeg-renderers/pacer.h } libva { message(VAAPI renderer selected) diff --git a/app/streaming/video/ffmpeg-renderers/pacer.cpp b/app/streaming/video/ffmpeg-renderers/pacer.cpp new file mode 100644 index 00000000..1c09a930 --- /dev/null +++ b/app/streaming/video/ffmpeg-renderers/pacer.cpp @@ -0,0 +1,70 @@ +#include "pacer.h" + +#define FRAME_HISTORY_ENTRIES 8 + +Pacer::Pacer() : + m_FrameQueueLock(0) +{ + +} + +Pacer::~Pacer() +{ + drain(); +} + +AVFrame* Pacer::getFrameAtVsync() +{ + SDL_AtomicLock(&m_FrameQueueLock); + + int frameDropTarget; + + // If the queue length history entries are large, be strict + // about dropping excess frames. + frameDropTarget = 1; + for (int i = 0; i < m_FrameQueueHistory.count(); i++) { + if (m_FrameQueueHistory[i] <= 1) { + // Be lenient as long as the queue length + // resolves before the end of frame history + frameDropTarget = 3; + } + } + + if (m_FrameQueueHistory.count() == FRAME_HISTORY_ENTRIES) { + m_FrameQueueHistory.dequeue(); + } + + m_FrameQueueHistory.enqueue(m_FrameQueue.count()); + + // Catch up if we're several frames ahead + while (m_FrameQueue.count() > frameDropTarget) { + AVFrame* frame = m_FrameQueue.dequeue(); + av_frame_free(&frame); + } + + if (m_FrameQueue.isEmpty()) { + SDL_AtomicUnlock(&m_FrameQueueLock); + return nullptr; + } + + // Grab the first frame + AVFrame* frame = m_FrameQueue.dequeue(); + SDL_AtomicUnlock(&m_FrameQueueLock); + + return frame; +} + +void Pacer::submitFrame(AVFrame* frame) +{ + SDL_AtomicLock(&m_FrameQueueLock); + m_FrameQueue.enqueue(frame); + SDL_AtomicUnlock(&m_FrameQueueLock); +} + +void Pacer::drain() +{ + while (!m_FrameQueue.isEmpty()) { + AVFrame* frame = m_FrameQueue.dequeue(); + av_frame_free(&frame); + } +} diff --git a/app/streaming/video/ffmpeg-renderers/pacer.h b/app/streaming/video/ffmpeg-renderers/pacer.h new file mode 100644 index 00000000..9afe7ef3 --- /dev/null +++ b/app/streaming/video/ffmpeg-renderers/pacer.h @@ -0,0 +1,24 @@ +#pragma once + +#include "renderer.h" + +#include + +class Pacer +{ +public: + Pacer(); + + ~Pacer(); + + AVFrame* getFrameAtVsync(); + + void submitFrame(AVFrame* frame); + + void drain(); + +private: + QQueue m_FrameQueue; + QQueue m_FrameQueueHistory; + SDL_SpinLock m_FrameQueueLock; +}; diff --git a/app/streaming/video/ffmpeg-renderers/vt.mm b/app/streaming/video/ffmpeg-renderers/vt.mm index 319a23e8..89db619c 100644 --- a/app/streaming/video/ffmpeg-renderers/vt.mm +++ b/app/streaming/video/ffmpeg-renderers/vt.mm @@ -2,20 +2,17 @@ // libavutil both defining AVMediaType #define AVMediaType AVMediaType_FFmpeg #include "vt.h" +#include "pacer.h" #undef AVMediaType #include #include -#include - #import #import #import #import -#define FRAME_HISTORY_ENTRIES 8 - class VTRenderer : public IFFmpegRenderer { public: @@ -24,13 +21,14 @@ public: m_DisplayLayer(nullptr), m_FormatDesc(nullptr), m_View(nullptr), - m_DisplayLink(nullptr), - m_FrameQueueLock(0) + m_DisplayLink(nullptr) { } virtual ~VTRenderer() { + m_Pacer.drain(); + if (m_HwContext != nullptr) { av_buffer_unref(&m_HwContext); } @@ -44,11 +42,6 @@ public: CVDisplayLinkRelease(m_DisplayLink); } - while (!m_FrameQueue.isEmpty()) { - AVFrame* frame = m_FrameQueue.dequeue(); - av_frame_free(&frame); - } - if (m_View != nullptr) { [m_View removeFromSuperview]; } @@ -58,42 +51,11 @@ public: { OSStatus status; - SDL_AtomicLock(&m_FrameQueueLock); - - int frameDropTarget; - - // If the queue length history entries are large, be strict - // about dropping excess frames. - frameDropTarget = 1; - for (int i = 0; i < m_FrameQueueHistory.count(); i++) { - if (m_FrameQueueHistory[i] <= 1) { - // Be lenient as long as the queue length - // resolves before the end of frame history - frameDropTarget = 3; - } - } - - if (m_FrameQueueHistory.count() == FRAME_HISTORY_ENTRIES) { - m_FrameQueueHistory.dequeue(); - } - - m_FrameQueueHistory.enqueue(m_FrameQueue.count()); - - // Catch up if we're several frames ahead - while (m_FrameQueue.count() > frameDropTarget) { - AVFrame* frame = m_FrameQueue.dequeue(); - av_frame_free(&frame); - } - - if (m_FrameQueue.isEmpty()) { - SDL_AtomicUnlock(&m_FrameQueueLock); + AVFrame* frame = m_Pacer.getFrameAtVsync(); + if (frame == nullptr) { return; } - // Grab the first frame - AVFrame* frame = m_FrameQueue.dequeue(); - SDL_AtomicUnlock(&m_FrameQueueLock); - CVPixelBufferRef pixBuf = reinterpret_cast(frame->data[3]); // If the format has changed or doesn't exist yet, construct it with the @@ -268,9 +230,7 @@ public: setupDisplayLayer(); } - SDL_AtomicLock(&m_FrameQueueLock); - m_FrameQueue.enqueue(frame); - SDL_AtomicUnlock(&m_FrameQueueLock); + m_Pacer.submitFrame(frame); } private: @@ -297,9 +257,7 @@ private: CMVideoFormatDescriptionRef m_FormatDesc; NSView* m_View; CVDisplayLinkRef m_DisplayLink; - QQueue m_FrameQueue; - QQueue m_FrameQueueHistory; - SDL_SpinLock m_FrameQueueLock; + Pacer m_Pacer; }; IFFmpegRenderer* VTRendererFactory::createRenderer() {