272 changes: 177 additions & 95 deletions mythtv/libs/libmythtv/decoders/mythv4l2m2mcontext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,15 @@
// FFmpeg
extern "C" {
#include "libavutil/opt.h"
#ifdef USING_V4L2_REQUEST
#include "libavcodec/v4l2_request.h"
#else
struct V4L2RequestDescriptor { int drm; };
#endif
}

#define LOC QString("V4L2_M2M: ")

static bool s_useV4L2Request = !qEnvironmentVariableIsEmpty("MYTHTV_V4L2_REQUEST");

/*! \class MythV4L2M2MContext
* \brief A handler for V4L2 Memory2Memory codecs.
*
Expand All @@ -41,7 +44,7 @@ MythV4L2M2MContext::MythV4L2M2MContext(DecoderBase *Parent, MythCodecID CodecID)
{
}

bool MythV4L2M2MContext::DecoderWillResetOnFlush(void)
bool MythV4L2M2MContext::DecoderWillResetOnFlush()
{
return codec_is_v4l2(m_codecID);
}
Expand Down Expand Up @@ -83,9 +86,15 @@ MythCodecID MythV4L2M2MContext::GetSupportedCodec(AVCodecContext **Context,
if (mythprofile == MythCodecContext::NoProfile)
return failure;

const V4L2Profiles& profiles = MythV4L2M2MContext::GetProfiles();
if (!profiles.contains(mythprofile))
return failure;
bool request = false;
const auto & standard = MythV4L2M2MContext::GetStandardProfiles();
if (!standard.contains(mythprofile))
{
const V4L2Profiles& requests = MythV4L2M2MContext::GetRequestProfiles();
if (!requests.contains(mythprofile))
return failure;
request = true;
}

#ifdef USING_MMAL
// If MMAL is available, assume this is a Raspberry Pi and check the supported
Expand All @@ -103,10 +112,8 @@ MythCodecID MythV4L2M2MContext::GetSupportedCodec(AVCodecContext **Context,
}
#endif

if (s_useV4L2Request && !decodeonly)
if (request)
{
LOG(VB_GENERAL, LOG_INFO, LOC + QString("Forcing support for %1 v42l_request")
.arg(ff_codec_id_string((*Context)->codec_id)));
(*Context)->pix_fmt = AV_PIX_FMT_DRM_PRIME;
return success;
}
Expand All @@ -120,36 +127,56 @@ int MythV4L2M2MContext::HwDecoderInit(AVCodecContext *Context)
{
if (!Context)
return -1;
if (s_useV4L2Request && codec_is_v4l2(m_codecID))
return 0;

if (codec_is_v4l2_dec(m_codecID))
return 0;

return MythDRMPRIMEContext::HwDecoderInit(Context);
}

void MythV4L2M2MContext::InitVideoCodec(AVCodecContext *Context, bool SelectedStream, bool &DirectRendering)
{
if (s_useV4L2Request && codec_is_v4l2(m_codecID))
// Fairly circular check of whether our codec id is using the request API.
// N.B. As for other areas of this class, this assumes there is no overlap
// between standard and request API codec support - though both can be used
// but for different codecs (as is expected on the Pi 4)
CodecProfile profile = NoProfile;
switch (m_codecID)
{
Context->get_format = MythV4L2M2MContext::GetV4L2RequestFormat;
return;
case kCodec_MPEG2_V4L2: profile = MPEG2; break;
case kCodec_H264_V4L2: profile = H264; break;
case kCodec_VP8_V4L2: profile = VP8; break;
case kCodec_VP9_V4L2: profile = VP9; break;
case kCodec_HEVC_V4L2: profile = HEVC; break;
default: break;
}

m_request = profile != NoProfile && GetRequestProfiles().contains(profile);

if (codec_is_v4l2_dec(m_codecID))
{
DirectRendering = false;
return;
}

if (m_request && codec_is_v4l2(m_codecID))
{
DirectRendering = false; // Surely true ?? And then an issue for regular V4L2 as well
Context->get_format = MythV4L2M2MContext::GetV4L2RequestFormat;
return;
}

MythDRMPRIMEContext::InitVideoCodec(Context, SelectedStream, DirectRendering);
}

bool MythV4L2M2MContext::RetrieveFrame(AVCodecContext *Context, MythVideoFrame *Frame, AVFrame *AvFrame)
{
if (s_useV4L2Request && codec_is_v4l2(m_codecID))
return MythCodecContext::GetBuffer2(Context, Frame, AvFrame, 0);

if (codec_is_v4l2_dec(m_codecID))
return GetBuffer(Context, Frame, AvFrame, 0);

if (m_request)
return MythV4L2M2MContext::GetRequestBuffer(Context, Frame, AvFrame);

return MythDRMPRIMEContext::RetrieveFrame(Context, Frame, AvFrame);
}

Expand All @@ -160,11 +187,12 @@ bool MythV4L2M2MContext::RetrieveFrame(AVCodecContext *Context, MythVideoFrame *
*/
void MythV4L2M2MContext::SetDecoderOptions(AVCodecContext* Context, AVCodec* Codec)
{
if (s_useV4L2Request && codec_is_v4l2(m_codecID))
if (m_request)
return;

if (!(Context && Codec))
return;

if (!(Codec->priv_class && Context->priv_data))
return;

Expand All @@ -188,12 +216,12 @@ bool MythV4L2M2MContext::GetBuffer(AVCodecContext *Context, MythVideoFrame *Fram
return false;

// Ensure we can render this format
auto *decoder = static_cast<AvFormatDecoder*>(Context->opaque);
VideoFrameType type = MythAVUtil::PixelFormatToFrameType(static_cast<AVPixelFormat>(AvFrame->format));
const VideoFrameTypes* supported = Frame->m_renderFormats;
auto foundIt = std::find(supported->cbegin(), supported->cend(), type);
auto * decoder = static_cast<AvFormatDecoder*>(Context->opaque);
auto type = MythAVUtil::PixelFormatToFrameType(static_cast<AVPixelFormat>(AvFrame->format));
const auto * supported = Frame->m_renderFormats;
auto found = std::find(supported->cbegin(), supported->cend(), type);
// No fallback currently (unlikely)
if (foundIt == supported->end())
if (found == supported->end())
return false;

// Re-allocate if necessary
Expand Down Expand Up @@ -222,10 +250,9 @@ bool MythV4L2M2MContext::GetBuffer(AVCodecContext *Context, MythVideoFrame *Fram
#define V4L2_PIX_FMT_VP9 v4l2_fourcc('V', 'P', '9', '0')
#endif

const V4L2Profiles& MythV4L2M2MContext::GetProfiles(void)
const V4L2Profiles& MythV4L2M2MContext::GetStandardProfiles()
{
using V4L2Mapping = QPair<const uint32_t, const MythCodecContext::CodecProfile>;
static const std::array<const V4L2Mapping,9> s_map
static const std::vector<V4L2Mapping> s_map
{{
{ V4L2_PIX_FMT_MPEG1, MythCodecContext::MPEG1 },
{ V4L2_PIX_FMT_MPEG2, MythCodecContext::MPEG2 },
Expand All @@ -243,23 +270,28 @@ const V4L2Profiles& MythV4L2M2MContext::GetProfiles(void)
static V4L2Profiles s_profiles;

QMutexLocker locker(&lock);
if (s_initialised)
return s_profiles;
if (!s_initialised)
s_profiles = GetProfiles(s_map);
s_initialised = true;
return s_profiles;
}

if (s_useV4L2Request)
V4L2Profiles MythV4L2M2MContext::GetProfiles(const std::vector<V4L2Mapping>& Profiles)
{
static const std::vector<uint32_t> s_formats
{
LOG(VB_GENERAL, LOG_INFO, LOC + "V4L2Request support endabled - assuming all available");
for (auto profile : s_map)
s_profiles.append(profile.second);
return s_profiles;
}
V4L2_PIX_FMT_YUV420, V4L2_PIX_FMT_YVU420, V4L2_PIX_FMT_YUV420M,
V4L2_PIX_FMT_YVU420M, V4L2_PIX_FMT_NV12, V4L2_PIX_FMT_NV12M,
V4L2_PIX_FMT_NV21, V4L2_PIX_FMT_NV21M
};

V4L2Profiles result;

const QString root("/dev/");
QDir dir(root);
QStringList namefilters;
namefilters.append("video*");
QStringList devices = dir.entryList(namefilters, QDir::Files |QDir::System);
auto devices = dir.entryList(namefilters, QDir::Files |QDir::System);
for (const QString& device : qAsConst(devices))
{
V4L2util v4l2dev(root + device);
Expand Down Expand Up @@ -291,11 +323,12 @@ const V4L2Profiles& MythV4L2M2MContext::GetProfiles(void)
// check codec support
QStringList debug;
QSize dummy{0, 0};
for (auto profile : s_map)

for (auto & profile : Profiles)
{
bool found = false;
uint32_t v4l2pixfmt = profile.first;
MythCodecContext::CodecProfile mythprofile = profile.second;
auto mythprofile = profile.second;
struct v4l2_fmtdesc fdesc {};
memset(&fdesc, 0, sizeof(fdesc));

Expand Down Expand Up @@ -324,19 +357,10 @@ const V4L2Profiles& MythV4L2M2MContext::GetProfiles(void)
if (res)
break;
pixformats.append(fourcc_str(static_cast<int>(fdesc.pixelformat)));

// this is a bit of a shortcut
if (fdesc.pixelformat == V4L2_PIX_FMT_YUV420 ||
fdesc.pixelformat == V4L2_PIX_FMT_YVU420 ||
fdesc.pixelformat == V4L2_PIX_FMT_YUV420M ||
fdesc.pixelformat == V4L2_PIX_FMT_YVU420M ||
fdesc.pixelformat == V4L2_PIX_FMT_NV12 ||
fdesc.pixelformat == V4L2_PIX_FMT_NV12M ||
fdesc.pixelformat == V4L2_PIX_FMT_NV21 ||
fdesc.pixelformat == V4L2_PIX_FMT_NV21M)
if (std::find(s_formats.cbegin(), s_formats.cend(), fdesc.pixelformat) != s_formats.cend())
{
if (!s_profiles.contains(mythprofile))
s_profiles.append(mythprofile);
if (!result.contains(mythprofile))
result.append(mythprofile);
foundfmt = true;
break;
}
Expand All @@ -354,19 +378,29 @@ const V4L2Profiles& MythV4L2M2MContext::GetProfiles(void)
}
}

return s_profiles;
return result;
}

void MythV4L2M2MContext::GetDecoderList(QStringList &Decoders)
{
const V4L2Profiles& profiles = MythV4L2M2MContext::GetProfiles();
if (profiles.isEmpty())
return;
const auto & profiles = MythV4L2M2MContext::GetStandardProfiles();
if (!profiles.isEmpty())
{
QSize size(0, 0);
Decoders.append("V4L2:");
for (MythCodecContext::CodecProfile profile : profiles)
Decoders.append(MythCodecContext::GetProfileDescription(profile, size));
}

const V4L2Profiles& requests = MythV4L2M2MContext::GetRequestProfiles();
if (!requests.isEmpty())
{
QSize size(0, 0);
Decoders.append("V4L2 Request:");
for (MythCodecContext::CodecProfile profile : requests)
Decoders.append(MythCodecContext::GetProfileDescription(profile, size));
}

QSize size(0, 0);
Decoders.append("V4L2:");
for (MythCodecContext::CodecProfile profile : profiles)
Decoders.append(MythCodecContext::GetProfileDescription(profile, size));
}

bool MythV4L2M2MContext::HaveV4L2Codecs(bool Reinit /*=false*/)
Expand All @@ -380,30 +414,77 @@ bool MythV4L2M2MContext::HaveV4L2Codecs(bool Reinit /*=false*/)
return s_available;
s_checked = true;

const V4L2Profiles& profiles = MythV4L2M2MContext::GetProfiles();
if (profiles.isEmpty())
const auto & standard = MythV4L2M2MContext::GetStandardProfiles();
const auto & request = MythV4L2M2MContext::GetRequestProfiles();
if (standard.isEmpty() && request.isEmpty())
{
LOG(VB_GENERAL, LOG_INFO, LOC + "No V4L2 decoders found");
return s_available;
}

LOG(VB_GENERAL, LOG_INFO, LOC + "Supported/available V4L2 decoders:");
s_available = true;
QSize size{0, 0};
for (auto profile : qAsConst(profiles))
QSize size {0, 0};
for (auto profile : qAsConst(standard))
LOG(VB_GENERAL, LOG_INFO, LOC + MythCodecContext::GetProfileDescription(profile, size));
for (auto profile : qAsConst(request))
LOG(VB_GENERAL, LOG_INFO, LOC + MythCodecContext::GetProfileDescription(profile, size) + "(Request)");
return s_available;
}

#ifndef V4L2_PIX_FMT_MPEG2_SLICE
#define V4L2_PIX_FMT_MPEG2_SLICE v4l2_fourcc('M', 'G', '2', 'S')
#endif

#ifndef V4L2_PIX_FMT_H264_SLICE
#define V4L2_PIX_FMT_H264_SLICE v4l2_fourcc('S', '2', '6', '4')
#endif

#ifndef V4L2_PIX_FMT_VP8_FRAME
#define V4L2_PIX_FMT_VP8_FRAME v4l2_fourcc('V', 'P', '8', 'F')
#endif

#ifndef V4L2_PIX_FMT_VP9_FRAME
#define V4L2_PIX_FMT_VP9_FRAME v4l2_fourcc('V', 'P', '9', 'F')
#endif

#ifndef V4L2_PIX_FMT_HEVC_SLICE
#define V4L2_PIX_FMT_HEVC_SLICE v4l2_fourcc('S', '2', '6', '5')
#endif

const V4L2Profiles& MythV4L2M2MContext::GetRequestProfiles()
{
static const std::vector<V4L2Mapping> s_map
{{
{ V4L2_PIX_FMT_MPEG2_SLICE, MythCodecContext::MPEG2 },
{ V4L2_PIX_FMT_H264_SLICE, MythCodecContext::H264 },
{ V4L2_PIX_FMT_VP8_FRAME, MythCodecContext::VP8 },
{ V4L2_PIX_FMT_VP9_FRAME, MythCodecContext::VP9 },
{ V4L2_PIX_FMT_HEVC_SLICE, MythCodecContext::HEVC }
}};

static QMutex lock(QMutex::Recursive);
static bool s_initialised = false;
static V4L2Profiles s_profiles;

QMutexLocker locker(&lock);
if (!s_initialised)
s_profiles = GetProfiles(s_map);
s_initialised = true;
return s_profiles;
}

AVPixelFormat MythV4L2M2MContext::GetV4L2RequestFormat(AVCodecContext *Context, const AVPixelFormat *PixFmt)
{
while (*PixFmt != AV_PIX_FMT_NONE)
{
if (*PixFmt == AV_PIX_FMT_DRM_PRIME)
{
if (MythCodecContext::InitialiseDecoder(Context, MythV4L2M2MContext::InitialiseV4L2RequestContext,
"V4L2 request context creation") >= 0)
if (MythCodecContext::InitialiseDecoder2(Context, MythV4L2M2MContext::InitialiseV4L2RequestContext,
"V4L2 request context creation") >= 0)
{
return AV_PIX_FMT_DRM_PRIME;
}
}
PixFmt++;
}
Expand All @@ -415,50 +496,51 @@ int MythV4L2M2MContext::InitialiseV4L2RequestContext(AVCodecContext *Context)
if (!Context || !gCoreContext->IsUIThread())
return -1;

// The interop must have a reference to the ui player so it can be deleted
// from the main thread.
auto * player = GetPlayerUI(Context);
if (!player)
return -1;

// Retrieve OpenGL render context
auto * render = dynamic_cast<MythRenderOpenGL*>(player->GetRender());
if (!render)
return -1;
OpenGLLocker locker(render);

// Create interop
MythOpenGLInterop *interop = nullptr;
#ifdef USING_EGL
interop = MythDRMPRIMEInterop::CreateDRM(render, player);
#endif
if (!interop)
return -1;

// N.B. Interop support should already have been checked
// Allocate the device context
auto * hwdeviceref = MythCodecContext::CreateDevice(AV_HWDEVICE_TYPE_DRM, interop);
auto * hwdeviceref = MythCodecContext::CreateDevice(AV_HWDEVICE_TYPE_DRM, nullptr);
if (!hwdeviceref)
{
interop->DecrRef();
return -1;
}

auto * hwdevicecontext = reinterpret_cast<AVHWDeviceContext*>(hwdeviceref->data);
if (!hwdevicecontext || !hwdevicecontext->hwctx)
{
interop->DecrRef();
return -1;
}

// Initialise device context
if (av_hwdevice_ctx_init(hwdeviceref) < 0)
{
LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to initialise device context");
av_buffer_unref(&hwdeviceref);
interop->DecrRef();
return -1;
}

Context->hw_device_ctx = hwdeviceref;
return 0;
}

bool MythV4L2M2MContext::GetRequestBuffer(AVCodecContext* Context, MythVideoFrame* Frame, AVFrame* AvFrame)
{
if (!Context || !AvFrame || !Frame)
return false;

if (Frame->m_type != 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->m_width = AvFrame->width;
Frame->m_height = AvFrame->height;
Frame->m_pixFmt = Context->pix_fmt;
Frame->m_swPixFmt = Context->sw_pix_fmt;
Frame->m_directRendering = true;
AvFrame->opaque = Frame;
AvFrame->reordered_opaque = Context->reordered_opaque;

// Frame->data[0] holds V4L2RequestDescriptor which holds AVDRMFrameDescriptor
Frame->m_buffer = reinterpret_cast<uint8_t*>(&(reinterpret_cast<V4L2RequestDescriptor*>(AvFrame->data[0])->drm));
// Retain the buffer so it is not released before we display it
Frame->m_priv[0] = reinterpret_cast<unsigned char*>(av_buffer_ref(AvFrame->buf[0]));
// Set interop
Frame->m_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;
}
14 changes: 11 additions & 3 deletions mythtv/libs/libmythtv/decoders/mythv4l2m2mcontext.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "mythdrmprimecontext.h"

using V4L2Profiles = QList<MythCodecContext::CodecProfile>;
using V4L2Mapping = QPair<const uint32_t, const MythCodecContext::CodecProfile>;

class MythV4L2M2MContext : public MythDRMPRIMEContext
{
Expand All @@ -20,16 +21,23 @@ class MythV4L2M2MContext : public MythDRMPRIMEContext
bool RetrieveFrame (AVCodecContext *Context, MythVideoFrame *Frame, AVFrame *AvFrame) override;
void SetDecoderOptions (AVCodecContext* Context, AVCodec* Codec) override;
int HwDecoderInit (AVCodecContext *Context) override;
bool DecoderWillResetOnFlush (void) override;
bool DecoderWillResetOnFlush () override;
static bool GetBuffer (AVCodecContext *Context, MythVideoFrame *Frame, AVFrame *AvFrame, int/*Flags*/);
static bool HaveV4L2Codecs (bool Reinit = false);
static void GetDecoderList (QStringList &Decoders);

static enum AVPixelFormat GetV4L2RequestFormat(AVCodecContext *Context, const AVPixelFormat *PixFmt);
static int InitialiseV4L2RequestContext(AVCodecContext *Context);
bool GetRequestBuffer(AVCodecContext* Context, MythVideoFrame* Frame, AVFrame* AvFrame);

protected:
static const V4L2Profiles& GetProfiles(void);
static const V4L2Profiles& GetStandardProfiles();
static const V4L2Profiles& GetRequestProfiles();

private:
static V4L2Profiles GetProfiles(const std::vector<V4L2Mapping> &Profiles);

bool m_request { false };
};

#endif // MYTHV4L2M2MCONTEXT_H
#endif