diff --git a/app/streaming/video/ffmpeg-renderers/eglvid.cpp b/app/streaming/video/ffmpeg-renderers/eglvid.cpp index cf5c1d55..b40245f6 100644 --- a/app/streaming/video/ffmpeg-renderers/eglvid.cpp +++ b/app/streaming/video/ffmpeg-renderers/eglvid.cpp @@ -836,3 +836,21 @@ void EGLRenderer::renderFrame(AVFrame* frame) av_frame_unref(m_LastFrame); av_frame_move_ref(m_LastFrame, frame); } + +bool EGLRenderer::testRenderFrame(AVFrame* frame) +{ + EGLImage imgs[EGL_MAX_PLANES]; + + // Make sure we can get working EGLImages from the backend renderer. + // Some devices (Raspberry Pi) will happily decode into DRM formats that + // its own GL implementation won't accept in eglCreateImage(). + ssize_t plane_count = m_Backend->exportEGLImages(frame, m_EGLDisplay, imgs); + if (plane_count <= 0) { + SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, + "Backend failed to export EGL image for test frame"); + return false; + } + + m_Backend->freeEGLImages(m_EGLDisplay, imgs); + return true; +} diff --git a/app/streaming/video/ffmpeg-renderers/eglvid.h b/app/streaming/video/ffmpeg-renderers/eglvid.h index 2dbcc175..336e8fac 100644 --- a/app/streaming/video/ffmpeg-renderers/eglvid.h +++ b/app/streaming/video/ffmpeg-renderers/eglvid.h @@ -12,6 +12,7 @@ public: virtual bool initialize(PDECODER_PARAMETERS params) override; virtual bool prepareDecoderContext(AVCodecContext* context, AVDictionary** options) override; virtual void renderFrame(AVFrame* frame) override; + virtual bool testRenderFrame(AVFrame* frame) override; virtual void notifyOverlayUpdated(Overlay::OverlayType) override; virtual bool isPixelFormatSupported(int videoFormat, enum AVPixelFormat pixelFormat) override; virtual AVPixelFormat getPreferredPixelFormat(int videoFormat) override; diff --git a/app/streaming/video/ffmpeg-renderers/renderer.h b/app/streaming/video/ffmpeg-renderers/renderer.h index 08f496c7..ddca40c9 100644 --- a/app/streaming/video/ffmpeg-renderers/renderer.h +++ b/app/streaming/video/ffmpeg-renderers/renderer.h @@ -86,6 +86,13 @@ public: virtual bool prepareDecoderContext(AVCodecContext* context, AVDictionary** options) = 0; virtual void renderFrame(AVFrame* frame) = 0; + virtual bool testRenderFrame(AVFrame*) { + // If the renderer doesn't provide an explicit test routine, + // we will always assume that any returned AVFrame can be + // rendered successfully. + return true; + } + virtual bool needsTestFrame() { // No test frame required by default return false; diff --git a/app/streaming/video/ffmpeg.cpp b/app/streaming/video/ffmpeg.cpp index f7d4c6b2..15688899 100644 --- a/app/streaming/video/ffmpeg.cpp +++ b/app/streaming/video/ffmpeg.cpp @@ -193,17 +193,22 @@ void FFmpegVideoDecoder::reset() } } -bool FFmpegVideoDecoder::createFrontendRenderer(PDECODER_PARAMETERS params) +bool FFmpegVideoDecoder::createFrontendRenderer(PDECODER_PARAMETERS params, bool eglOnly) { + if (eglOnly) { #ifdef HAVE_EGL - if (m_BackendRenderer->canExportEGL()) { - m_FrontendRenderer = new EGLRenderer(m_BackendRenderer); - if (m_FrontendRenderer->initialize(params)) { - return true; + if (m_BackendRenderer->canExportEGL()) { + m_FrontendRenderer = new EGLRenderer(m_BackendRenderer); + if (m_FrontendRenderer->initialize(params)) { + return true; + } + delete m_FrontendRenderer; + m_FrontendRenderer = nullptr; } - delete m_FrontendRenderer; - } #endif + // If we made it here, we failed to create the EGLRenderer + return false; + } if (m_BackendRenderer->isDirectRenderingSupported()) { // The backend renderer can render to the display @@ -221,13 +226,13 @@ bool FFmpegVideoDecoder::createFrontendRenderer(PDECODER_PARAMETERS params) return true; } -bool FFmpegVideoDecoder::completeInitialization(AVCodec* decoder, PDECODER_PARAMETERS params, bool testFrame) +bool FFmpegVideoDecoder::completeInitialization(AVCodec* decoder, PDECODER_PARAMETERS params, bool testFrame, bool eglOnly) { // In test-only mode, we should only see test frames SDL_assert(!m_TestOnly || testFrame); // Create the frontend renderer based on the capabilities of the backend renderer - if (!createFrontendRenderer(params)) { + if (!createFrontendRenderer(params, eglOnly)) { return false; } @@ -365,6 +370,14 @@ bool FFmpegVideoDecoder::completeInitialization(AVCodec* decoder, PDECODER_PARAM } } + // Allow the renderer to do any validation it wants on this frame + if (!m_FrontendRenderer->testRenderFrame(frame)) { + SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, + "Render test failed"); + av_frame_free(&frame); + return false; + } + av_frame_free(&frame); if (err < 0) { char errorstring[512]; @@ -555,42 +568,48 @@ bool FFmpegVideoDecoder::tryInitializeRenderer(AVCodec* decoder, const AVCodecHWConfig* hwConfig, std::function createRendererFunc) { - m_BackendRenderer = createRendererFunc(); m_HwDecodeCfg = hwConfig; - if (m_BackendRenderer != nullptr && - m_BackendRenderer->initialize(params) && - completeInitialization(decoder, params, m_TestOnly || m_BackendRenderer->needsTestFrame())) { - if (m_TestOnly) { - // This decoder is only for testing capabilities, so don't bother - // creating a usable renderer - return true; - } - - if (m_BackendRenderer->needsTestFrame()) { - // The test worked, so now let's initialize it for real - reset(); - if ((m_BackendRenderer = createRendererFunc()) != nullptr && - m_BackendRenderer->initialize(params) && - completeInitialization(decoder, params, false)) { + // i == 0 - Indirect via EGL frontend with zero-copy DMA-BUF passing + // i == 1 - Direct rendering or indirect via SDL read-back + for (int i = 0; i < 2; i++) { + SDL_assert(m_BackendRenderer == nullptr); + if ((m_BackendRenderer = createRendererFunc()) != nullptr && + m_BackendRenderer->initialize(params) && + completeInitialization(decoder, params, m_TestOnly || m_BackendRenderer->needsTestFrame(), i == 0 /* EGL */)) { + if (m_TestOnly) { + // This decoder is only for testing capabilities, so don't bother + // creating a usable renderer return true; } - else { - SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, - "Decoder failed to initialize after successful test"); + + if (m_BackendRenderer->needsTestFrame()) { + // The test worked, so now let's initialize it for real reset(); + if ((m_BackendRenderer = createRendererFunc()) != nullptr && + m_BackendRenderer->initialize(params) && + completeInitialization(decoder, params, false, i == 0 /* EGL */)) { + return true; + } + else { + SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, + "Decoder failed to initialize after successful test"); + reset(); + } + } + else { + // No test required. Good to go now. + return true; } } else { - // No test required. Good to go now. - return true; + // Failed to initialize, so keep looking + reset(); } } - else { - // Failed to initialize or test frame failed, so keep looking - reset(); - } + // reset() must be called before we reach this point! + SDL_assert(m_BackendRenderer == nullptr); return false; } diff --git a/app/streaming/video/ffmpeg.h b/app/streaming/video/ffmpeg.h index b579e6e0..734a6e8e 100644 --- a/app/streaming/video/ffmpeg.h +++ b/app/streaming/video/ffmpeg.h @@ -26,7 +26,7 @@ public: virtual IFFmpegRenderer* getBackendRenderer(); private: - bool completeInitialization(AVCodec* decoder, PDECODER_PARAMETERS params, bool testFrame); + bool completeInitialization(AVCodec* decoder, PDECODER_PARAMETERS params, bool testFrame, bool eglOnly); void stringifyVideoStats(VIDEO_STATS& stats, char* output); @@ -34,7 +34,7 @@ private: void addVideoStats(VIDEO_STATS& src, VIDEO_STATS& dst); - bool createFrontendRenderer(PDECODER_PARAMETERS params); + bool createFrontendRenderer(PDECODER_PARAMETERS params, bool eglOnly); bool tryInitializeRendererForDecoderByName(const char* decoderName, PDECODER_PARAMETERS params);