From ffdfcc832f82521c328e02912d707aa3b2b7ed20 Mon Sep 17 00:00:00 2001
From: Jernej Skrabec <jernej.skrabec@siol.net>
Date: Thu, 26 Dec 2019 11:01:51 +0100
Subject: [PATCH 2/7] WIP: DRMPRIME deinterlace filter

---
 .../DVDCodecs/Video/DVDVideoCodecDRMPRIME.cpp | 379 +++++++++++++++---
 .../DVDCodecs/Video/DVDVideoCodecDRMPRIME.h   |   9 +-
 2 files changed, 328 insertions(+), 60 deletions(-)

diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecDRMPRIME.cpp b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecDRMPRIME.cpp
index 4cf36f83e6..0d32d65ce4 100644
--- a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecDRMPRIME.cpp
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecDRMPRIME.cpp
@@ -20,6 +20,7 @@
 #include "utils/CPUInfo.h"
 #include "utils/StringUtils.h"
 #include "utils/log.h"
+#include "utils/StringUtils.h"
 
 #if defined(HAVE_GBM)
 #include "windowing/gbm/WinSystemGbm.h"
@@ -92,12 +93,15 @@ CDVDVideoCodecDRMPRIME::CDVDVideoCodecDRMPRIME(CProcessInfo& processInfo)
   : CDVDVideoCodec(processInfo)
 {
   m_pFrame = av_frame_alloc();
+  m_pFilterFrame = av_frame_alloc();
   m_videoBufferPool = std::make_shared<CVideoBufferPoolDRMPRIMEFFmpeg>();
 }
 
 CDVDVideoCodecDRMPRIME::~CDVDVideoCodecDRMPRIME()
 {
   av_frame_free(&m_pFrame);
+  av_frame_free(&m_pFilterFrame);
+  FilterClose();
   avcodec_free_context(&m_pCodecContext);
 }
 
@@ -376,8 +380,19 @@ bool CDVDVideoCodecDRMPRIME::Open(CDVDStreamInfo& hints, CDVDCodecOptions& optio
   }
 
   UpdateProcessInfo(m_pCodecContext, m_pCodecContext->pix_fmt);
-  m_processInfo.SetVideoDeintMethod("none");
+  m_processInfo.SetVideoInterlaced(false);
   m_processInfo.SetVideoDAR(hints.aspect);
+  m_processInfo.SetVideoDeintMethod("none");
+
+  FilterTest();
+
+  if (!m_deintFilterName.empty())
+  {
+    std::list<EINTERLACEMETHOD> methods;
+    methods.push_back(EINTERLACEMETHOD::VS_INTERLACEMETHOD_DEINTERLACE);
+    m_processInfo.UpdateDeinterlacingMethods(methods);
+    m_processInfo.SetDeinterlacingMethodDefault(EINTERLACEMETHOD::VS_INTERLACEMETHOD_DEINTERLACE);
+  }
 
   return true;
 }
@@ -453,6 +468,8 @@ void CDVDVideoCodecDRMPRIME::Reset()
     return;
 
   Drain();
+  m_filters.clear();
+  FilterClose();
 
   do
   {
@@ -500,7 +517,7 @@ void CDVDVideoCodecDRMPRIME::Drain()
   av_packet_free(&avpkt);
 }
 
-void CDVDVideoCodecDRMPRIME::SetPictureParams(VideoPicture* pVideoPicture)
+bool CDVDVideoCodecDRMPRIME::SetPictureParams(VideoPicture* pVideoPicture)
 {
   pVideoPicture->iWidth = m_pFrame->width;
   pVideoPicture->iHeight = m_pFrame->height;
@@ -595,13 +612,238 @@ void CDVDVideoCodecDRMPRIME::SetPictureParams(VideoPicture* pVideoPicture)
                            ? DVD_NOPTS_VALUE
                            : static_cast<double>(pts) * DVD_TIME_BASE / AV_TIME_BASE;
   pVideoPicture->dts = DVD_NOPTS_VALUE;
+
+  if (pVideoPicture->videoBuffer)
+  {
+    pVideoPicture->videoBuffer->Release();
+    pVideoPicture->videoBuffer = nullptr;
+  }
+
+  if (IsSupportedHwFormat(static_cast<AVPixelFormat>(m_pFrame->format)))
+  {
+    CVideoBufferDRMPRIMEFFmpeg* buffer =
+        dynamic_cast<CVideoBufferDRMPRIMEFFmpeg*>(m_videoBufferPool->Get());
+    buffer->SetPictureParams(*pVideoPicture);
+    buffer->SetRef(m_pFrame);
+    pVideoPicture->videoBuffer = buffer;
+  }
+  else if (m_pFrame->opaque)
+  {
+    CVideoBufferDMA* buffer = static_cast<CVideoBufferDMA*>(m_pFrame->opaque);
+    buffer->SetPictureParams(*pVideoPicture);
+    buffer->Acquire();
+    buffer->SyncEnd();
+    buffer->SetDimensions(m_pFrame->width, m_pFrame->height);
+
+    pVideoPicture->videoBuffer = buffer;
+    av_frame_unref(m_pFrame);
+  }
+
+  if (!pVideoPicture->videoBuffer)
+  {
+    CLog::Log(LOGERROR, "CDVDVideoCodecDRMPRIME::{} - videoBuffer:nullptr format:{}", __FUNCTION__,
+              av_get_pix_fmt_name(static_cast<AVPixelFormat>(m_pFrame->format)));
+    av_frame_unref(m_pFrame);
+    return false;
+  }
+
+  return true;
 }
 
