diff --git a/app/app.pro b/app/app.pro index 7ff21a31..84b4a835 100644 --- a/app/app.pro +++ b/app/app.pro @@ -34,7 +34,7 @@ win32 { LIBS += -L$$PWD/../libs/windows/lib/x64 } - LIBS += ws2_32.lib winmm.lib dxva2.lib ole32.lib + LIBS += ws2_32.lib winmm.lib dxva2.lib ole32.lib dwmapi.lib } macx { INCLUDEPATH += $$PWD/../libs/mac/include $$PWD/../libs/mac/Frameworks/SDL2.framework/Versions/A/Headers @@ -159,8 +159,13 @@ config_SLVideo { win32 { message(DXVA2 renderer selected) - SOURCES += streaming/video/ffmpeg-renderers/dxva2.cpp - HEADERS += streaming/video/ffmpeg-renderers/dxva2.h + SOURCES += \ + streaming/video/ffmpeg-renderers/dxva2.cpp \ + streaming/video/ffmpeg-renderers/pacer/dxvsyncsource.cpp + + HEADERS += \ + streaming/video/ffmpeg-renderers/dxva2.h \ + streaming/video/ffmpeg-renderers/pacer/dxvsyncsource.h } macx { message(VideoToolbox renderer selected) diff --git a/app/streaming/session.cpp b/app/streaming/session.cpp index aa933db1..ec7f0a08 100644 --- a/app/streaming/session.cpp +++ b/app/streaming/session.cpp @@ -821,7 +821,7 @@ void Session::exec() // Destroy the old decoder delete m_VideoDecoder; - // Chose a new decoder (hopefully the same one, but possibly + // Choose a new decoder (hopefully the same one, but possibly // not if a GPU was removed or something). if (!chooseDecoder(m_Preferences.videoDecoderSelection, m_Window, m_ActiveVideoFormat, m_ActiveVideoWidth, diff --git a/app/streaming/video/ffmpeg-renderers/dxva2.cpp b/app/streaming/video/ffmpeg-renderers/dxva2.cpp index 87a5d4ee..61effc76 100644 --- a/app/streaming/video/ffmpeg-renderers/dxva2.cpp +++ b/app/streaming/video/ffmpeg-renderers/dxva2.cpp @@ -11,6 +11,7 @@ DEFINE_GUID(DXVADDI_Intel_ModeH264_E, 0x604F8E68,0x4951,0x4C54,0x88,0xFE,0xAB,0x DXVA2Renderer::DXVA2Renderer() : m_SdlRenderer(nullptr), + m_Pacer(this), m_DecService(nullptr), m_Decoder(nullptr), m_SurfacesUsed(0), @@ -27,6 +28,8 @@ DXVA2Renderer::DXVA2Renderer() : DXVA2Renderer::~DXVA2Renderer() { + m_Pacer.drain(); + SAFE_COM_RELEASE(m_DecService); SAFE_COM_RELEASE(m_Decoder); SAFE_COM_RELEASE(m_Device); @@ -430,12 +433,16 @@ bool DXVA2Renderer::isDecoderBlacklisted() return result; } -bool DXVA2Renderer::initialize(SDL_Window* window, int videoFormat, int width, int height, int) +bool DXVA2Renderer::initialize(SDL_Window* window, int videoFormat, int width, int height, int maxFps) { m_VideoFormat = videoFormat; m_VideoWidth = width; m_VideoHeight = height; + if (!m_Pacer.initialize(window, maxFps)) { + return false; + } + // FFmpeg will be decoding on different threads than the main thread that we're // currently running on right now. We must set this hint so SDL will pass // D3DCREATE_MULTITHREADED to IDirect3D9::CreateDevice(). @@ -493,6 +500,11 @@ bool DXVA2Renderer::initialize(SDL_Window* window, int videoFormat, int width, i } void DXVA2Renderer::renderFrame(AVFrame* frame) +{ + m_Pacer.submitFrame(frame); +} + +void DXVA2Renderer::renderFrameAtVsync(AVFrame *frame) { IDirect3DSurface9* surface = reinterpret_cast(frame->data[3]); HRESULT hr; @@ -632,7 +644,6 @@ void DXVA2Renderer::renderFrame(AVFrame* frame) SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "SDL_RenderClear() failed: %s", SDL_GetError()); - av_frame_free(&frame); // We're going to cheat a little bit here. It seems SDL's // renderer may flake out in scenarios like moving the window @@ -646,8 +657,6 @@ void DXVA2Renderer::renderFrame(AVFrame* frame) } hr = m_Processor->VideoProcessBlt(m_RenderTarget, &bltParams, &sample, 1, nullptr); - av_frame_free(&frame); - if (FAILED(hr)) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "VideoProcessBlt() failed: %x", diff --git a/app/streaming/video/ffmpeg-renderers/dxva2.h b/app/streaming/video/ffmpeg-renderers/dxva2.h index 6ec1f5f4..a47644be 100644 --- a/app/streaming/video/ffmpeg-renderers/dxva2.h +++ b/app/streaming/video/ffmpeg-renderers/dxva2.h @@ -1,6 +1,7 @@ #pragma once #include "renderer.h" +#include "pacer/pacer.h" #include #include @@ -9,7 +10,7 @@ extern "C" { #include } -class DXVA2Renderer : public IFFmpegRenderer +class DXVA2Renderer : public IFFmpegRenderer, public IVsyncRenderer { public: DXVA2Renderer(); @@ -21,6 +22,7 @@ public: int maxFps); virtual bool prepareDecoderContext(AVCodecContext* context); virtual void renderFrame(AVFrame* frame); + virtual void renderFrameAtVsync(AVFrame* frame); private: bool initializeDecoder(); @@ -44,6 +46,7 @@ private: int m_DisplayHeight; SDL_Renderer* m_SdlRenderer; + Pacer m_Pacer; struct dxva_context m_DXVAContext; IDirect3DSurface9* m_DecSurfaces[19]; diff --git a/app/streaming/video/ffmpeg-renderers/pacer/dxvsyncsource.cpp b/app/streaming/video/ffmpeg-renderers/pacer/dxvsyncsource.cpp new file mode 100644 index 00000000..23ddc0fc --- /dev/null +++ b/app/streaming/video/ffmpeg-renderers/pacer/dxvsyncsource.cpp @@ -0,0 +1,49 @@ +#include "dxvsyncsource.h" + +#include + +DxVsyncSource::DxVsyncSource(Pacer* pacer) : + m_Pacer(pacer), + m_Thread(nullptr) +{ + SDL_AtomicSet(&m_Stopping, 0); +} + +DxVsyncSource::~DxVsyncSource() +{ + if (m_Thread != nullptr) { + SDL_AtomicSet(&m_Stopping, 1); + SDL_WaitThread(m_Thread, nullptr); + } +} + +bool DxVsyncSource::initialize(SDL_Window* window) +{ + m_Thread = SDL_CreateThread(vsyncThread, "DX Vsync Thread", this); + if (m_Thread == nullptr) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "Unable to create DX V-sync thread: %s", + SDL_GetError()); + return false; + } + + return true; +} + +int DxVsyncSource::vsyncThread(void* context) +{ + DxVsyncSource* me = reinterpret_cast(context); + + SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH); + + while (SDL_AtomicGet(&me->m_Stopping) == 0) { + // FIXME: We should really use D3DKMTWaitForVerticalBlankEvent() instead! + // https://bugs.chromium.org/p/chromium/issues/detail?id=467617 + // https://chromium.googlesource.com/chromium/src.git/+/c564f2fe339b2b2abb0c8773c90c83215670ea71/gpu/ipc/service/gpu_vsync_provider_win.cc + DwmFlush(); + + me->m_Pacer->vsyncCallback(); + } + + return 0; +} diff --git a/app/streaming/video/ffmpeg-renderers/pacer/dxvsyncsource.h b/app/streaming/video/ffmpeg-renderers/pacer/dxvsyncsource.h new file mode 100644 index 00000000..d3a93606 --- /dev/null +++ b/app/streaming/video/ffmpeg-renderers/pacer/dxvsyncsource.h @@ -0,0 +1,20 @@ +#pragma once + +#include "pacer.h" + +class DxVsyncSource : public IVsyncSource +{ +public: + DxVsyncSource(Pacer* pacer); + + virtual ~DxVsyncSource(); + + virtual bool initialize(SDL_Window* window); + +private: + static int vsyncThread(void* context); + + Pacer* m_Pacer; + SDL_Thread* m_Thread; + SDL_atomic_t m_Stopping; +}; diff --git a/app/streaming/video/ffmpeg-renderers/pacer/pacer.cpp b/app/streaming/video/ffmpeg-renderers/pacer/pacer.cpp index c3bc1794..3b7a867c 100644 --- a/app/streaming/video/ffmpeg-renderers/pacer/pacer.cpp +++ b/app/streaming/video/ffmpeg-renderers/pacer/pacer.cpp @@ -4,6 +4,10 @@ #include "displaylinkvsyncsource.h" #endif +#ifdef Q_OS_WIN32 +#include "dxvsyncsource.h" +#endif + #define FRAME_HISTORY_ENTRIES 8 Pacer::Pacer(IVsyncRenderer* renderer) : @@ -18,9 +22,6 @@ Pacer::Pacer(IVsyncRenderer* renderer) : Pacer::~Pacer() { - // Stop V-sync callbacks - delete m_VsyncSource; - drain(); } @@ -105,8 +106,10 @@ bool Pacer::initialize(SDL_Window* window, int maxVideoFps) "Frame pacing: target %d Hz with %d FPS stream", m_DisplayFps, m_MaxVideoFps); -#ifdef Q_OS_DARWIN +#if defined(Q_OS_DARWIN) m_VsyncSource = new DisplayLinkVsyncSource(this); +#elif defined(Q_OS_WIN32) + m_VsyncSource = new DxVsyncSource(this); #else SDL_assert(false); #endif @@ -126,6 +129,10 @@ void Pacer::submitFrame(AVFrame* frame) void Pacer::drain() { + // Stop V-sync callbacks + delete m_VsyncSource; + m_VsyncSource = nullptr; + while (!m_FrameQueue.isEmpty()) { AVFrame* frame = m_FrameQueue.dequeue(); av_frame_free(&frame); diff --git a/app/streaming/video/ffmpeg.cpp b/app/streaming/video/ffmpeg.cpp index 94eb3791..211a2e75 100644 --- a/app/streaming/video/ffmpeg.cpp +++ b/app/streaming/video/ffmpeg.cpp @@ -96,10 +96,10 @@ void FFmpegVideoDecoder::reset() } } - avcodec_free_context(&m_VideoDecoderCtx); - delete m_Renderer; m_Renderer = nullptr; + + avcodec_free_context(&m_VideoDecoderCtx); } bool FFmpegVideoDecoder::completeInitialization(AVCodec* decoder, int videoFormat, int width, int height, bool testOnly)