diff --git a/mythtv/libs/libmythtv/decoders/mythv4l2m2mcontext.cpp b/mythtv/libs/libmythtv/decoders/mythv4l2m2mcontext.cpp index e555dd0240..e70570e46d 100644 --- a/mythtv/libs/libmythtv/decoders/mythv4l2m2mcontext.cpp +++ b/mythtv/libs/libmythtv/decoders/mythv4l2m2mcontext.cpp @@ -23,12 +23,11 @@ // FFmpeg extern "C" { #include "libavutil/opt.h" +#include "libavcodec/v4l2_request.h" } #define LOC QString("V4L2_M2M: ") -static bool s_useV4L2Request = !qEnvironmentVariableIsEmpty("MYTHTV_V4L2_REQUEST"); - /*! \class MythV4L2M2MContext * \brief A handler for V4L2 Memory2Memory codecs. * @@ -41,7 +40,7 @@ MythV4L2M2MContext::MythV4L2M2MContext(DecoderBase *Parent, MythCodecID CodecID) { } -bool MythV4L2M2MContext::DecoderWillResetOnFlush(void) +bool MythV4L2M2MContext::DecoderWillResetOnFlush() { return codec_is_v4l2(m_codecID); } @@ -87,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 V4L2Profiles& 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 @@ -107,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; } @@ -124,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; + } + 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); } @@ -164,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; @@ -226,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; - static const std::array s_map + static const std::vector s_map {{ { V4L2_PIX_FMT_MPEG1, MythCodecContext::MPEG1 }, { V4L2_PIX_FMT_MPEG2, MythCodecContext::MPEG2 }, @@ -247,17 +270,22 @@ 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& Profiles) +{ + static const std::vector 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); @@ -295,7 +323,8 @@ 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; @@ -328,19 +357,10 @@ const V4L2Profiles& MythV4L2M2MContext::GetProfiles(void) if (res) break; pixformats.append(fourcc_str(static_cast(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; } @@ -358,22 +378,32 @@ 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 V4L2Profiles& 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(void) +bool MythV4L2M2MContext::HaveV4L2Codecs() { static QMutex lock(QMutex::Recursive); QMutexLocker locker(&lock); @@ -384,8 +414,9 @@ bool MythV4L2M2MContext::HaveV4L2Codecs(void) return s_available; s_checked = true; - const V4L2Profiles& profiles = MythV4L2M2MContext::GetProfiles(); - if (profiles.isEmpty()) + const V4L2Profiles& standard = MythV4L2M2MContext::GetStandardProfiles(); + const V4L2Profiles& request = MythV4L2M2MContext::GetRequestProfiles(); + if (standard.isEmpty() && request.isEmpty()) { LOG(VB_GENERAL, LOG_INFO, LOC + "No V4L2 decoders found"); return s_available; @@ -393,21 +424,67 @@ bool MythV4L2M2MContext::HaveV4L2Codecs(void) 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 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++; } @@ -419,57 +496,51 @@ int MythV4L2M2MContext::InitialiseV4L2RequestContext(AVCodecContext *Context) if (!Context || !gCoreContext->IsUIThread()) return -1; - // We need a render device - MythRenderOpenGL* render = MythRenderOpenGL::GetOpenGLRender(); - if (!render) - return -1; - - // The interop must have a reference to the ui player so it can be deleted - // from the main thread. - MythPlayerUI* player = GetPlayerUI(Context); - if (!player) - return -1; - - // Check interop support - MythOpenGLInterop::Type type = MythOpenGLInterop::GetInteropType(FMT_DRMPRIME, player); - if (type == MythOpenGLInterop::Unsupported) - return -1; - - // Create interop - MythOpenGLInterop *interop = nullptr; -#ifdef USING_EGL - interop = MythDRMPRIMEInterop::Create(render, type); -#endif - if (!interop) - return -1; - - // Set the player required to process interop release - interop->SetPlayer(player); - + // N.B. Interop support should already have been checked // Allocate the device context - AVBufferRef* hwdeviceref = MythCodecContext::CreateDevice(AV_HWDEVICE_TYPE_DRM, interop); + AVBufferRef* hwdeviceref = MythCodecContext::CreateDevice(AV_HWDEVICE_TYPE_DRM, nullptr); if (!hwdeviceref) - { - interop->DecrRef(); return -1; - } - - auto* hwdevicecontext = reinterpret_cast(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(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(&(reinterpret_cast(AvFrame->data[0])->drm)); + // Retain the buffer so it is not released before we display it + Frame->m_priv[0] = reinterpret_cast(av_buffer_ref(AvFrame->buf[0])); + // Set interop + Frame->m_priv[1] = reinterpret_cast(m_interop); + // Set the release method + AvFrame->buf[1] = av_buffer_create(reinterpret_cast(Frame), 0, MythCodecContext::ReleaseBuffer, + static_cast(Context->opaque), 0); + return true; +} diff --git a/mythtv/libs/libmythtv/decoders/mythv4l2m2mcontext.h b/mythtv/libs/libmythtv/decoders/mythv4l2m2mcontext.h index 14d5e1c082..97f2223830 100644 --- a/mythtv/libs/libmythtv/decoders/mythv4l2m2mcontext.h +++ b/mythtv/libs/libmythtv/decoders/mythv4l2m2mcontext.h @@ -5,6 +5,7 @@ #include "mythdrmprimecontext.h" using V4L2Profiles = QList; +using V4L2Mapping = QPair; class MythV4L2M2MContext : public MythDRMPRIMEContext { @@ -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 (void); + static bool HaveV4L2Codecs (); 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 &Profiles); + + bool m_request { false }; }; -#endif // MYTHV4L2M2MCONTEXT_H +#endif