-CDVDVideoCodec::VCReturn CDVDVideoCodecDRMPRIME::ProcessFilterIn()
+void CDVDVideoCodecDRMPRIME::FilterTest()
 {
-  if (!m_pFilterIn)
-    return VC_PICTURE;
+  const AVFilter* filter;
+  void* opaque{};
+
+  m_deintFilterName.clear();
+
+  while ((filter = av_filter_iterate(&opaque)) != nullptr)
+  {
+    std::string name(filter->name);
+
+    if (name.find("deinterlace") != std::string::npos)
+    {
+      if (FilterOpen(name, true))
+      {
+        m_deintFilterName = name;
 
+        CLog::Log(LOGDEBUG, "CDVDVideoCodecDRMPRIME::{} - found deinterlacing filter {}",
+                  __FUNCTION__, name);
+
+        return;
+      }
+    }
+  }
+
+  CLog::Log(LOGDEBUG, "CDVDVideoCodecDRMPRIME::{} - no deinterlacing filter found",
+            __FUNCTION__);
+}
+
+bool CDVDVideoCodecDRMPRIME::FilterOpen(const std::string& filters, bool test)
+{
+  int result;
+
+  if (m_pFilterGraph)
+    FilterClose();
+
+  if (filters.empty())
+    return true;
+
+  if (!(m_pFilterGraph = avfilter_graph_alloc()))
+  {
+    CLog::Log(LOGERROR, "CDVDVideoCodecDRMPRIME::FilterOpen - unable to alloc filter graph");
+    return false;
+  }
+
+  const AVFilter* srcFilter = avfilter_get_by_name("buffer");
+  const AVFilter* outFilter = avfilter_get_by_name("buffersink");
+  enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_DRM_PRIME, AV_PIX_FMT_NONE };
+
+  std::string args = StringUtils::Format("video_size={}x{}:pix_fmt={}:time_base={}/{}:"
+                                         "pixel_aspect={}/{}",
+                                         m_pCodecContext->width,
+                                         m_pCodecContext->height,
+                                         m_pCodecContext->pix_fmt,
+                                         m_pCodecContext->time_base.num ?
+                                           m_pCodecContext->time_base.num : 1,
+                                         m_pCodecContext->time_base.num ?
+                                           m_pCodecContext->time_base.den : 1,
+                                         m_pCodecContext->sample_aspect_ratio.num != 0 ?
+                                           m_pCodecContext->sample_aspect_ratio.num : 1,
+                                         m_pCodecContext->sample_aspect_ratio.num != 0 ?
+                                           m_pCodecContext->sample_aspect_ratio.den : 1);
+
+  result = avfilter_graph_create_filter(&m_pFilterIn, srcFilter, "src",
+                                        args.c_str(), NULL, m_pFilterGraph);
+  if (result < 0)
+  {
+    char err[AV_ERROR_MAX_STRING_SIZE] = {};
+    av_strerror(result, err, AV_ERROR_MAX_STRING_SIZE);
+    CLog::Log(LOGERROR,
+              "CDVDVideoCodecDRMPRIME::FilterOpen - avfilter_graph_create_filter: src: {} ({})",
+              err, result);
+    return false;
+  }
+
+  AVBufferSrcParameters *par = av_buffersrc_parameters_alloc();
+  if (!par)
+  {
+    CLog::Log(LOGERROR, "CDVDVideoCodecDRMPRIME::FilterOpen - unable to alloc buffersrc");
+    return false;
+  }
+
+  memset(par, 0, sizeof(*par));
+  par->format = AV_PIX_FMT_NONE;
+  par->hw_frames_ctx = m_pCodecContext->hw_device_ctx;
+
+  result = av_buffersrc_parameters_set(m_pFilterIn, par);
+  if (result < 0)
+  {
+    char err[AV_ERROR_MAX_STRING_SIZE] = {};
+    av_strerror(result, err, AV_ERROR_MAX_STRING_SIZE);
+    CLog::Log(LOGERROR,
+              "CDVDVideoCodecDRMPRIME::FilterOpen - av_buffersrc_parameters_set:  {} ({})",
+              err, result);
+    return false;
+  }
+  av_freep(&par);
+
+  result = avfilter_graph_create_filter(&m_pFilterOut, outFilter, "out",
+                                        NULL, NULL, m_pFilterGraph);
+  if (result < 0)
+  {
+    char err[AV_ERROR_MAX_STRING_SIZE] = {};
+    av_strerror(result, err, AV_ERROR_MAX_STRING_SIZE);
+    CLog::Log(LOGERROR,
+              "CDVDVideoCodecDRMPRIME::FilterOpen - avfilter_graph_create_filter: out: {} ({})",
+              err, result);
+    return false;
+  }
+
+  result = av_opt_set_int_list(m_pFilterOut, "pix_fmts", &pix_fmts[0],
+                               AV_PIX_FMT_NONE, AV_OPT_SEARCH_CHILDREN);
+  if (result < 0)
+  {
+    CLog::Log(LOGERROR, "CDVDVideoCodecDRMPRIME::FilterOpen - failed settings pix formats");
+    return false;
+  }
+
+  AVFilterInOut* outputs = avfilter_inout_alloc();
+  AVFilterInOut* inputs  = avfilter_inout_alloc();
+
+  outputs->name = av_strdup("in");
+  outputs->filter_ctx = m_pFilterIn;
+  outputs->pad_idx = 0;
+  outputs->next = nullptr;
+
+  inputs->name = av_strdup("out");
+  inputs->filter_ctx = m_pFilterOut;
+  inputs->pad_idx = 0;
+  inputs->next = nullptr;
+
+  result = avfilter_graph_parse_ptr(m_pFilterGraph, filters.c_str(), &inputs, &outputs, NULL);
+  avfilter_inout_free(&outputs);
+  avfilter_inout_free(&inputs);
+
+  if (result < 0)
+  {
+    CLog::Log(LOGERROR, "CDVDVideoCodecDRMPRIME::FilterOpen - avfilter_graph_parse");
+    return false;
+  }
+
+  if ((result = avfilter_graph_config(m_pFilterGraph,  nullptr)) < 0)
+  {
+    char err[AV_ERROR_MAX_STRING_SIZE] = {};
+    av_strerror(result, err, AV_ERROR_MAX_STRING_SIZE);
+    CLog::Log(LOGERROR, "CDVDVideoCodecDRMPRIME::FilterOpen - avfilter_graph_config:  {} ({})",
+              err, result);
+    return false;
+  }
+
+  if (test)
+  {
+    FilterClose();
+    return true;
+  }
+
+  if (filters.find("deinterlace") != std::string::npos)
+  {
+    m_processInfo.SetVideoDeintMethod(filters);
+  }
+  else
+  {
+    m_processInfo.SetVideoDeintMethod("none");
+  }
+
+  if (CServiceBroker::GetLogging().CanLogComponent(LOGVIDEO))
+  {
+    char* graphDump = avfilter_graph_dump(m_pFilterGraph, nullptr);
+    if (graphDump)
+    {
+      CLog::Log(LOGDEBUG, "CDVDVideoCodecDRMPRIME::FilterOpen - Final filter graph:\n{}",
+                graphDump);
+      av_freep(&graphDump);
+    }
+  }
+
+  return true;
+}
+
+void CDVDVideoCodecDRMPRIME::FilterClose()
+{
+  if (m_pFilterGraph)
+  {
+    CLog::Log(LOGDEBUG, LOGVIDEO, "CDVDVideoCodecDRMPRIME::FilterClose - Freeing filter graph");
+    avfilter_graph_free(&m_pFilterGraph);
+
+    // Disposed by above code
+    m_pFilterIn = nullptr;
+    m_pFilterOut = nullptr;
+  }
+}
+
+CDVDVideoCodec::VCReturn CDVDVideoCodecDRMPRIME::ProcessFilterIn()
+{
   int ret = av_buffersrc_add_frame(m_pFilterIn, m_pFrame);
   if (ret < 0)
   {
@@ -617,21 +859,14 @@ CDVDVideoCodec::VCReturn CDVDVideoCodecDRMPRIME::ProcessFilterIn()
 
 CDVDVideoCodec::VCReturn CDVDVideoCodecDRMPRIME::ProcessFilterOut()
 {
-  if (!m_pFilterOut)
-    return VC_EOF;
-
-  int ret = av_buffersink_get_frame(m_pFilterOut, m_pFrame);
+  int ret = av_buffersink_get_frame(m_pFilterOut, m_pFilterFrame);
   if (ret == AVERROR(EAGAIN))
     return VC_BUFFER;
   else if (ret == AVERROR_EOF)
   {
-    if (m_codecControlFlags & DVD_CODEC_CTRL_DRAIN)
-    {
-      CLog::Log(LOGDEBUG, "CDVDVideoCodecDRMPRIME::{} - flush buffers", __FUNCTION__);
-      avcodec_flush_buffers(m_pCodecContext);
-      SetCodecControl(m_codecControlFlags & ~DVD_CODEC_CTRL_DRAIN);
-    }
-    return VC_EOF;
+    ret = av_buffersink_get_frame(m_pFilterOut, m_pFilterFrame);
+    if (ret < 0)
+      return VC_BUFFER;
   }
   else if (ret)
   {
@@ -642,71 +877,97 @@ CDVDVideoCodec::VCReturn CDVDVideoCodecDRMPRIME::ProcessFilterOut()
     return VC_ERROR;
   }
 
+  av_frame_unref(m_pFrame);
+  av_frame_move_ref(m_pFrame, m_pFilterFrame);
+
   return VC_PICTURE;
 }
 
+std::string CDVDVideoCodecDRMPRIME::GetFilterChain(bool interlaced)
+{
+  // ask codec to do deinterlacing if possible
+  EINTERLACEMETHOD mInt = m_processInfo.GetVideoSettings().m_InterlaceMethod;
+  std::string filterChain;
+
+  if (!m_processInfo.Supports(mInt))
+    mInt = m_processInfo.GetFallbackDeintMethod();
+
+  if (mInt != VS_INTERLACEMETHOD_NONE && interlaced && !m_deintFilterName.empty())
+    filterChain += m_deintFilterName;
+
+  return filterChain;
+}
+
 CDVDVideoCodec::VCReturn CDVDVideoCodecDRMPRIME::GetPicture(VideoPicture* pVideoPicture)
 {
   if (m_codecControlFlags & DVD_CODEC_CTRL_DRAIN)
     Drain();
 
-  auto result = ProcessFilterOut();
-  if (result != VC_PICTURE)
+  if (m_pFilterGraph)
   {
-    int ret = avcodec_receive_frame(m_pCodecContext, m_pFrame);
-    if (ret == AVERROR(EAGAIN))
-      return VC_BUFFER;
-    else if (ret == AVERROR_EOF)
-      return VC_EOF;
-    else if (ret)
+    auto ret = ProcessFilterOut();
+    if (ret == VC_PICTURE)
     {
-      char err[AV_ERROR_MAX_STRING_SIZE] = {};
-      av_strerror(ret, err, AV_ERROR_MAX_STRING_SIZE);
-      CLog::Log(LOGERROR, "CDVDVideoCodecDRMPRIME::{} - receive frame failed: {} ({})",
-                __FUNCTION__, err, ret);
-      return VC_ERROR;
+      if (!SetPictureParams(pVideoPicture))
+        return VC_ERROR;
+      return VC_PICTURE;
+    }
+    else if (ret != VC_BUFFER)
+    {
+      return ret;
     }
-
-    result = ProcessFilterIn();
-    if (result != VC_PICTURE)
-      return result;
   }
 
-  SetPictureParams(pVideoPicture);
-
-  if (pVideoPicture->videoBuffer)
+  int ret = avcodec_receive_frame(m_pCodecContext, m_pFrame);
+  if (ret == AVERROR(EAGAIN))
+    return VC_BUFFER;
+  else if (ret == AVERROR_EOF)
+    return VC_EOF;
+  else if (ret)
   {
-    pVideoPicture->videoBuffer->Release();
-    pVideoPicture->videoBuffer = nullptr;
+    char err[AV_ERROR_MAX_STRING_SIZE] = {};
+    av_strerror(ret, err, AV_ERROR_MAX_STRING_SIZE);
+    CLog::Log(LOGERROR, "CDVDVideoCodecDRMPRIME::{} - receive frame failed: {} ({})",
+              __FUNCTION__, err, ret);
+    return VC_ERROR;
   }
 
-  if (IsSupportedHwFormat(static_cast<AVPixelFormat>(m_pFrame->format)))
+  if (!m_processInfo.GetVideoInterlaced() && m_pFrame->interlaced_frame)
+    m_processInfo.SetVideoInterlaced(true);
+
+  std::string filterChain = GetFilterChain(m_pFrame->interlaced_frame);
+  if (!filterChain.empty())
   {
-    CVideoBufferDRMPRIMEFFmpeg* buffer =
-        dynamic_cast<CVideoBufferDRMPRIMEFFmpeg*>(m_videoBufferPool->Get());
-    buffer->SetPictureParams(*pVideoPicture);
-    buffer->SetRef(m_pFrame);
-    pVideoPicture->videoBuffer = buffer;
+    bool reopenFilter = false;
+    if (m_filters != filterChain)
+      reopenFilter = true;
+
+    if (m_pFilterGraph &&
+        (m_pFilterIn->outputs[0]->w != m_pCodecContext->width ||
+         m_pFilterIn->outputs[0]->h != m_pCodecContext->height))
+      reopenFilter = true;
+
+    if (reopenFilter)
+    {
+      m_filters = filterChain;
+      if (!FilterOpen(filterChain, false))
+        FilterClose();
+    }
+
+    if (m_pFilterGraph)
+    {
+      if (ProcessFilterIn() != VC_PICTURE)
+        return VC_NONE;
+    }
   }
-  else if (m_pFrame->opaque)
+  else
   {
-    CVideoBufferDMA* buffer = static_cast<CVideoBufferDMA*>(m_pFrame->opaque);
-    buffer->SetPictureParams(*pVideoPicture);
-    buffer->Acquire();
-    buffer->SyncEnd();
-    buffer->SetDimensions(m_pFrame->width, m_pFrame->height);
-
-    pVideoPicture->videoBuffer = buffer;
-    av_frame_unref(m_pFrame);
+    m_filters.clear();
+    FilterClose();
   }
 
-  if (!pVideoPicture->videoBuffer)
-  {
-    CLog::Log(LOGERROR, "CDVDVideoCodecDRMPRIME::{} - videoBuffer:nullptr format:{}", __FUNCTION__,
-              av_get_pix_fmt_name(static_cast<AVPixelFormat>(m_pFrame->format)));
-    av_frame_unref(m_pFrame);
+  if (!SetPictureParams(pVideoPicture))
     return VC_ERROR;
-  }
 
   return VC_PICTURE;
 }
diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecDRMPRIME.h b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecDRMPRIME.h
index b5cacf1a3c..fab3431d40 100644
--- a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecDRMPRIME.h
+++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecDRMPRIME.h
@@ -38,19 +38,26 @@ public:
 
 protected:
   void Drain();
-  void SetPictureParams(VideoPicture* pVideoPicture);
+  bool SetPictureParams(VideoPicture* pVideoPicture);
   void UpdateProcessInfo(struct AVCodecContext* avctx, const enum AVPixelFormat fmt);
   CDVDVideoCodec::VCReturn ProcessFilterIn();
   CDVDVideoCodec::VCReturn ProcessFilterOut();
   static enum AVPixelFormat GetFormat(struct AVCodecContext* avctx, const enum AVPixelFormat* fmt);
   static int GetBuffer(struct AVCodecContext* avctx, AVFrame* frame, int flags);
+  bool FilterOpen(const std::string& filters, bool test);
+  void FilterClose();
+  void FilterTest();
+  std::string GetFilterChain(bool interlaced);
 
   std::string m_name;
+  std::string m_deintFilterName;
+  std::string m_filters;
   int m_codecControlFlags = 0;
   CDVDStreamInfo m_hints;
   double m_DAR = 1.0;
   AVCodecContext* m_pCodecContext = nullptr;
   AVFrame* m_pFrame = nullptr;
+  AVFrame* m_pFilterFrame = nullptr;
   AVFilterGraph* m_pFilterGraph = nullptr;
   AVFilterContext* m_pFilterIn = nullptr;
   AVFilterContext* m_pFilterOut = nullptr;
-- 
2.39.2

