Use Pacer to drive all rendering

This commit is contained in:
Cameron Gutman 2018-08-15 23:57:03 -07:00
parent f6a451d6e7
commit 7d61acb2a8
13 changed files with 65 additions and 75 deletions

View file

@ -11,7 +11,6 @@ DEFINE_GUID(DXVADDI_Intel_ModeH264_E, 0x604F8E68,0x4951,0x4C54,0x88,0xFE,0xAB,0x
DXVA2Renderer::DXVA2Renderer() : DXVA2Renderer::DXVA2Renderer() :
m_SdlRenderer(nullptr), m_SdlRenderer(nullptr),
m_Pacer(this),
m_DecService(nullptr), m_DecService(nullptr),
m_Decoder(nullptr), m_Decoder(nullptr),
m_SurfacesUsed(0), m_SurfacesUsed(0),
@ -28,8 +27,6 @@ DXVA2Renderer::DXVA2Renderer() :
DXVA2Renderer::~DXVA2Renderer() DXVA2Renderer::~DXVA2Renderer()
{ {
m_Pacer.drain();
SAFE_COM_RELEASE(m_DecService); SAFE_COM_RELEASE(m_DecService);
SAFE_COM_RELEASE(m_Decoder); SAFE_COM_RELEASE(m_Decoder);
SAFE_COM_RELEASE(m_Device); SAFE_COM_RELEASE(m_Device);
@ -499,11 +496,6 @@ bool DXVA2Renderer::initialize(SDL_Window* window, int videoFormat, int width, i
return true; return true;
} }
void DXVA2Renderer::renderFrame(AVFrame* frame)
{
m_Pacer.submitFrame(frame);
}
void DXVA2Renderer::renderFrameAtVsync(AVFrame *frame) void DXVA2Renderer::renderFrameAtVsync(AVFrame *frame)
{ {
IDirect3DSurface9* surface = reinterpret_cast<IDirect3DSurface9*>(frame->data[3]); IDirect3DSurface9* surface = reinterpret_cast<IDirect3DSurface9*>(frame->data[3]);

View file

@ -10,7 +10,7 @@ extern "C" {
#include <libavcodec/dxva2.h> #include <libavcodec/dxva2.h>
} }
class DXVA2Renderer : public IFFmpegRenderer, public IVsyncRenderer class DXVA2Renderer : public IFFmpegRenderer
{ {
public: public:
DXVA2Renderer(); DXVA2Renderer();
@ -21,7 +21,6 @@ public:
int height, int height,
int maxFps); int maxFps);
virtual bool prepareDecoderContext(AVCodecContext* context); virtual bool prepareDecoderContext(AVCodecContext* context);
virtual void renderFrame(AVFrame* frame);
virtual void renderFrameAtVsync(AVFrame* frame); virtual void renderFrameAtVsync(AVFrame* frame);
private: private:
@ -46,7 +45,6 @@ private:
int m_DisplayHeight; int m_DisplayHeight;
SDL_Renderer* m_SdlRenderer; SDL_Renderer* m_SdlRenderer;
Pacer m_Pacer;
struct dxva_context m_DXVAContext; struct dxva_context m_DXVAContext;
IDirect3DSurface9* m_DecSurfaces[19]; IDirect3DSurface9* m_DecSurfaces[19];

View file

@ -10,7 +10,7 @@
#define FRAME_HISTORY_ENTRIES 8 #define FRAME_HISTORY_ENTRIES 8
Pacer::Pacer(IVsyncRenderer* renderer) : Pacer::Pacer(IFFmpegRenderer* renderer) :
m_FrameQueueLock(0), m_FrameQueueLock(0),
m_VsyncSource(nullptr), m_VsyncSource(nullptr),
m_VsyncRenderer(renderer), m_VsyncRenderer(renderer),
@ -111,10 +111,15 @@ bool Pacer::initialize(SDL_Window* window, int maxVideoFps)
#elif defined(Q_OS_WIN32) #elif defined(Q_OS_WIN32)
m_VsyncSource = new DxVsyncSource(this); m_VsyncSource = new DxVsyncSource(this);
#else #else
SDL_assert(false); // Platforms without a VsyncSource will just render frames
// immediately like they used to.
#endif #endif
return m_VsyncSource->initialize(window); if (m_VsyncSource != nullptr && !m_VsyncSource->initialize(window)) {
return false;
}
return true;
} }
void Pacer::submitFrame(AVFrame* frame) void Pacer::submitFrame(AVFrame* frame)
@ -122,9 +127,18 @@ void Pacer::submitFrame(AVFrame* frame)
// Make sure initialize() has been called // Make sure initialize() has been called
SDL_assert(m_MaxVideoFps != 0); SDL_assert(m_MaxVideoFps != 0);
// Queue the frame until the V-sync callback if
// we have a V-sync source, otherwise deliver it
// immediately and hope for the best.
if (m_VsyncSource != nullptr) {
SDL_AtomicLock(&m_FrameQueueLock); SDL_AtomicLock(&m_FrameQueueLock);
m_FrameQueue.enqueue(frame); m_FrameQueue.enqueue(frame);
SDL_AtomicUnlock(&m_FrameQueueLock); SDL_AtomicUnlock(&m_FrameQueueLock);
}
else {
m_VsyncRenderer->renderFrameAtVsync(frame);
av_frame_free(&frame);
}
} }
void Pacer::drain() void Pacer::drain()

