From eba8bab152c28be45022f16916ab7acac0d6206a Mon Sep 17 00:00:00 2001 From: Mark Kendall Date: Wed, 10 Jul 2019 19:13:29 +0100 Subject: [PATCH] Add Video4Linux codecs (decoder) support - tested on a Pi3 (H264, MPEG4, MPEG2) but should get some hardware decoding working on other SoCs - only 'copy back' support at present. DRM PRIME support may be added for copy free rendering - and hopefully improved performance - the main problem in testing is that the Pi3 VC4 drivers do not return any interlacing flags (kernel source tree checked) - so interlacing is not autodetected. - there is some static initialisation to ensure the v4l2-dec decoder isn't available where not supported - but the check may need improvement. - HEVC not tested (no Pi4 here) or indeed the VC6 drivers - requires the open source Pi drivers to be enabled. --- mythtv/configure | 3 +- .../libmythtv/decoders/avformatdecoder.cpp | 39 ++- .../libmythtv/decoders/mythcodeccontext.cpp | 9 + mythtv/libs/libmythtv/libmythtv.pro | 2 + mythtv/libs/libmythtv/mythcodecid.cpp | 2 +- mythtv/libs/libmythtv/mythcodecid.h | 2 +- mythtv/libs/libmythtv/mythframe.h | 6 + mythtv/libs/libmythtv/mythv4l2m2mcontext.cpp | 256 ++++++++++++++++++ mythtv/libs/libmythtv/mythv4l2m2mcontext.h | 22 ++ mythtv/libs/libmythtv/mythvaapicontext.cpp | 2 +- mythtv/libs/libmythtv/mythvaapicontext.h | 2 +- mythtv/libs/libmythtv/videoout_null.cpp | 4 + mythtv/libs/libmythtv/videoout_opengl.cpp | 2 + 13 files changed, 345 insertions(+), 6 deletions(-) create mode 100644 mythtv/libs/libmythtv/mythv4l2m2mcontext.cpp create mode 100644 mythtv/libs/libmythtv/mythv4l2m2mcontext.h diff --git a/mythtv/configure b/mythtv/configure index c1a110d7ae9..92a053101c6 100755 --- a/mythtv/configure +++ b/mythtv/configure @@ -2598,7 +2598,7 @@ opengl_deps_any="agl_h GL_gl_h EGL_egl_h GLES2_gl2_h darwin windows x11" opengles_deps="GLES2_gl2_h" opengl_video_deps="opengl" opengl_themepainter_deps="opengl" -v4l2_deps="backend linux_videodev2_h" +v4l2_deps="linux_videodev2_h" v4l1_deps="backend v4l2 linux_videodev_h" xrandr_deps="x11" asi_deps="backend" @@ -7352,6 +7352,7 @@ if enabled x11 ; then echo "VAAPI support ${vaapi-no}" echo "NVDEC support ${nvdec-no}" fi + echo "Video4Linux codecs ${v4l2-no}" echo "OpenGL support ${opengl-no}" echo "OpenGL video ${opengl_video-no}" echo "OpenGL ThemePainter ${opengl_themepainter-no}" diff --git a/mythtv/libs/libmythtv/decoders/avformatdecoder.cpp b/mythtv/libs/libmythtv/decoders/avformatdecoder.cpp index 771a105658e..6853207c69f 100644 --- a/mythtv/libs/libmythtv/decoders/avformatdecoder.cpp +++ b/mythtv/libs/libmythtv/decoders/avformatdecoder.cpp @@ -71,6 +71,10 @@ extern "C" { #include "mythvdpauhelper.h" #endif +#ifdef USING_V4L2 +#include "mythv4l2m2mcontext.h" +#endif + extern "C" { #include "libavutil/avutil.h" #include "libavutil/error.h" @@ -389,6 +393,14 @@ void AvFormatDecoder::GetDecoders(render_opts &opts) (*opts.equiv_decoders)["vtb"].append("dummy"); (*opts.equiv_decoders)["vtb-dec"].append("dummy"); #endif +#ifdef USING_V4L2 + if (MythV4L2M2MContext::HaveV4L2Codecs()) + { + opts.decoders->append("v4l2-dec"); + (*opts.equiv_decoders)["v4l2-dec"].append("dummy"); + } +#endif + PrivateDecoder::GetDecoders(opts); } @@ -1580,6 +1592,13 @@ void AvFormatDecoder::InitVideoCodec(AVStream *stream, AVCodecContext *enc, m_directrendering = false; } else +#endif +#ifdef USING_V4L2 + if (codec_is_v4l2_dec(m_video_codec_id)) + { + m_directrendering = false; + } + else #endif if (codec1 && codec1->capabilities & AV_CODEC_CAP_DR1) { @@ -2404,7 +2423,7 @@ int AvFormatDecoder::ScanStreams(bool novideo) } m_video_codec_id = kCodec_NONE; - int version = mpeg_version(enc->codec_id); + uint version = mpeg_version(enc->codec_id); if (version) m_video_codec_id = static_cast(kCodec_MPEG1 + version - 1); @@ -2520,6 +2539,24 @@ int AvFormatDecoder::ScanStreams(bool novideo) } } #endif // USING_NVDEC +#ifdef USING_V4L2 + if (!foundgpudecoder) + { + MythCodecID v4l2mcid; + AVPixelFormat pix_fmt = AV_PIX_FMT_YUV420P; + v4l2mcid = MythV4L2M2MContext::GetSupportedCodec(enc, &codec, dec, + mpeg_version(static_cast(enc->codec_id)), pix_fmt); + + if (codec_is_v4l2_dec(v4l2mcid)) + { + gCodecMap->freeCodecContext(m_ic->streams[selTrack]); + enc = gCodecMap->getCodecContext(m_ic->streams[selTrack], codec); + m_video_codec_id = v4l2mcid; + enc->pix_fmt = pix_fmt; + foundgpudecoder = true; + } + } +#endif // USING_V4L2 } // default to mpeg2 if (m_video_codec_id == kCodec_NONE) diff --git a/mythtv/libs/libmythtv/decoders/mythcodeccontext.cpp b/mythtv/libs/libmythtv/decoders/mythcodeccontext.cpp index 4bd8c5df041..5b0631d6e28 100644 --- a/mythtv/libs/libmythtv/decoders/mythcodeccontext.cpp +++ b/mythtv/libs/libmythtv/decoders/mythcodeccontext.cpp @@ -43,6 +43,10 @@ #ifdef USING_MEDIACODEC #include "mythmediacodeccontext.h" #endif +#ifdef USING_V4L2 +#include "mythv4l2m2mcontext.h" +#endif + #include "mythcodeccontext.h" #define LOC QString("MythCodecContext: ") @@ -77,6 +81,11 @@ MythCodecContext *MythCodecContext::CreateContext(DecoderBase *Parent, MythCodec if (codec_is_mediacodec(Codec) || codec_is_mediacodec_dec(Codec)) mctx = new MythMediaCodecContext(Parent, Codec); #endif +#ifdef USING_V4L2 + if (codec_is_v4l2_dec(Codec)) + mctx = new MythV4L2M2MContext(Parent, Codec); +#endif + Q_UNUSED(Codec); if (!mctx) diff --git a/mythtv/libs/libmythtv/libmythtv.pro b/mythtv/libs/libmythtv/libmythtv.pro index a806fa9c437..880ff69b118 100644 --- a/mythtv/libs/libmythtv/libmythtv.pro +++ b/mythtv/libs/libmythtv/libmythtv.pro @@ -662,6 +662,8 @@ using_backend { HEADERS += recorders/v4l2encsignalmonitor.h SOURCES += recorders/v4l2encsignalmonitor.cpp + HEADERS += mythv4l2m2mcontext.h + SOURCES += mythv4l2m2mcontext.cpp DEFINES += USING_V4L2 } diff --git a/mythtv/libs/libmythtv/mythcodecid.cpp b/mythtv/libs/libmythtv/mythcodecid.cpp index 894e36c348e..ac6e2b9b163 100644 --- a/mythtv/libs/libmythtv/mythcodecid.cpp +++ b/mythtv/libs/libmythtv/mythcodecid.cpp @@ -367,7 +367,7 @@ AVCodecID myth2av_codecid(MythCodecID codec_id, bool &vdpau) return AV_CODEC_ID_NONE; } -int mpeg_version(int codec_id) +uint mpeg_version(int codec_id) { switch (codec_id) { diff --git a/mythtv/libs/libmythtv/mythcodecid.h b/mythtv/libs/libmythtv/mythcodecid.h index 4108bfd683f..063cd91eca6 100644 --- a/mythtv/libs/libmythtv/mythcodecid.h +++ b/mythtv/libs/libmythtv/mythcodecid.h @@ -292,7 +292,7 @@ inline AVCodecID myth2av_codecid(MythCodecID codec_id) } // AV codec id convenience functions -int mpeg_version(int codec_id); +uint mpeg_version(int codec_id); #define CODEC_IS_H264(id) (mpeg_version(id) == 5) #define CODEC_IS_MPEG(id) (mpeg_version(id) && mpeg_version(id) <= 2) #define CODEC_IS_FFMPEG_MPEG(id) (CODEC_IS_MPEG(id)) diff --git a/mythtv/libs/libmythtv/mythframe.h b/mythtv/libs/libmythtv/mythframe.h index 0a3662f4d4d..2664c04ab6c 100644 --- a/mythtv/libs/libmythtv/mythframe.h +++ b/mythtv/libs/libmythtv/mythframe.h @@ -484,6 +484,12 @@ static inline void copyplane(uint8_t* dst, int dst_pitch, const uint8_t* src, int src_pitch, int width, int height) { + if ((dst_pitch == width) && (src_pitch == width)) + { + memcpy(dst, src, static_cast(width * height)); + return; + } + for (int y = 0; y < height; y++) { memcpy(dst, src, static_cast(width)); diff --git a/mythtv/libs/libmythtv/mythv4l2m2mcontext.cpp b/mythtv/libs/libmythtv/mythv4l2m2mcontext.cpp new file mode 100644 index 00000000000..6e4a2f39772 --- /dev/null +++ b/mythtv/libs/libmythtv/mythv4l2m2mcontext.cpp @@ -0,0 +1,256 @@ +// Qt +#include + +// MythTV +#include "mythlogging.h" +#include "v4l2util.h" +#include "fourcc.h" +#include "avformatdecoder.h" +#include "mythv4l2m2mcontext.h" + +// Sys +#include + +#define LOC QString("V4L2_M2M: ") + +MythV4L2M2MContext::MythV4L2M2MContext(DecoderBase *Parent, MythCodecID CodecID) + : MythCodecContext(Parent, CodecID) +{ +} + +inline uint32_t V4L2CodecType(AVCodecID Id) +{ + switch (Id) + { + case AV_CODEC_ID_MPEG1VIDEO: return V4L2_PIX_FMT_MPEG1; + case AV_CODEC_ID_MPEG2VIDEO: return V4L2_PIX_FMT_MPEG2; + case AV_CODEC_ID_MPEG4: return V4L2_PIX_FMT_MPEG4; + case AV_CODEC_ID_H263: return V4L2_PIX_FMT_H263; + case AV_CODEC_ID_H264: return V4L2_PIX_FMT_H264; + case AV_CODEC_ID_VC1: return V4L2_PIX_FMT_VC1_ANNEX_G; + case AV_CODEC_ID_VP8: return V4L2_PIX_FMT_VP8; + case AV_CODEC_ID_VP9: return V4L2_PIX_FMT_VP9; + case AV_CODEC_ID_HEVC: return V4L2_PIX_FMT_HEVC; + default: break; + } + return 0; +} +MythCodecID MythV4L2M2MContext::GetSupportedCodec(AVCodecContext *Context, + AVCodec **Codec, + const QString &Decoder, + uint StreamType, + AVPixelFormat &PixFmt) +{ + bool decodeonly = Decoder == "v4l2-dec"; + MythCodecID success = static_cast((decodeonly ? kCodec_MPEG1_V4L2_DEC : kCodec_MPEG1_V4L2) + (StreamType - 1)); + MythCodecID failure = static_cast(kCodec_MPEG1 + (StreamType - 1)); + + // not us + if (!Decoder.startsWith("v4l2")) + return failure; + + // unknown codec + uint32_t v4l2_fmt = V4L2CodecType((*Codec)->id); + if (!v4l2_fmt) + return failure; + + // supported by device driver? + if (!HaveV4L2Codecs((*Codec)->id)) + return failure; + + // look for a decoder + QString name = QString((*Codec)->name) + "_v4l2m2m"; + if (name == "mpeg2video_v4l2m2m") + name = "mpeg2_v4l2m2m"; + AVCodec *codec = avcodec_find_decoder_by_name(name.toLocal8Bit()); + if (!codec) + { + // this shouldn't happen! + LOG(VB_GENERAL, LOG_ERR, LOC + QString("Failed to find %1").arg(name)); + return failure; + } + + LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Found V4L2/FFmpeg decoder '%1'").arg(name)); + *Codec = codec; + PixFmt = decodeonly ? Context->pix_fmt : AV_PIX_FMT_DRM_PRIME; + return success; +} + +bool MythV4L2M2MContext::RetrieveFrame(AVCodecContext *Context, VideoFrame *Frame, AVFrame *AvFrame) +{ + if (codec_is_v4l2_dec(m_codecID)) + return GetBuffer(Context, Frame, AvFrame, 0); + return false; +} + +/*! \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. +*/ +bool MythV4L2M2MContext::GetBuffer(AVCodecContext *Context, VideoFrame *Frame, AVFrame *AvFrame, int) +{ + // Sanity checks + if (!Context || !AvFrame || !Frame) + return false; + + // Ensure we can render this format + AvFormatDecoder *decoder = static_cast(Context->opaque); + VideoFrameType type = PixelFormatToFrameType(static_cast(AvFrame->format)); + VideoFrameType* supported = decoder->GetPlayer()->DirectRenderFormats(); + bool found = false; + while (*supported != FMT_NONE) + { + if (*supported == type) + { + found = true; + break; + } + supported++; + } + + // No fallback currently (unlikely) + if (!found) + return false; + + // Re-allocate if necessary + if ((Frame->codec != type) || (Frame->width != AvFrame->width) || (Frame->height != AvFrame->height)) + if (!VideoBuffers::ReinitBuffer(Frame, type, decoder->GetVideoCodecID(), AvFrame->width, AvFrame->height)) + return false; + + // Copy data + uint count = planes(Frame->codec); + for (uint plane = 0; plane < count; ++plane) + copyplane(Frame->buf + Frame->offsets[plane], Frame->pitches[plane], AvFrame->data[plane], AvFrame->linesize[plane], + pitch_for_plane(Frame->codec, AvFrame->width, plane), height_for_plane(Frame->codec, AvFrame->height, plane)); + + return true; +} + +bool MythV4L2M2MContext::GetDRMBuffer(AVCodecContext *Context, VideoFrame *Frame, AVFrame *AvFrame, int) +{ + if (!Context || !AvFrame || !Frame) + return false; + + // TODO + return false; +} + +bool MythV4L2M2MContext::HaveV4L2Codecs(AVCodecID Codec /* = AV_CODEC_ID_NONE */) +{ + static QVector avcodecs({AV_CODEC_ID_MPEG1VIDEO, AV_CODEC_ID_MPEG2VIDEO, + AV_CODEC_ID_MPEG4, AV_CODEC_ID_H263, + AV_CODEC_ID_H264, AV_CODEC_ID_VC1, + AV_CODEC_ID_VP8, AV_CODEC_ID_VP9, + AV_CODEC_ID_HEVC}); + + static QMutex lock(QMutex::Recursive); + static bool s_needscheck = true; + static QVector s_supportedV4L2Codecs; + + QMutexLocker locker(&lock); + + if (s_needscheck) + { + s_needscheck = false; + s_supportedV4L2Codecs.clear(); + + // Iterate over /dev/videoXX and check for generic codecs support + // We don't care what device is used or actually try a given format (which + // would require width/height etc) - but simply check for capture and output + // support for the given codecs, whether multiplanar or not. + const QString root("/dev/"); + QDir dir(root); + QStringList namefilters; + namefilters.append("video*"); + QStringList devices = dir.entryList(namefilters, QDir::Files |QDir::System); + foreach (QString device, devices) + { + V4L2util v4l2dev(root + device); + uint32_t caps = v4l2dev.GetCapabilities(); + LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Device: %1 Driver: '%2' Capabilities: 0x%3") + .arg(v4l2dev.GetDeviceName()).arg(v4l2dev.GetDriverName()).arg(caps, 0, 16)); + + // check capture and output support + // these mimic the device checks in v4l2_m2m.c + bool mplanar = (caps & (V4L2_CAP_VIDEO_CAPTURE_MPLANE | V4L2_CAP_VIDEO_OUTPUT_MPLANE) && + caps & V4L2_CAP_STREAMING); + bool mplanarm2m = caps & V4L2_CAP_VIDEO_M2M_MPLANE; + bool splanar = (caps & (V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_OUTPUT) && + caps & V4L2_CAP_STREAMING); + bool splanarm2m = caps & V4L2_CAP_VIDEO_M2M; + + if (!(mplanar || mplanarm2m || splanar || splanarm2m)) + continue; + + v4l2_buf_type capturetype = V4L2_BUF_TYPE_VIDEO_CAPTURE; + v4l2_buf_type outputtype = V4L2_BUF_TYPE_VIDEO_OUTPUT; + + if (mplanar || mplanarm2m) + { + capturetype = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + outputtype = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + } + + // check codec support + QStringList debug; + foreach (AVCodecID codec, avcodecs) + { + bool found = false; + uint32_t v4l2pixfmt = V4L2CodecType(codec); + struct v4l2_fmtdesc fdesc; + memset(&fdesc, 0, sizeof(fdesc)); + + // check output first + fdesc.type = outputtype; + while (!found) + { + int res = ioctl(v4l2dev.FD(), VIDIOC_ENUM_FMT, &fdesc); + if (res) + break; + if (fdesc.pixelformat == v4l2pixfmt) + found = true; + fdesc.index++; + } + + if (found) + { + // check capture + memset(&fdesc, 0, sizeof(fdesc)); + fdesc.type = capturetype; + while (true) + { + int res = ioctl(v4l2dev.FD(), VIDIOC_ENUM_FMT, &fdesc); + if (res) + break; + // this is a bit of a shortcut + if (fdesc.pixelformat == V4L2_PIX_FMT_YUV420 || + fdesc.pixelformat == V4L2_PIX_FMT_NV12) + { + if (!s_supportedV4L2Codecs.contains(codec)) + s_supportedV4L2Codecs.append(codec); + debug.append(avcodec_get_name(codec)); + break; + } + fdesc.index++; + } + } + } + if (debug.isEmpty()) + debug.append("None"); + LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Device: %1 Supported codecs: '%2'") + .arg(v4l2dev.GetDeviceName()).arg(debug.join(","))); + } + QStringList gdebug; + foreach (AVCodecID codec, s_supportedV4L2Codecs) + gdebug.append(avcodec_get_name(codec)); + if (gdebug.isEmpty()) + gdebug.append("None"); + LOG(VB_GENERAL, LOG_INFO, LOC + QString("V4L2 codecs supported: %1").arg(gdebug.join(","))); + } + + if (!Codec) + return !s_supportedV4L2Codecs.isEmpty(); + return s_supportedV4L2Codecs.contains(Codec); +} diff --git a/mythtv/libs/libmythtv/mythv4l2m2mcontext.h b/mythtv/libs/libmythtv/mythv4l2m2mcontext.h new file mode 100644 index 00000000000..3015ee55591 --- /dev/null +++ b/mythtv/libs/libmythtv/mythv4l2m2mcontext.h @@ -0,0 +1,22 @@ +#ifndef MYTHV4L2M2MCONTEXT_H +#define MYTHV4L2M2MCONTEXT_H + +// MythTV +#include "mythcodeccontext.h" + +class MythV4L2M2MContext : public MythCodecContext +{ + public: + MythV4L2M2MContext(DecoderBase *Parent, MythCodecID CodecID); + static MythCodecID GetSupportedCodec (AVCodecContext *Context, + AVCodec **Codec, + const QString &Decoder, + uint StreamType, + AVPixelFormat &PixFmt); + bool RetrieveFrame (AVCodecContext *Context, VideoFrame *Frame, AVFrame *AvFrame) override; + static bool GetBuffer (AVCodecContext *Context, VideoFrame *Frame, AVFrame *AvFrame, int); + static bool GetDRMBuffer (AVCodecContext *Context, VideoFrame *Frame, AVFrame *AvFrame, int); + static bool HaveV4L2Codecs (AVCodecID Codec = AV_CODEC_ID_NONE); +}; + +#endif // MYTHV4L2M2MCONTEXT_H diff --git a/mythtv/libs/libmythtv/mythvaapicontext.cpp b/mythtv/libs/libmythtv/mythvaapicontext.cpp index 4529f465536..232fa055a4b 100644 --- a/mythtv/libs/libmythtv/mythvaapicontext.cpp +++ b/mythtv/libs/libmythtv/mythvaapicontext.cpp @@ -130,7 +130,7 @@ inline AVPixelFormat MythVAAPIContext::FramesFormat(AVPixelFormat Format) MythCodecID MythVAAPIContext::GetSupportedCodec(AVCodecContext *Context, AVCodec **Codec, const QString &Decoder, - int StreamType, + uint StreamType, AVPixelFormat &PixFmt) { bool decodeonly = Decoder == "vaapi-dec"; diff --git a/mythtv/libs/libmythtv/mythvaapicontext.h b/mythtv/libs/libmythtv/mythvaapicontext.h index 55b28a37a25..46cae543570 100644 --- a/mythtv/libs/libmythtv/mythvaapicontext.h +++ b/mythtv/libs/libmythtv/mythvaapicontext.h @@ -31,7 +31,7 @@ class MTV_PUBLIC MythVAAPIContext : public MythCodecContext static MythCodecID GetSupportedCodec (AVCodecContext *Context, AVCodec **Codec, const QString &Decoder, - int StreamType, + uint StreamType, AVPixelFormat &PixFmt); static enum AVPixelFormat GetFormat (AVCodecContext *Context, const AVPixelFormat *PixFmt); diff --git a/mythtv/libs/libmythtv/videoout_null.cpp b/mythtv/libs/libmythtv/videoout_null.cpp index e4f2899fdfd..3bc7f8835c6 100644 --- a/mythtv/libs/libmythtv/videoout_null.cpp +++ b/mythtv/libs/libmythtv/videoout_null.cpp @@ -40,6 +40,10 @@ void VideoOutputNull::GetRenderOptions(render_opts &opts, #ifdef USING_MEDIACODEC if (opts.decoders->contains("mediacodec-dec")) (*opts.safe_renderers)["mediacodec-dec"].append("null"); +#endif +#ifdef USING_V4L2 + if (opts.decoders->contains("v4l2-dec")) + (*opts.safe_renderers)["v4l2-dec"].append("null"); #endif opts.priorities->insert("null", 10); } diff --git a/mythtv/libs/libmythtv/videoout_opengl.cpp b/mythtv/libs/libmythtv/videoout_opengl.cpp index 7bd5b8fddd6..c4e07de2b9a 100644 --- a/mythtv/libs/libmythtv/videoout_opengl.cpp +++ b/mythtv/libs/libmythtv/videoout_opengl.cpp @@ -50,6 +50,8 @@ void VideoOutputOpenGL::GetRenderOptions(render_opts &Options, (*Options.safe_renderers)["nvdec-dec"].append(safe); if (Options.decoders->contains("vtb-dec")) (*Options.safe_renderers)["vtb-dec"].append(safe); + if (Options.decoders->contains("v4l2-dec")) + (*Options.safe_renderers)["v4l2-dec"].append(safe); // OpenGL UYVY Options.renderers->append("opengl");