diff --git a/.travis.yml b/.travis.yml index 203ba3d9..3c52e44f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,6 +15,7 @@ matrix: apt: sources: - sourceline: 'ppa:beineri/opt-qt596-xenial' + - sourceline: 'ppa:jonathonf/ffmpeg-4' packages: - qt59base - qt59quickcontrols2 @@ -22,6 +23,9 @@ matrix: - libesd0-dev - libgl-dev - libopus-dev + - ffmpeg + - libva-dev + - libvdpau-dev install: - '[ "$TRAVIS_OS_NAME" != osx ] || brew install qt5' diff --git a/app/app.pro b/app/app.pro index e6f48b66..f524de18 100644 --- a/app/app.pro +++ b/app/app.pro @@ -53,6 +53,10 @@ unix:!macx { packagesExist(libva) { CONFIG += libva } + + packagesExist(vdpau) { + CONFIG += libvdpau + } } } win32 { @@ -112,6 +116,13 @@ libva { SOURCES += streaming/video/ffmpeg-renderers/vaapi.cpp HEADERS += streaming/video/ffmpeg-renderers/vaapi.h } +libvdpau { + message(VDPAU renderer selected) + + DEFINES += HAVE_LIBVDPAU + SOURCES += streaming/video/ffmpeg-renderers/vdpau.cpp + HEADERS += streaming/video/ffmpeg-renderers/vdpau.h +} config_SLVideo { message(SLVideo decoder/renderer selected) diff --git a/app/streaming/session.cpp b/app/streaming/session.cpp index 402f5d2b..ebc407f2 100644 --- a/app/streaming/session.cpp +++ b/app/streaming/session.cpp @@ -138,6 +138,10 @@ bool Session::chooseDecoder(StreamingPreferences::VideoDecoderSelection vds, } #endif +#if !defined(HAVE_FFMPEG) && !defined(HAVE_SLVIDEO) +#error No video decoding libraries available! +#endif + // If we reach this, we didn't initialize any decoders successfully return false; } diff --git a/app/streaming/video/ffmpeg-renderers/vaapi.cpp b/app/streaming/video/ffmpeg-renderers/vaapi.cpp index 7e566bf0..c985fe65 100644 --- a/app/streaming/video/ffmpeg-renderers/vaapi.cpp +++ b/app/streaming/video/ffmpeg-renderers/vaapi.cpp @@ -70,7 +70,6 @@ VAAPIRenderer::initialize(SDL_Window* window, int, int width, int height) return false; } - err = av_hwdevice_ctx_create(&m_HwContext, AV_HWDEVICE_TYPE_VAAPI, nullptr, nullptr, 0); @@ -81,6 +80,14 @@ VAAPIRenderer::initialize(SDL_Window* window, int, int width, int height) return false; } + // This quirk is set for the VDPAU wrapper which doesn't work with our VAAPI renderer + if (((AVVAAPIDeviceContext*)((AVHWDeviceContext*)(m_HwContext->data))->hwctx)->driver_quirks & AV_VAAPI_DRIVER_QUIRK_SURFACE_ATTRIBUTES) { + // Fail and let our VDPAU renderer pick this up + SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, + "Avoiding VDPAU wrapper for VAAPI decoding"); + return false; + } + return true; } diff --git a/app/streaming/video/ffmpeg-renderers/vaapi.h b/app/streaming/video/ffmpeg-renderers/vaapi.h index b689967e..ab01a5cf 100644 --- a/app/streaming/video/ffmpeg-renderers/vaapi.h +++ b/app/streaming/video/ffmpeg-renderers/vaapi.h @@ -38,10 +38,6 @@ public: virtual void renderFrame(AVFrame* frame); private: - static - enum AVPixelFormat ffGetFormat(AVCodecContext*, - const enum AVPixelFormat* pixFmts); - Window m_XWindow; AVBufferRef* m_HwContext; void* m_X11VaLibHandle; @@ -51,4 +47,3 @@ private: int m_DisplayWidth; int m_DisplayHeight; }; - diff --git a/app/streaming/video/ffmpeg-renderers/vdpau.cpp b/app/streaming/video/ffmpeg-renderers/vdpau.cpp new file mode 100644 index 00000000..e59ff119 --- /dev/null +++ b/app/streaming/video/ffmpeg-renderers/vdpau.cpp @@ -0,0 +1,292 @@ +#include "vdpau.h" + +#include + +#define BAIL_ON_FAIL(status, something) if ((status) != VDP_STATUS_OK) { \ + SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, \ + #something " failed: %d", (status)); \ + return false; \ + } + +#define GET_PROC_ADDRESS(id, func) status = vdpauCtx->get_proc_address(vdpauCtx->device, id, (void**)func); \ + BAIL_ON_FAIL(status, id) + +const VdpRGBAFormat VDPAURenderer::k_OutputFormats[] = { + VDP_RGBA_FORMAT_B8G8R8A8, + VDP_RGBA_FORMAT_R8G8B8A8 +}; + +VDPAURenderer::VDPAURenderer() + : m_HwContext(nullptr), + m_PresentationQueueTarget(0), + m_PresentationQueue(0), + m_VideoMixer(0), + m_NextSurfaceIndex(0) +{ + SDL_zero(m_OutputSurface); +} + +VDPAURenderer::~VDPAURenderer() +{ + for (int i = 0; i < OUTPUT_SURFACE_COUNT; i++) { + if (m_OutputSurface[i] != 0) { + m_VdpOutputSurfaceDestroy(m_OutputSurface[i]); + } + } + + if (m_VideoMixer != 0) { + m_VdpVideoMixerDestroy(m_VideoMixer); + } + + if (m_PresentationQueue != 0) { + m_VdpPresentationQueueDestroy(m_PresentationQueue); + } + + if (m_PresentationQueueTarget != 0) { + m_VdpPresentationQueueTargetDestroy(m_PresentationQueueTarget); + } + + // This must be done last as it frees VDPAU context required to call + // the functions above. + if (m_HwContext != nullptr) { + av_buffer_unref(&m_HwContext); + } +} + +bool VDPAURenderer::initialize(SDL_Window* window, int, int width, int height) +{ + int err; + VdpStatus status; + SDL_SysWMinfo info; + + m_VideoWidth = width; + m_VideoHeight = height; + + err = av_hwdevice_ctx_create(&m_HwContext, + AV_HWDEVICE_TYPE_VDPAU, + nullptr, nullptr, 0); + if (err < 0) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "Failed to create VDPAU context: %d", + err); + return false; + } + + AVHWDeviceContext* devCtx = (AVHWDeviceContext*)m_HwContext->data; + AVVDPAUDeviceContext* vdpauCtx = (AVVDPAUDeviceContext*)devCtx->hwctx; + + GET_PROC_ADDRESS(VDP_FUNC_ID_GET_ERROR_STRING, &m_VdpGetErrorString); + GET_PROC_ADDRESS(VDP_FUNC_ID_PRESENTATION_QUEUE_TARGET_DESTROY, &m_VdpPresentationQueueTargetDestroy); + GET_PROC_ADDRESS(VDP_FUNC_ID_VIDEO_MIXER_CREATE, &m_VdpVideoMixerCreate); + GET_PROC_ADDRESS(VDP_FUNC_ID_VIDEO_MIXER_DESTROY, &m_VdpVideoMixerDestroy); + GET_PROC_ADDRESS(VDP_FUNC_ID_VIDEO_MIXER_RENDER, &m_VdpVideoMixerRender); + GET_PROC_ADDRESS(VDP_FUNC_ID_PRESENTATION_QUEUE_CREATE, &m_VdpPresentationQueueCreate); + GET_PROC_ADDRESS(VDP_FUNC_ID_PRESENTATION_QUEUE_DESTROY, &m_VdpPresentationQueueDestroy); + GET_PROC_ADDRESS(VDP_FUNC_ID_PRESENTATION_QUEUE_DISPLAY, &m_VdpPresentationQueueDisplay); + GET_PROC_ADDRESS(VDP_FUNC_ID_PRESENTATION_QUEUE_SET_BACKGROUND_COLOR, &m_VdpPresentationQueueSetBackgroundColor); + GET_PROC_ADDRESS(VDP_FUNC_ID_PRESENTATION_QUEUE_BLOCK_UNTIL_SURFACE_IDLE, &m_VdpPresentationQueueBlockUntilSurfaceIdle); + GET_PROC_ADDRESS(VDP_FUNC_ID_OUTPUT_SURFACE_CREATE, &m_VdpOutputSurfaceCreate); + GET_PROC_ADDRESS(VDP_FUNC_ID_OUTPUT_SURFACE_DESTROY, &m_VdpOutputSurfaceDestroy); + GET_PROC_ADDRESS(VDP_FUNC_ID_OUTPUT_SURFACE_QUERY_CAPABILITIES, &m_VdpOutputSurfaceQueryCapabilities); + + SDL_GetWindowSize(window, (int*)&m_DisplayWidth, (int*)&m_DisplayHeight); + + SDL_VERSION(&info.version); + + if (!SDL_GetWindowWMInfo(window, &info)) { + SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, + "SDL_GetWindowWMInfo() failed: %s", + SDL_GetError()); + return false; + } + + SDL_assert(info.subsystem == SDL_SYSWM_X11); + + if (info.subsystem == SDL_SYSWM_X11) { + GET_PROC_ADDRESS(VDP_FUNC_ID_PRESENTATION_QUEUE_TARGET_CREATE_X11, + &m_VdpPresentationQueueTargetCreateX11); + status = m_VdpPresentationQueueTargetCreateX11(vdpauCtx->device, + info.info.x11.window, + &m_PresentationQueueTarget); + if (status != VDP_STATUS_OK) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "VdpPresentationQueueTargetCreateX11() failed: %s", + m_VdpGetErrorString(status)); + return false; + } + } + else if (info.subsystem == SDL_SYSWM_WAYLAND) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "VDPAU backend does not currently support Wayland"); + return false; + } + else { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "Unsupported VDPAU rendering subsystem: %d", + info.subsystem); + return false; + } + + // Try our available output formats to find something the GPU supports + bool foundFormat = false; + for (int i = 0; i < OUTPUT_SURFACE_FORMAT_COUNT; i++) { + VdpBool supported; + uint32_t maxWidth, maxHeight; + status = m_VdpOutputSurfaceQueryCapabilities(vdpauCtx->device, k_OutputFormats[i], + &supported, &maxWidth, &maxHeight); + if (status != VDP_STATUS_OK) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "VdpOutputSurfaceQueryCapabilities() failed: %s", + m_VdpGetErrorString(status)); + return false; + } + + if (supported) { + if (m_DisplayWidth <= maxWidth && m_DisplayHeight <= maxHeight) { + m_OutputSurfaceFormat = k_OutputFormats[i]; + foundFormat = true; + break; + } + else { + SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, + "Display size not within capabilities %dx%d vs %dx%d", + m_DisplayWidth, m_DisplayWidth, + maxWidth, maxHeight); + } + } + } + + if (!foundFormat) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "No compatible output surface format found!"); + return false; + } + + // Create the output surfaces + for (int i = 0; i < OUTPUT_SURFACE_COUNT; i++) { + status = m_VdpOutputSurfaceCreate(vdpauCtx->device, m_OutputSurfaceFormat, + m_DisplayWidth, m_DisplayHeight, + &m_OutputSurface[i]); + if (status != VDP_STATUS_OK) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "VdpOutputSurfaceCreate() failed: %s", + m_VdpGetErrorString(status)); + return false; + } + } + + status = m_VdpPresentationQueueCreate(vdpauCtx->device, m_PresentationQueueTarget, + &m_PresentationQueue); + if (status != VDP_STATUS_OK) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "VdpPresentationQueueCreate() failed: %s", + m_VdpGetErrorString(status)); + return false; + } + + // Set the background to black + VdpColor color; + SDL_zero(color); + m_VdpPresentationQueueSetBackgroundColor(m_PresentationQueue, &color); + +#define PARAM_COUNT 2 + const VdpVideoMixerParameter params[PARAM_COUNT] = { + VDP_VIDEO_MIXER_PARAMETER_VIDEO_SURFACE_WIDTH, + VDP_VIDEO_MIXER_PARAMETER_VIDEO_SURFACE_HEIGHT, + }; + const void* const paramValues[PARAM_COUNT] = { + &m_VideoWidth, + &m_VideoHeight, + }; + + status = m_VdpVideoMixerCreate(vdpauCtx->device, 0, nullptr, + PARAM_COUNT, params, paramValues, + &m_VideoMixer); + if (status != VDP_STATUS_OK) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "VdpVideoMixerCreate() failed: %s", + m_VdpGetErrorString(status)); + return false; + } + + return true; +} + +bool VDPAURenderer::prepareDecoderContext(AVCodecContext* context) +{ + context->hw_device_ctx = av_buffer_ref(m_HwContext); + + SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, + "Using VDPAU accelerated renderer"); + + return true; +} + +void VDPAURenderer::renderFrame(AVFrame* frame) +{ + VdpStatus status; + VdpVideoSurface videoSurface = (VdpVideoSurface)(uintptr_t)frame->data[3]; + + // This is safe without locking because this is always called on the main thread + VdpOutputSurface chosenSurface = m_OutputSurface[m_NextSurfaceIndex]; + m_NextSurfaceIndex = (m_NextSurfaceIndex + 1) % OUTPUT_SURFACE_COUNT; + + // Wait for this frame to be off the screen + VdpTime pts; + m_VdpPresentationQueueBlockUntilSurfaceIdle(m_PresentationQueue, chosenSurface, &pts); + + VdpRect outputRect; + + // Center in frame and preserve aspect ratio + double srcAspectRatio = (double)m_VideoWidth / (double)m_VideoHeight; + double dstAspectRatio = (double)m_DisplayWidth / (double)m_DisplayHeight; + if (dstAspectRatio < srcAspectRatio) { + // Greater height per width + uint32_t drawHeight = (uint32_t)(m_DisplayWidth / srcAspectRatio); + outputRect.y0 = (m_DisplayHeight - drawHeight) / 2; + outputRect.y1 = outputRect.y0 + drawHeight; + outputRect.x0 = 0; + outputRect.x1 = outputRect.x0 + m_DisplayWidth; + } + else { + // Greater width per height + uint32_t drawWidth = (uint32_t)(m_DisplayHeight * srcAspectRatio); + outputRect.y0 = 0; + outputRect.y1 = outputRect.y0 + m_DisplayHeight; + outputRect.x0 = (m_DisplayWidth - drawWidth) / 2; + outputRect.x1 = outputRect.x0 + drawWidth; + } + + // Render the next frame into the output surface + status = m_VdpVideoMixerRender(m_VideoMixer, + VDP_INVALID_HANDLE, nullptr, + VDP_VIDEO_MIXER_PICTURE_STRUCTURE_FRAME, + 0, nullptr, + videoSurface, + 0, nullptr, + nullptr, + chosenSurface, + &outputRect, + nullptr, + 0, + nullptr); + + // The decoder can have this surface back now + av_frame_free(&frame); + + if (status != VDP_STATUS_OK) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "VdpVideoMixerRender() failed: %s", + m_VdpGetErrorString(status)); + return; + } + + // Queue the frame for display immediately + status = m_VdpPresentationQueueDisplay(m_PresentationQueue, chosenSurface, 0, 0, 0); + if (status != VDP_STATUS_OK) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "VdpPresentationQueueDisplay() failed: %s", + m_VdpGetErrorString(status)); + return; + } +} diff --git a/app/streaming/video/ffmpeg-renderers/vdpau.h b/app/streaming/video/ffmpeg-renderers/vdpau.h new file mode 100644 index 00000000..1aac97fd --- /dev/null +++ b/app/streaming/video/ffmpeg-renderers/vdpau.h @@ -0,0 +1,57 @@ +#pragma once + +#include "renderer.h" + +extern "C" { +#include +#include +#include +} + +class VDPAURenderer : public IFFmpegRenderer +{ +public: + VDPAURenderer(); + virtual ~VDPAURenderer(); + virtual bool initialize(SDL_Window* window, + int videoFormat, + int width, + int height); + virtual bool prepareDecoderContext(AVCodecContext* context); + virtual void renderFrame(AVFrame* frame); + +private: + uint32_t m_VideoWidth, m_VideoHeight; + uint32_t m_DisplayWidth, m_DisplayHeight; + AVBufferRef* m_HwContext; + VdpPresentationQueueTarget m_PresentationQueueTarget; + VdpPresentationQueue m_PresentationQueue; + VdpVideoMixer m_VideoMixer; + VdpRGBAFormat m_OutputSurfaceFormat; + +#define OUTPUT_SURFACE_COUNT 3 + VdpOutputSurface m_OutputSurface[OUTPUT_SURFACE_COUNT]; + int m_NextSurfaceIndex; + +#define OUTPUT_SURFACE_FORMAT_COUNT 2 + static const VdpRGBAFormat k_OutputFormats[OUTPUT_SURFACE_FORMAT_COUNT]; + + VdpGetErrorString* m_VdpGetErrorString; + VdpPresentationQueueTargetDestroy* m_VdpPresentationQueueTargetDestroy; + VdpVideoMixerCreate* m_VdpVideoMixerCreate; + VdpVideoMixerDestroy* m_VdpVideoMixerDestroy; + VdpVideoMixerRender* m_VdpVideoMixerRender; + VdpPresentationQueueCreate* m_VdpPresentationQueueCreate; + VdpPresentationQueueDestroy* m_VdpPresentationQueueDestroy; + VdpPresentationQueueDisplay* m_VdpPresentationQueueDisplay; + VdpPresentationQueueSetBackgroundColor* m_VdpPresentationQueueSetBackgroundColor; + VdpPresentationQueueBlockUntilSurfaceIdle* m_VdpPresentationQueueBlockUntilSurfaceIdle; + VdpOutputSurfaceCreate* m_VdpOutputSurfaceCreate; + VdpOutputSurfaceDestroy* m_VdpOutputSurfaceDestroy; + VdpOutputSurfaceQueryCapabilities* m_VdpOutputSurfaceQueryCapabilities; + + // X11 stuff + VdpPresentationQueueTargetCreateX11* m_VdpPresentationQueueTargetCreateX11; +}; + + diff --git a/app/streaming/video/ffmpeg.cpp b/app/streaming/video/ffmpeg.cpp index 0bd1ad83..22496dbc 100644 --- a/app/streaming/video/ffmpeg.cpp +++ b/app/streaming/video/ffmpeg.cpp @@ -13,6 +13,10 @@ #include "ffmpeg-renderers/vaapi.h" #endif +#ifdef HAVE_LIBVDPAU +#include "ffmpeg-renderers/vdpau.h" +#endif + // This is gross but it allows us to use sizeof() #include "ffmpeg_videosamples.cpp" @@ -195,6 +199,10 @@ IFFmpegRenderer* FFmpegVideoDecoder::createAcceleratedRenderer(const AVCodecHWCo #ifdef HAVE_LIBVA case AV_HWDEVICE_TYPE_VAAPI: return new VAAPIRenderer(); +#endif +#ifdef HAVE_LIBVDPAU + case AV_HWDEVICE_TYPE_VDPAU: + return new VDPAURenderer(); #endif default: return nullptr;