View file

@ -4,14 +4,6 @@
#include <QQueue> #include <QQueue>
class Pacer;
class IVsyncRenderer {
public:
virtual ~IVsyncRenderer() {}
virtual void renderFrameAtVsync(AVFrame* frame) = 0;
};
class IVsyncSource { class IVsyncSource {
public: public:
virtual ~IVsyncSource() {} virtual ~IVsyncSource() {}
@ -21,7 +13,7 @@ public:
class Pacer class Pacer
{ {
public: public:
Pacer(IVsyncRenderer* renderer); Pacer(IFFmpegRenderer* renderer);
~Pacer(); ~Pacer();
@ -39,7 +31,7 @@ private:
SDL_SpinLock m_FrameQueueLock; SDL_SpinLock m_FrameQueueLock;
IVsyncSource* m_VsyncSource; IVsyncSource* m_VsyncSource;
IVsyncRenderer* m_VsyncRenderer; IFFmpegRenderer* m_VsyncRenderer;
int m_MaxVideoFps; int m_MaxVideoFps;
int m_DisplayFps; int m_DisplayFps;
}; };

View file

@ -15,7 +15,7 @@ public:
int height, int height,
int maxFps) = 0; int maxFps) = 0;
virtual bool prepareDecoderContext(AVCodecContext* context) = 0; virtual bool prepareDecoderContext(AVCodecContext* context) = 0;
virtual void renderFrame(AVFrame* frame) = 0; virtual void renderFrameAtVsync(AVFrame* frame) = 0;
}; };
class SdlRenderer : public IFFmpegRenderer { class SdlRenderer : public IFFmpegRenderer {
@ -28,7 +28,7 @@ public:
int height, int height,
int maxFps); int maxFps);
virtual bool prepareDecoderContext(AVCodecContext* context); virtual bool prepareDecoderContext(AVCodecContext* context);
virtual void renderFrame(AVFrame* frame); virtual void renderFrameAtVsync(AVFrame* frame);
private: private:
SDL_Renderer* m_Renderer; SDL_Renderer* m_Renderer;

View file

@ -68,7 +68,7 @@ bool SdlRenderer::initialize(SDL_Window* window,
return true; return true;
} }
void SdlRenderer::renderFrame(AVFrame* frame) void SdlRenderer::renderFrameAtVsync(AVFrame* frame)
{ {
SDL_UpdateYUVTexture(m_Texture, nullptr, SDL_UpdateYUVTexture(m_Texture, nullptr,
frame->data[0], frame->data[0],
@ -78,9 +78,6 @@ void SdlRenderer::renderFrame(AVFrame* frame)
frame->data[2], frame->data[2],
frame->linesize[2]); frame->linesize[2]);
// Done with the frame now
av_frame_free(&frame);
SDL_RenderClear(m_Renderer); SDL_RenderClear(m_Renderer);
SDL_RenderCopy(m_Renderer, m_Texture, nullptr, nullptr); SDL_RenderCopy(m_Renderer, m_Texture, nullptr, nullptr);
SDL_RenderPresent(m_Renderer); SDL_RenderPresent(m_Renderer);

View file

@ -142,7 +142,7 @@ VAAPIRenderer::prepareDecoderContext(AVCodecContext* context)
} }
void void
VAAPIRenderer::renderFrame(AVFrame* frame) VAAPIRenderer::renderFrameAtVsync(AVFrame* frame)
{ {
VASurfaceID surface = (VASurfaceID)(uintptr_t)frame->data[3]; VASurfaceID surface = (VASurfaceID)(uintptr_t)frame->data[3];
AVHWDeviceContext* deviceContext = (AVHWDeviceContext*)m_HwContext->data; AVHWDeviceContext* deviceContext = (AVHWDeviceContext*)m_HwContext->data;
@ -197,6 +197,4 @@ VAAPIRenderer::renderFrame(AVFrame* frame)
// We don't accept anything else in initialize(). // We don't accept anything else in initialize().
SDL_assert(false); SDL_assert(false);
} }
av_frame_free(&frame);
} }

