Skip to content

Commit

Permalink
V4L2 Codecs: Add DRM PRIME direct rendering support
Browse files Browse the repository at this point in the history
- tested on a Pi 3
- significant performance improvement but far from perfect and still
doesn't feel as fast as it should be.
- returned texture is RGB so limited colourspace support and no
deinterlacing (as noted however, the VC4 driver does not return
interlacing flags - also appears to be an issue on other drivers)
- the DRM PRIME code should be generic and not tied to V4L2 - it could
in theory be used to display VAAPI frames.
- configure/configuration may need a little more work with respect to
libdrm
  • Loading branch information
mark-kendall committed Nov 10, 2019
1 parent cc7572f commit 07e17d7
Show file tree
Hide file tree
Showing 11 changed files with 351 additions and 19 deletions.
8 changes: 6 additions & 2 deletions mythtv/libs/libmythtv/decoders/mythcodeccontext.cpp
Expand Up @@ -90,7 +90,7 @@ MythCodecContext *MythCodecContext::CreateContext(DecoderBase *Parent, MythCodec
mctx = new MythMediaCodecContext(Parent, Codec);
#endif
#ifdef USING_V4L2
if (codec_is_v4l2_dec(Codec))
if (codec_is_v4l2_dec(Codec) || codec_is_v4l2(Codec))
mctx = new MythV4L2M2MContext(Parent, Codec);
#endif
#ifdef USING_MMAL
Expand Down Expand Up @@ -156,6 +156,10 @@ void MythCodecContext::GetDecoders(RenderOptions &Opts)
#ifdef USING_V4L2
if (MythV4L2M2MContext::HaveV4L2Codecs())
{
#ifdef USING_V4L2PRIME
Opts.decoders->append("v4l2");
(*Opts.equiv_decoders)["v4l2"].append("dummy");
#endif
Opts.decoders->append("v4l2-dec");
(*Opts.equiv_decoders)["v4l2-dec"].append("dummy");
}
Expand Down Expand Up @@ -206,7 +210,7 @@ MythCodecID MythCodecContext::FindDecoder(const QString &Decoder, AVStream *Stre
#endif
#ifdef USING_V4L2
result = MythV4L2M2MContext::GetSupportedCodec(Context, Codec, Decoder, Stream, streamtype);
if (codec_is_v4l2_dec(result))
if (codec_is_v4l2_dec(result) || codec_is_v4l2(result))
return result;
#endif
#ifdef USING_MMAL
Expand Down
79 changes: 73 additions & 6 deletions mythtv/libs/libmythtv/decoders/mythv4l2m2mcontext.cpp
Expand Up @@ -6,6 +6,7 @@
#include "v4l2util.h"
#include "fourcc.h"
#include "avformatdecoder.h"
#include "mythdrmprimeinterop.h"
#include "mythv4l2m2mcontext.h"

// Sys
Expand All @@ -23,6 +24,12 @@ MythV4L2M2MContext::MythV4L2M2MContext(DecoderBase *Parent, MythCodecID CodecID)
{
}

MythV4L2M2MContext::~MythV4L2M2MContext()
{
if (m_interop)
m_interop->DecrRef();
}

inline uint32_t V4L2CodecType(AVCodecID Id)
{
switch (Id)
Expand Down Expand Up @@ -83,8 +90,30 @@ MythCodecID MythV4L2M2MContext::GetSupportedCodec(AVCodecContext **Context,
return success;
}

int MythV4L2M2MContext::HwDecoderInit(AVCodecContext *Context)
{
if (!Context)
return -1;

if (codec_is_v4l2_dec(m_codecID))
return 0;

if (!codec_is_v4l2(m_codecID) || Context->pix_fmt != AV_PIX_FMT_DRM_PRIME)
return -1;

MythRenderOpenGL *context = MythRenderOpenGL::GetOpenGLRender();
m_interop = MythDRMPRIMEInterop::Create(context, MythOpenGLInterop::DRMPRIME);
return m_interop ? 0 : -1;
}

void MythV4L2M2MContext::InitVideoCodec(AVCodecContext *Context, bool SelectedStream, bool &DirectRendering)
{
if (codec_is_v4l2(m_codecID))
{
DirectRendering = false;
Context->get_format = MythV4L2M2MContext::GetFormat;
return;
}
if (codec_is_v4l2_dec(m_codecID))
{
DirectRendering = false;
Expand All @@ -96,7 +125,9 @@ void MythV4L2M2MContext::InitVideoCodec(AVCodecContext *Context, bool SelectedSt

bool MythV4L2M2MContext::RetrieveFrame(AVCodecContext *Context, VideoFrame *Frame, AVFrame *AvFrame)
{
if (codec_is_v4l2_dec(m_codecID))
if (codec_is_v4l2(m_codecID))
return GetDRMBuffer(Context, Frame, AvFrame, 0);
else if (codec_is_v4l2_dec(m_codecID))
return GetBuffer(Context, Frame, AvFrame, 0);
return false;
}
Expand All @@ -112,15 +143,18 @@ void MythV4L2M2MContext::SetDecoderOptions(AVCodecContext* Context, AVCodec* Cod
return;
if (!(Codec->priv_class && Context->priv_data))
return;
LOG(VB_PLAYBACK, LOG_INFO, LOC + "Setting number of capture buffers to 2");
av_opt_set(Context->priv_data, "num_capture_buffers", "2", 0);

// Honestly - I don't know:)
int buffers = codec_is_v4l2(m_codecID) ? 8 : 2;
LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Setting number of capture buffers to %1").arg(buffers));
av_opt_set_int(Context->priv_data, "num_capture_buffers", buffers, 0);
}

/*! \brief Retrieve a frame from CPU memory
*
* This is similar to the default, direct render supporting, get_av_buffer in
* AvFormatDecoder but we copy the data from the AVFrame rather than providing
* our own buffer.
* our own buffer (the codec does not support direct rendering).
*/
bool MythV4L2M2MContext::GetBuffer(AVCodecContext *Context, VideoFrame *Frame, AVFrame *AvFrame, int)
{
Expand Down Expand Up @@ -166,8 +200,41 @@ bool MythV4L2M2MContext::GetDRMBuffer(AVCodecContext *Context, VideoFrame *Frame
if (!Context || !AvFrame || !Frame)
return false;

// TODO
return false;
if (Frame->codec != FMT_DRMPRIME || static_cast<AVPixelFormat>(AvFrame->format) != AV_PIX_FMT_DRM_PRIME)
{
LOG(VB_GENERAL, LOG_ERR, LOC + "Not a DRM PRIME buffer");
return false;
}

Frame->width = AvFrame->width;
Frame->height = AvFrame->height;
Frame->pix_fmt = Context->pix_fmt;
Frame->sw_pix_fmt = Context->sw_pix_fmt;
Frame->directrendering = 1;
AvFrame->opaque = Frame;
AvFrame->reordered_opaque = Context->reordered_opaque;

// Frame->data[0] holds AVDRMFrameDescriptor
Frame->buf = AvFrame->data[0];
// Retain the buffer so it is not released before we display it
Frame->priv[0] = reinterpret_cast<unsigned char*>(av_buffer_ref(AvFrame->buf[0]));
// Add interop
Frame->priv[1] = reinterpret_cast<unsigned char*>(m_interop);
// Set the release method
AvFrame->buf[1] = av_buffer_create(reinterpret_cast<uint8_t*>(Frame), 0, MythCodecContext::ReleaseBuffer,
static_cast<AvFormatDecoder*>(Context->opaque), 0);
return true;
}

AVPixelFormat MythV4L2M2MContext::GetFormat(AVCodecContext*, const AVPixelFormat *PixFmt)
{
while (*PixFmt != AV_PIX_FMT_NONE)
{
if (*PixFmt == AV_PIX_FMT_DRM_PRIME)
return AV_PIX_FMT_DRM_PRIME;
PixFmt++;
}
return AV_PIX_FMT_NONE;
}

bool MythV4L2M2MContext::HaveV4L2Codecs(AVCodecID Codec /* = AV_CODEC_ID_NONE */)
Expand Down
10 changes: 9 additions & 1 deletion mythtv/libs/libmythtv/decoders/mythv4l2m2mcontext.h
Expand Up @@ -4,10 +4,13 @@
// MythTV
#include "mythcodeccontext.h"

class MythDRMPRIMEInterop;

class MythV4L2M2MContext : public MythCodecContext
{
public:
MythV4L2M2MContext(DecoderBase *Parent, MythCodecID CodecID);
~MythV4L2M2MContext() override;
static MythCodecID GetSupportedCodec (AVCodecContext **Context,
AVCodec **Codec,
const QString &Decoder,
Expand All @@ -16,9 +19,14 @@ class MythV4L2M2MContext : public MythCodecContext
void InitVideoCodec (AVCodecContext *Context, bool SelectedStream, bool &DirectRendering) override;
bool RetrieveFrame (AVCodecContext *Context, VideoFrame *Frame, AVFrame *AvFrame) override;
void SetDecoderOptions (AVCodecContext* Context, AVCodec* Codec) override;
int HwDecoderInit (AVCodecContext *Context) override;
static bool GetBuffer (AVCodecContext *Context, VideoFrame *Frame, AVFrame *AvFrame, int);
static bool GetDRMBuffer (AVCodecContext *Context, VideoFrame *Frame, AVFrame *AvFrame, int);
bool GetDRMBuffer (AVCodecContext *Context, VideoFrame *Frame, AVFrame *AvFrame, int);
static bool HaveV4L2Codecs (AVCodecID Codec = AV_CODEC_ID_NONE);
static enum AVPixelFormat GetFormat (AVCodecContext*, const AVPixelFormat *PixFmt);

private:
MythDRMPRIMEInterop *m_interop { nullptr };
};

#endif // MYTHV4L2M2MCONTEXT_H
7 changes: 7 additions & 0 deletions mythtv/libs/libmythtv/libmythtv.pro
Expand Up @@ -676,6 +676,13 @@ using_backend {
HEADERS += decoders/mythv4l2m2mcontext.h
SOURCES += decoders/mythv4l2m2mcontext.cpp
DEFINES += USING_V4L2

using_v4l2prime:using_opengl_video {
DEFINES += USING_V4L2PRIME
HEADERS += opengl/mythdrmprimeinterop.h
SOURCES += opengl/mythdrmprimeinterop.cpp
LIBS += -ldrm
}
}

# Support for cable boxes that provide Firewire out
Expand Down
196 changes: 196 additions & 0 deletions mythtv/libs/libmythtv/opengl/mythdrmprimeinterop.cpp
@@ -0,0 +1,196 @@
// MythTV
#include "videocolourspace.h"
#include "mythdrmprimeinterop.h"

// FFmpeg
extern "C" {
#include "libavutil/hwcontext_drm.h"
#include "libavutil/pixdesc.h"
}

// EGL
#include "mythegldefs.h"

#define LOC QString("DRMInterop: ")

MythDRMPRIMEInterop::MythDRMPRIMEInterop(MythRenderOpenGL *Context)
: MythOpenGLInterop(Context, DRMPRIME)
{
}

MythDRMPRIMEInterop* MythDRMPRIMEInterop::Create(MythRenderOpenGL *Context, Type InteropType)
{
if (Context && (InteropType == DRMPRIME))
return new MythDRMPRIMEInterop(Context);
return nullptr;
}

MythOpenGLInterop::Type MythDRMPRIMEInterop::GetInteropType(MythCodecID CodecId, MythRenderOpenGL *Context)
{
// TODO - this should be tied to pix_fmt (i.e. AV_PIX_FMT_DRM_PRIME) not codec.
// Probably applies to all interops
if (!codec_is_v4l2(CodecId))
return Unsupported;

if (!Context)
Context = MythRenderOpenGL::GetOpenGLRender();
if (!Context)
return Unsupported;

OpenGLLocker locker(Context);
return (Context->IsEGL() && Context->hasExtension("GL_OES_EGL_image") &&
Context->HasEGLExtension("EGL_EXT_image_dma_buf_import")) ? DRMPRIME : Unsupported;
}

AVDRMFrameDescriptor* MythDRMPRIMEInterop::VerifyBuffer(MythRenderOpenGL *Context, VideoFrame *Frame)
{
AVDRMFrameDescriptor* result = nullptr;

if ((Frame->pix_fmt != AV_PIX_FMT_DRM_PRIME) || (Frame->codec != FMT_DRMPRIME) ||
!Frame->buf || !Frame->priv[0])
{
LOG(VB_GENERAL, LOG_ERR, LOC + QString("Invalid DRM PRIME buffer %1 %2 %3 %4")
.arg(Frame->buf != nullptr).arg(Frame->priv[0] != nullptr)
.arg(format_description(Frame->codec))
.arg(av_get_pix_fmt_name(static_cast<AVPixelFormat>(Frame->pix_fmt))));
return result;
}

// Sanity check the context
if (m_context != Context)
{
LOG(VB_GENERAL, LOG_ERR, LOC + "Mismatched OpenGL contexts!");
return result;
}

// Check size
QSize surfacesize(Frame->width, Frame->height);
if (m_openglTextureSize != surfacesize)
{
if (!m_openglTextureSize.isEmpty())
LOG(VB_GENERAL, LOG_WARNING, LOC + "Video texture size changed!");
m_openglTextureSize = surfacesize;
}

return reinterpret_cast<AVDRMFrameDescriptor*>(Frame->buf);
}

/*! \brief Copy the frame described by AVDRMFrameDescriptor to an OpenGLTexture.
*
* This code currently uses the OpenGL ES2.0 'version' of EGL_EXT_image_dma_buf_import.
* The incoming DRM planes are combined into a single RGB texture, with limited hints
* for colourspace handling. Hence there are no picture controls and currently
* no deinterlacing support (although for the Raspberry Pi VC4 codecs and Amlogic
* S905 drivers the interlacing flags do not appear to be passed through anyway).
*
* For OpenGL ES3.0 capable devices, we should be able pass the planes into the
* driver as separate textures - which will allow full colourspace control and deinterlacing
* (as for VAAPI DRM interop).
*/
vector<MythVideoTexture*> MythDRMPRIMEInterop::Acquire(MythRenderOpenGL *Context,
VideoColourSpace *ColourSpace,
VideoFrame *Frame, FrameScanType)
{
vector<MythVideoTexture*> result;
if (!Frame)
return result;

AVDRMFrameDescriptor* drmdesc = VerifyBuffer(Context, Frame);
if (!drmdesc)
return result;

// Disable picture attributes on first pass
if (ColourSpace && m_openglTextures.isEmpty())
ColourSpace->SetSupportedAttributes(kPictureAttributeSupported_None);

OpenGLLocker locker(m_context);

// Validate descriptor
if (drmdesc->nb_layers != 1)
{
LOG(VB_PLAYBACK, LOG_ERR, LOC + QString("Invalid DRM PRIME layer count (%1)")
.arg(drmdesc->nb_layers));
return result;
}

// Create texture
if (m_openglTextures.isEmpty())
{
vector<QSize> sizes;
sizes.push_back(m_openglTextureSize);
vector<MythVideoTexture*> textures = MythVideoTexture::CreateTextures(m_context, FMT_DRMPRIME, FMT_RGBA32, sizes);
for (uint i = 0; i < textures.size(); ++i)
textures[i]->m_target = GL_TEXTURE_EXTERNAL_OES;
MythVideoTexture::SetTextureFilters(m_context, textures, QOpenGLTexture::Linear);
m_openglTextures.insert(DUMMY_INTEROP_ID, textures);
}

if (!m_openglTextures.contains(DUMMY_INTEROP_ID))
return result;
result = m_openglTextures[DUMMY_INTEROP_ID];

EGLint colourspace = EGL_ITU_REC709_EXT;
switch (Frame->colorspace)
{
case AVCOL_SPC_BT470BG:
case AVCOL_SPC_SMPTE170M:
case AVCOL_SPC_SMPTE240M:
colourspace = EGL_ITU_REC601_EXT;
break;
case AVCOL_SPC_BT2020_CL:
case AVCOL_SPC_BT2020_NCL:
colourspace = EGL_ITU_REC2020_EXT;
break;
default:
if (Frame->width < 1280)
colourspace = EGL_ITU_REC601_EXT;
break;
}

std::vector<EGLint> attrs = {
EGL_LINUX_DRM_FOURCC_EXT, static_cast<EGLint>(drmdesc->layers[0].format),
EGL_WIDTH, Frame->width,
EGL_HEIGHT, Frame->height,
EGL_YUV_COLOR_SPACE_HINT_EXT, colourspace,
EGL_SAMPLE_RANGE_HINT_EXT, Frame->colorrange == AVCOL_RANGE_JPEG ? EGL_YUV_FULL_RANGE_EXT : EGL_YUV_NARROW_RANGE_EXT,
EGL_YUV_CHROMA_VERTICAL_SITING_HINT_EXT, EGL_YUV_CHROMA_SITING_0_EXT,
EGL_YUV_CHROMA_HORIZONTAL_SITING_HINT_EXT, EGL_YUV_CHROMA_SITING_0_EXT,
EGL_DMA_BUF_PLANE0_FD_EXT, drmdesc->objects[drmdesc->layers[0].planes[0].object_index].fd,
EGL_DMA_BUF_PLANE0_OFFSET_EXT, static_cast<EGLint>(drmdesc->layers[0].planes[0].offset),
EGL_DMA_BUF_PLANE0_PITCH_EXT, static_cast<EGLint>(drmdesc->layers[0].planes[0].pitch)
};

if (drmdesc->layers[0].nb_planes > 1)
{
attrs.push_back(EGL_DMA_BUF_PLANE1_FD_EXT);
attrs.push_back(drmdesc->objects[drmdesc->layers[0].planes[1].object_index].fd);
attrs.push_back(EGL_DMA_BUF_PLANE1_OFFSET_EXT);
attrs.push_back(static_cast<EGLint>(drmdesc->layers[0].planes[1].offset));
attrs.push_back(EGL_DMA_BUF_PLANE1_PITCH_EXT);
attrs.push_back(static_cast<EGLint>(drmdesc->layers[0].planes[1].pitch));
}

if (drmdesc->layers[0].nb_planes > 2)
{
attrs.push_back(EGL_DMA_BUF_PLANE2_FD_EXT);
attrs.push_back(drmdesc->objects[drmdesc->layers[0].planes[2].object_index].fd);
attrs.push_back(EGL_DMA_BUF_PLANE2_OFFSET_EXT);
attrs.push_back(static_cast<EGLint>(drmdesc->layers[0].planes[2].offset));
attrs.push_back(EGL_DMA_BUF_PLANE2_PITCH_EXT);
attrs.push_back(static_cast<EGLint>(drmdesc->layers[0].planes[2].pitch));
}
attrs.push_back(EGL_NONE);

EGLImageKHR image = m_context->eglCreateImageKHR(m_context->GetEGLDisplay(), EGL_NO_CONTEXT,
EGL_LINUX_DMA_BUF_EXT, nullptr, &attrs[0]);
if (!image)
LOG(VB_GENERAL, LOG_ERR, LOC + QString("No EGLImage '%1'").arg(eglGetError()));

MythVideoTexture *texture = result[0];
m_context->glBindTexture(texture->m_target, texture->m_textureId);
m_context->eglImageTargetTexture2DOES(texture->m_target, image);
m_context->glBindTexture(texture->m_target, 0);
m_context->eglDestroyImageKHR(m_context->GetEGLDisplay(), image);

return result;
}

0 comments on commit 07e17d7

Please sign in to comment.