From dba479774b113271e2e252a48b8619f508211e20 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sun, 21 Apr 2019 05:22:37 +0000 Subject: [PATCH] Add DRM renderer for Rockchip devices --- app/app.pro | 12 + app/streaming/video/ffmpeg-renderers/drm.cpp | 268 +++++++++++++++++++ app/streaming/video/ffmpeg-renderers/drm.h | 24 ++ app/streaming/video/ffmpeg.cpp | 22 ++ 4 files changed, 326 insertions(+) create mode 100644 app/streaming/video/ffmpeg-renderers/drm.cpp create mode 100644 app/streaming/video/ffmpeg-renderers/drm.h diff --git a/app/app.pro b/app/app.pro index 5402c4e6..6fa19f7d 100644 --- a/app/app.pro +++ b/app/app.pro @@ -99,6 +99,11 @@ unix:!macx { PKGCONFIG += mmal CONFIG += mmal } + + packagesExist(libdrm) { + PKGCONFIG += libdrm + CONFIG += libdrm + } } } win32 { @@ -225,6 +230,13 @@ mmal { SOURCES += streaming/video/ffmpeg-renderers/mmal.cpp HEADERS += streaming/video/ffmpeg-renderers/mmal.h } +libdrm { + message(DRM renderer selected) + + DEFINES += HAVE_DRM + SOURCES += streaming/video/ffmpeg-renderers/drm.cpp + HEADERS += streaming/video/ffmpeg-renderers/drm.h +} config_SL { message(Steam Link build configuration selected) diff --git a/app/streaming/video/ffmpeg-renderers/drm.cpp b/app/streaming/video/ffmpeg-renderers/drm.cpp new file mode 100644 index 00000000..39dd43bf --- /dev/null +++ b/app/streaming/video/ffmpeg-renderers/drm.cpp @@ -0,0 +1,268 @@ +#include "drm.h" + +extern "C" { + #include +} + +#include + +#include +#include + +#include "streaming/streamutils.h" +#include "streaming/session.h" + +#include + +DrmRenderer::DrmRenderer() + : m_DrmFd(-1), + m_CrtcId(0), + m_PlaneId(0), + m_CurrentFbId(0) +{ +} + +DrmRenderer::~DrmRenderer() +{ + if (m_CurrentFbId != 0) { + drmModeRmFB(m_DrmFd, m_CurrentFbId); + } + + if (m_DrmFd != -1) { + close(m_DrmFd); + } +} + +bool DrmRenderer::prepareDecoderContext(AVCodecContext*) +{ + /* Nothing to do */ + + SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, + "Using DRM renderer"); + + return true; +} + +bool DrmRenderer::initialize(PDECODER_PARAMETERS) +{ + const char* device = SDL_getenv("DRM_DEV"); + int i; + + if (device == nullptr) { + device = "/dev/dri/card0"; + } + + SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, + "Opening DRM device: %s", + device); + + m_DrmFd = open(device, O_RDWR | O_CLOEXEC); + if (m_DrmFd < 0) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "Failed to open DRM device: %d", + errno); + return false; + } + + drmModeRes* resources = drmModeGetResources(m_DrmFd); + if (resources == nullptr) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "drmModeGetResources() failed: %d", + errno); + return false; + } + + // Look for a connected connector and get the associated encoder + uint32_t encoderId = 0; + for (i = 0; i < resources->count_connectors && encoderId == 0; i++) { + drmModeConnector* connector = drmModeGetConnector(m_DrmFd, resources->connectors[i]); + if (connector != nullptr) { + if (connector->connection == DRM_MODE_CONNECTED && connector->count_modes > 0) { + encoderId = connector->encoder_id; + } + + drmModeFreeConnector(connector); + } + } + + if (encoderId == 0) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "No connected displays found!"); + drmModeFreeResources(resources); + return false; + } + + // Now find the CRTC from the encoder + m_CrtcId = 0; + for (i = 0; i < resources->count_encoders && m_CrtcId == 0; i++) { + drmModeEncoder* encoder = drmModeGetEncoder(m_DrmFd, resources->encoders[i]); + if (encoder != nullptr) { + if (encoder->encoder_id == encoderId) { + m_CrtcId = encoder->crtc_id; + } + + drmModeFreeEncoder(encoder); + } + } + + if (m_CrtcId == 0) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "DRM encoder not found!"); + drmModeFreeResources(resources); + return false; + } + + int crtcIndex = -1; + for (int i = 0; i < resources->count_crtcs; i++) { + if (resources->crtcs[i] == m_CrtcId) { + drmModeCrtc* crtc = drmModeGetCrtc(m_DrmFd, resources->crtcs[i]); + crtcIndex = i; + m_OutputRect.x = m_OutputRect.y = 0; + m_OutputRect.w = crtc->width; + m_OutputRect.h = crtc->height; + drmModeFreeCrtc(crtc); + break; + } + } + + drmModeFreeResources(resources); + + if (crtcIndex == -1) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "Failed to get CRTC!"); + return false; + } + + drmSetClientCap(m_DrmFd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1); + + drmModePlaneRes* planeRes = drmModeGetPlaneResources(m_DrmFd); + if (planeRes == nullptr) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "drmGetPlaneResources() failed: %d", + errno); + return false; + } + + // Find an NV12 overlay plane to render on + m_PlaneId = 0; + for (uint32_t i = 0; i < planeRes->count_planes && m_PlaneId == 0; i++) { + drmModePlane* plane = drmModeGetPlane(m_DrmFd, planeRes->planes[i]); + if (plane != nullptr) { + bool matchingFormat = false; + for (uint32_t j = 0; j < plane->count_formats; j++) { + if (plane->formats[j] == DRM_FORMAT_NV12) { + matchingFormat = true; + break; + } + } + + if (matchingFormat == false) { + drmModeFreePlane(plane); + continue; + } + + if ((plane->possible_crtcs & (1 << crtcIndex)) && plane->crtc_id == 0) { + drmModeObjectPropertiesPtr props = drmModeObjectGetProperties(m_DrmFd, planeRes->planes[i], DRM_MODE_OBJECT_PLANE); + if (props != nullptr) { + for (uint32_t j = 0; j < props->count_props && m_PlaneId == 0; j++) { + drmModePropertyPtr prop = drmModeGetProperty(m_DrmFd, props->props[j]); + if (prop != nullptr) { + if (!strcmp(prop->name, "type") && props->prop_values[j] == DRM_PLANE_TYPE_OVERLAY) { + m_PlaneId = plane->plane_id; + } + + drmModeFreeProperty(prop); + } + } + + drmModeFreeObjectProperties(props); + } + } + + drmModeFreePlane(plane); + } + } + + drmModeFreePlaneResources(planeRes); + + return true; +} + +enum AVPixelFormat DrmRenderer::getPreferredPixelFormat(int) +{ + // DRM PRIME buffers + return AV_PIX_FMT_DRM_PRIME; +} + +void DrmRenderer::renderFrame(AVFrame* frame) +{ + AVDRMFrameDescriptor* drmFrame = (AVDRMFrameDescriptor*)frame->data[0]; + int err; + uint32_t primeHandle; + uint32_t handles[4] = {}; + uint32_t pitches[4] = {}; + uint32_t offsets[4] = {}; + + SDL_Rect src, dst; + + src.x = src.y = 0; + src.w = frame->width; + src.h = frame->height; + dst = m_OutputRect; + + StreamUtils::scaleSourceToDestinationSurface(&src, &dst); + + // Convert the FD in the AVDRMFrameDescriptor to a PRIME handle + // that can be used in drmModeAddFB2() + SDL_assert(drmFrame->nb_objects == 1); + err = drmPrimeFDToHandle(m_DrmFd, drmFrame->objects[0].fd, &primeHandle); + if (err < 0) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "drmPrimeFDToHandle() failed: %d", + errno); + return; + } + + SDL_assert(drmFrame->nb_layers == 1); + SDL_assert(drmFrame->layers[0].nb_planes == 2); + for (int i = 0; i < drmFrame->layers[0].nb_planes; i++) { + handles[i] = primeHandle; + pitches[i] = drmFrame->layers[0].planes[i].pitch; + offsets[i] = drmFrame->layers[0].planes[i].offset; + } + + // Remember the last FB object we created so we can free it + // when we are finished rendering this one (if successful). + uint32_t lastFbId = m_CurrentFbId; + + // Create a frame buffer object from the PRIME buffer + err = drmModeAddFB2(m_DrmFd, frame->width, frame->height, + drmFrame->layers[0].format, + handles, pitches, offsets, &m_CurrentFbId, 0); + if (err < 0) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "drmModeAddFB2() failed: %d", + errno); + m_CurrentFbId = lastFbId; + return; + } + + // Update the overlay + err = drmModeSetPlane(m_DrmFd, m_PlaneId, m_CrtcId, m_CurrentFbId, 0, + dst.x, dst.y, + dst.w, dst.h, + 0, 0, + frame->width << 16, + frame->height << 16); + if (err < 0) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "drmModeSetPlane() failed: %d", + errno); + drmModeRmFB(m_DrmFd, m_CurrentFbId); + m_CurrentFbId = lastFbId; + return; + } + + // Free the previous FB object which has now been superseded + drmModeRmFB(m_DrmFd, lastFbId); +} diff --git a/app/streaming/video/ffmpeg-renderers/drm.h b/app/streaming/video/ffmpeg-renderers/drm.h new file mode 100644 index 00000000..97915d6a --- /dev/null +++ b/app/streaming/video/ffmpeg-renderers/drm.h @@ -0,0 +1,24 @@ +#pragma once + +#include "renderer.h" + +#include +#include + +class DrmRenderer : public IFFmpegRenderer { +public: + DrmRenderer(); + virtual ~DrmRenderer() override; + virtual bool initialize(PDECODER_PARAMETERS params) override; + virtual bool prepareDecoderContext(AVCodecContext* context) override; + virtual void renderFrame(AVFrame* frame) override; + virtual enum AVPixelFormat getPreferredPixelFormat(int videoFormat) override; + +private: + int m_DrmFd; + uint32_t m_CrtcId; + uint32_t m_PlaneId; + uint32_t m_CurrentFbId; + SDL_Rect m_OutputRect; +}; + diff --git a/app/streaming/video/ffmpeg.cpp b/app/streaming/video/ffmpeg.cpp index 2c497c87..436a40d1 100644 --- a/app/streaming/video/ffmpeg.cpp +++ b/app/streaming/video/ffmpeg.cpp @@ -27,6 +27,10 @@ #include "ffmpeg-renderers/mmal.h" #endif +#ifdef HAVE_DRM +#include "ffmpeg-renderers/drm.h" +#endif + // This is gross but it allows us to use sizeof() #include "ffmpeg_videosamples.cpp" @@ -493,6 +497,24 @@ bool FFmpegVideoDecoder::initialize(PDECODER_PARAMETERS params) } } #endif + +#ifdef HAVE_DRM + // RKMPP is a hardware accelerated decoder that outputs DRI PRIME buffers + AVCodec* rkmppDecoder; + + if (params->videoFormat & VIDEO_FORMAT_MASK_H264) { + rkmppDecoder = avcodec_find_decoder_by_name("h264_rkmpp"); + } + else { + rkmppDecoder = avcodec_find_decoder_by_name("hevc_rkmpp"); + } + + if (rkmppDecoder != nullptr && + tryInitializeRenderer(rkmppDecoder, params, nullptr, + []() -> IFFmpegRenderer* { return new DrmRenderer(); })) { + return true; + } +#endif } // Fallback to software if no matching hardware decoder was found