View file

@ -36,7 +36,7 @@ public:
int height, int height,
int maxFps); int maxFps);
virtual bool prepareDecoderContext(AVCodecContext* context); virtual bool prepareDecoderContext(AVCodecContext* context);
virtual void renderFrame(AVFrame* frame); virtual void renderFrameAtVsync(AVFrame* frame);
private: private:
int m_WindowSystem; int m_WindowSystem;

View file

@ -223,7 +223,7 @@ bool VDPAURenderer::prepareDecoderContext(AVCodecContext* context)
return true; return true;
} }
void VDPAURenderer::renderFrame(AVFrame* frame) void VDPAURenderer::renderFrameAtVsync(AVFrame* frame)
{ {
VdpStatus status; VdpStatus status;
VdpVideoSurface videoSurface = (VdpVideoSurface)(uintptr_t)frame->data[3]; VdpVideoSurface videoSurface = (VdpVideoSurface)(uintptr_t)frame->data[3];
@ -243,7 +243,6 @@ void VDPAURenderer::renderFrame(AVFrame* frame)
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"VdpVideoSurfaceGetParameters() failed: %s", "VdpVideoSurfaceGetParameters() failed: %s",
m_VdpGetErrorString(status)); m_VdpGetErrorString(status));
av_frame_free(&frame);
return; return;
} }
@ -270,7 +269,6 @@ void VDPAURenderer::renderFrame(AVFrame* frame)
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"VdpVideoMixerCreate() failed: %s", "VdpVideoMixerCreate() failed: %s",
m_VdpGetErrorString(status)); m_VdpGetErrorString(status));
av_frame_free(&frame);
return; return;
} }
} }
@ -313,10 +311,6 @@ void VDPAURenderer::renderFrame(AVFrame* frame)
nullptr, nullptr,
0, 0,
nullptr); nullptr);
// The decoder can have this surface back now
av_frame_free(&frame);
if (status != VDP_STATUS_OK) { if (status != VDP_STATUS_OK) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"VdpVideoMixerRender() failed: %s", "VdpVideoMixerRender() failed: %s",

View file

@ -19,7 +19,7 @@ public:
int height, int height,
int maxFps); int maxFps);
virtual bool prepareDecoderContext(AVCodecContext* context); virtual bool prepareDecoderContext(AVCodecContext* context);
virtual void renderFrame(AVFrame* frame); virtual void renderFrameAtVsync(AVFrame* frame);
private: private:
uint32_t m_VideoWidth, m_VideoHeight; uint32_t m_VideoWidth, m_VideoHeight;

View file

@ -13,22 +13,19 @@
#import <VideoToolbox/VideoToolbox.h> #import <VideoToolbox/VideoToolbox.h>
#import <AVFoundation/AVFoundation.h> #import <AVFoundation/AVFoundation.h>
class VTRenderer : public IFFmpegRenderer, public IVsyncRenderer class VTRenderer : public IFFmpegRenderer
{ {
public: public:
VTRenderer() VTRenderer()
: m_HwContext(nullptr), : m_HwContext(nullptr),
m_DisplayLayer(nullptr), m_DisplayLayer(nullptr),
m_FormatDesc(nullptr), m_FormatDesc(nullptr),
m_View(nullptr), m_View(nullptr)
m_Pacer(this)
{ {
} }
virtual ~VTRenderer() virtual ~VTRenderer()
{ {
m_Pacer.drain();
if (m_HwContext != nullptr) { if (m_HwContext != nullptr) {
av_buffer_unref(&m_HwContext); av_buffer_unref(&m_HwContext);
} }
@ -48,6 +45,13 @@ public:
OSStatus status; OSStatus status;
CVPixelBufferRef pixBuf = reinterpret_cast<CVPixelBufferRef>(frame->data[3]); CVPixelBufferRef pixBuf = reinterpret_cast<CVPixelBufferRef>(frame->data[3]);
// FIXME: Only on main thread
if (m_DisplayLayer.status == AVQueuedSampleBufferRenderingStatusFailed) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Resetting failed AVSampleBufferDisplay layer");
setupDisplayLayer();
}
// If the format has changed or doesn't exist yet, construct it with the // If the format has changed or doesn't exist yet, construct it with the
// pixel buffer data // pixel buffer data
if (!m_FormatDesc || !CMVideoFormatDescriptionMatchesImageBuffer(m_FormatDesc, pixBuf)) { if (!m_FormatDesc || !CMVideoFormatDescriptionMatchesImageBuffer(m_FormatDesc, pixBuf)) {
@ -97,10 +101,6 @@ public:
{ {
int err; int err;
if (!m_Pacer.initialize(window, maxFps)) {
return false;
}
if (videoFormat & VIDEO_FORMAT_MASK_H264) { if (videoFormat & VIDEO_FORMAT_MASK_H264) {
// Prior to 10.13, we'll just assume everything has // Prior to 10.13, we'll just assume everything has
// H.264 support and fail open to allow VT decode. // H.264 support and fail open to allow VT decode.
@ -185,17 +185,6 @@ public:
return true; return true;
} }
virtual void renderFrame(AVFrame* frame) override
{
if (m_DisplayLayer.status == AVQueuedSampleBufferRenderingStatusFailed) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Resetting failed AVSampleBufferDisplay layer");
setupDisplayLayer();
}
m_Pacer.submitFrame(frame);
}
private: private:
void setupDisplayLayer() void setupDisplayLayer()
{ {
@ -219,7 +208,6 @@ private:
AVSampleBufferDisplayLayer* m_DisplayLayer; AVSampleBufferDisplayLayer* m_DisplayLayer;
CMVideoFormatDescriptionRef m_FormatDesc; CMVideoFormatDescriptionRef m_FormatDesc;
NSView* m_View; NSView* m_View;
Pacer m_Pacer;
}; };
IFFmpegRenderer* VTRendererFactory::createRenderer() { IFFmpegRenderer* VTRendererFactory::createRenderer() {

View file

@ -53,7 +53,8 @@ FFmpegVideoDecoder::FFmpegVideoDecoder()
m_DecodeBuffer(1024 * 1024, 0), m_DecodeBuffer(1024 * 1024, 0),
m_HwDecodeCfg(nullptr), m_HwDecodeCfg(nullptr),
m_Renderer(nullptr), m_Renderer(nullptr),
m_ConsecutiveFailedDecodes(0) m_ConsecutiveFailedDecodes(0),
m_Pacer(nullptr)
{ {
av_init_packet(&m_Pkt); av_init_packet(&m_Pkt);
SDL_AtomicSet(&m_QueuedFrames, 0); SDL_AtomicSet(&m_QueuedFrames, 0);
@ -91,19 +92,29 @@ void FFmpegVideoDecoder::reset()
dropFrame(&event.user); dropFrame(&event.user);
} }
else { else {
SDL_Delay(100); SDL_Delay(10);
SDL_PumpEvents(); SDL_PumpEvents();
} }
} }
delete m_Pacer;
m_Pacer = nullptr;
delete m_Renderer; delete m_Renderer;
m_Renderer = nullptr; m_Renderer = nullptr;
avcodec_free_context(&m_VideoDecoderCtx); avcodec_free_context(&m_VideoDecoderCtx);
} }
bool FFmpegVideoDecoder::completeInitialization(AVCodec* decoder, int videoFormat, int width, int height, bool testOnly) bool FFmpegVideoDecoder::completeInitialization(AVCodec* decoder, SDL_Window* window,
int videoFormat, int width, int height,
int maxFps, bool testOnly)
{ {
m_Pacer = new Pacer(m_Renderer);
if (!m_Pacer->initialize(window, maxFps)) {
return false;
}
m_VideoDecoderCtx = avcodec_alloc_context3(decoder); m_VideoDecoderCtx = avcodec_alloc_context3(decoder);
if (!m_VideoDecoderCtx) { if (!m_VideoDecoderCtx) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
@ -254,7 +265,7 @@ bool FFmpegVideoDecoder::initialize(
m_Renderer = new SdlRenderer(); m_Renderer = new SdlRenderer();
if (vds != StreamingPreferences::VDS_FORCE_HARDWARE && if (vds != StreamingPreferences::VDS_FORCE_HARDWARE &&
m_Renderer->initialize(window, videoFormat, width, height, maxFps) && m_Renderer->initialize(window, videoFormat, width, height, maxFps) &&
completeInitialization(decoder, videoFormat, width, height, false)) { completeInitialization(decoder, window, videoFormat, width, height, maxFps, false)) {
return true; return true;
} }
else { else {
@ -271,12 +282,12 @@ bool FFmpegVideoDecoder::initialize(
m_HwDecodeCfg = config; m_HwDecodeCfg = config;
// Submit test frame to ensure this codec really works // Submit test frame to ensure this codec really works
if (m_Renderer->initialize(window, videoFormat, width, height, maxFps) && if (m_Renderer->initialize(window, videoFormat, width, height, maxFps) &&
completeInitialization(decoder, videoFormat, width, height, true)) { completeInitialization(decoder, window, videoFormat, width, height, maxFps, true)) {
// OK, it worked, so now let's initialize it for real // OK, it worked, so now let's initialize it for real
reset(); reset();
if ((m_Renderer = createAcceleratedRenderer(config)) != nullptr && if ((m_Renderer = createAcceleratedRenderer(config)) != nullptr &&
m_Renderer->initialize(window, videoFormat, width, height, maxFps) && m_Renderer->initialize(window, videoFormat, width, height, maxFps) &&
completeInitialization(decoder, videoFormat, width, height, false)) { completeInitialization(decoder, window, videoFormat, width, height, maxFps, false)) {
return true; return true;
} }
else { else {
@ -380,7 +391,7 @@ int FFmpegVideoDecoder::submitDecodeUnit(PDECODE_UNIT du)
void FFmpegVideoDecoder::renderFrame(SDL_UserEvent* event) void FFmpegVideoDecoder::renderFrame(SDL_UserEvent* event)
{ {
AVFrame* frame = reinterpret_cast<AVFrame*>(event->data1); AVFrame* frame = reinterpret_cast<AVFrame*>(event->data1);
m_Renderer->renderFrame(frame); m_Pacer->submitFrame(frame);
SDL_AtomicDecRef(&m_QueuedFrames); SDL_AtomicDecRef(&m_QueuedFrames);
} }
@ -388,6 +399,9 @@ void FFmpegVideoDecoder::renderFrame(SDL_UserEvent* event)
void FFmpegVideoDecoder::dropFrame(SDL_UserEvent* event) void FFmpegVideoDecoder::dropFrame(SDL_UserEvent* event)
{ {
AVFrame* frame = reinterpret_cast<AVFrame*>(event->data1); AVFrame* frame = reinterpret_cast<AVFrame*>(event->data1);
// We should really call Pacer::submitFrame() here and let it
// take care of it, but that will regress frame dropping for
// clients without an IVsyncSource implementation.
av_frame_free(&frame); av_frame_free(&frame);
SDL_AtomicDecRef(&m_QueuedFrames); SDL_AtomicDecRef(&m_QueuedFrames);
} }

View file

@ -2,6 +2,7 @@
#include "decoder.h" #include "decoder.h"
#include "ffmpeg-renderers/renderer.h" #include "ffmpeg-renderers/renderer.h"
#include "ffmpeg-renderers/pacer/pacer.h"
extern "C" { extern "C" {
#include <libavcodec/avcodec.h> #include <libavcodec/avcodec.h>
@ -25,8 +26,9 @@ public:
virtual IFFmpegRenderer* getRenderer(); virtual IFFmpegRenderer* getRenderer();
private: private:
bool completeInitialization(AVCodec* decoder, int videoFormat, bool completeInitialization(AVCodec* decoder, SDL_Window* window,
int width, int height, bool testOnly); int videoFormat, int width, int height,
int maxFps, bool testOnly);
IFFmpegRenderer* createAcceleratedRenderer(const AVCodecHWConfig* hwDecodeCfg); IFFmpegRenderer* createAcceleratedRenderer(const AVCodecHWConfig* hwDecodeCfg);
@ -43,6 +45,7 @@ private:
IFFmpegRenderer* m_Renderer; IFFmpegRenderer* m_Renderer;
SDL_atomic_t m_QueuedFrames; SDL_atomic_t m_QueuedFrames;
int m_ConsecutiveFailedDecodes; int m_ConsecutiveFailedDecodes;
Pacer* m_Pacer;
static const uint8_t k_H264TestFrame[]; static const uint8_t k_H264TestFrame[];
static const uint8_t k_HEVCTestFrame[]; static const uint8_t k_HEVCTestFrame[];