소스 검색

Add system codecs support

While they mostly work automagically, we still have to make sure they
interact with codec downloading well. In particular, it could happen
that we prefer a downloadable codec over a system one, depending on
the media. We also only allow using system codecs which are known
working.
Vincent Lang 9 년 전
부모
커밋
2a046e91f1

+ 1 - 1
CMakeModules/FetchDependencies.cmake

@@ -20,7 +20,7 @@ option(ENABLE_CODECS "Enable CodecManifest downloading for Codecs on Demand" OFF
 if(ENABLE_CODECS)
   add_definitions(-DHAVE_CODEC_MANIFEST)
 
-  set(CODECS_BUILD_NUMBER 98)
+  set(CODECS_BUILD_NUMBER 117)
   message(STATUS "Downloading https://nightlies.plex.tv/codecs/${CODECS_BUILD_NUMBER}/CodecManifest-${ARCHSTR}.h")
   file(
     DOWNLOAD https://nightlies.plex.tv/codecs/${CODECS_BUILD_NUMBER}/CodecManifest-${ARCHSTR}.h  ${CMAKE_CURRENT_BINARY_DIR}/src/CodecManifest.h

+ 21 - 0
resources/settings/settings_description.json

@@ -98,6 +98,27 @@
           ["yes", "yes"],
           ["minimized", "minimized"]
         ]
+      },
+      {
+        // Warning: we should make this hidden or remove it later on
+        "value": "useSystemVideoCodecs",
+        "default": [
+          {
+            "value": true,
+            // MMAL works just fine
+            "platforms": [ "oe" ]
+          },
+          {
+            "value": false
+          }
+        ],
+        "hidden": false
+      },
+      {
+        // Warning: we should make this hidden or remove it later on
+        "value": "useSystemAudioCodecs2",
+        "default": true,
+        "hidden": false
       }
     ]
   },

+ 120 - 12
src/player/CodecsComponent.cpp

@@ -10,6 +10,7 @@
 #include <QUrl>
 #include <QUrlQuery>
 #include "system/SystemComponent.h"
+#include "settings/SettingsComponent.h"
 #include "utils/Utils.h"
 #include "shared/Paths.h"
 #include "PlayerComponent.h"
@@ -37,6 +38,45 @@ static const Codec Encoders[] = {
 };
 #endif
 
+// We might want to use Codec.quality to decide this one day.
+// But for now, it's better if we can quickly change these.
+static QSet<QString> g_systemVideoDecoderWhitelist = {
+  // definitely work
+  "h264_mmal",
+  "mpeg2_mmal",
+  "mpeg4_mmal",
+  "vc1_mmal",
+  // still sketchy at best, partially broken
+  "h264_mf",
+  "hevc_mf",
+  "vc1_mf",
+  "wmv1_mf",
+  "wmv2_mf",
+  "wmv3_mf",
+  "mpeg4_mf",
+  "msmpeg4v1_mf",
+  "msmpeg4v2_mf",
+  "msmpeg4v3_mf",
+};
+
+static QSet<QString> g_systemAudioDecoderWhitelist = {
+  // should work well
+  "aac_at",
+  "ac3_at",
+  "mp1_at",
+  "mp2_at",
+  "mp3_at",
+  "ac3_mf",
+  "eac3_mf",
+  "aac_mf",
+  "mp1_mf",
+  "mp2_mf",
+  "mp3_mf",
+};
+
+static QSet<QString> g_systemAudioEncoderWhitelist = {
+};
+
 static QString g_codecVersion;
 static QList<CodecDriver> g_cachedCodecList;
 
@@ -99,7 +139,8 @@ void Codecs::updateCachedCodecList()
       codec.format = plexNameToFF(list[i].codecName);
       codec.driver = list[i].name;
       codec.external = list[i].external;
-      g_cachedCodecList.append(codec);
+      if (!codec.isSystemCodec())
+        g_cachedCodecList.append(codec);
     }
   }
 
@@ -156,6 +197,38 @@ QString CodecDriver::getPath() const
   return QDir(codecsPath()).absoluteFilePath(getFileName());
 }
 
+///////////////////////////////////////////////////////////////////////////////////////////////////
+bool CodecDriver::isSystemCodec() const
+{
+  // MS Windows
+  if (driver.endsWith("_mf"))
+    return true;
+  // OSX
+  if (driver.endsWith("_at"))
+    return true;
+  // Linux on RPI
+  if (driver.endsWith("_mmal"))
+    return true;
+  return false;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+bool CodecDriver::isWhitelistedSystemAudioCodec() const
+{
+  if (type == CodecType::Decoder)
+    return g_systemAudioDecoderWhitelist.contains(driver);
+  else
+    return g_systemAudioEncoderWhitelist.contains(driver);
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+bool CodecDriver::isWhitelistedSystemVideoCodec() const
+{
+  if (type == CodecType::Decoder)
+    return g_systemVideoDecoderWhitelist.contains(driver);
+  return false;
+}
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 // Returns "" on error.
 static QString getDeviceID()
@@ -427,23 +500,51 @@ void Downloader::networkFinished(QNetworkReply* pReply)
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
-static CodecDriver selectBestCodec(const StreamInfo& stream)
+static bool useSystemAudioDecoders()
+{
+  return SettingsComponent::Get().value(SETTINGS_SECTION_MAIN, "useSystemAudioCodecs2").toBool();
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+static bool useSystemVideoDecoders()
+{
+  return SettingsComponent::Get().value(SETTINGS_SECTION_MAIN, "useSystemVideoCodecs").toBool();
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+static CodecDriver selectBestDecoder(const StreamInfo& stream)
 {
   QList<CodecDriver> codecs = Codecs::findCodecsByFormat(Codecs::getCachecCodecList(), CodecType::Decoder, stream.codec);
-  CodecDriver best = {}, secondBest = {};
-  // For now, pick the first working one, or the first installable one.
-  // In future, it might need to be more clever.
+  CodecDriver best = {};
+  int bestScore = -1;
   for (auto codec : codecs)
   {
-    if (codec.present)
+    int score = -1;
+
+    if (codec.isSystemCodec())
+    {
+      // we always want to avoid using non-whitelisted system codecs
+      // on the other hand, always prefer whitelisted system codecs
+      if ((codec.isWhitelistedSystemAudioCodec() && useSystemAudioDecoders()) ||
+          (codec.isWhitelistedSystemVideoCodec() && useSystemVideoDecoders()))
+        score = 15;
+    }
+    else
+    {
+      // prefer codecs which do not have to be downloaded over others
+      if (codec.present)
+        score = 10;
+      else
+        score = 5;
+    }
+
+    if (score > bestScore)
     {
       best = codec;
-      break;
+      bestScore = score;
     }
-    if (!secondBest.valid())
-      secondBest = codec;
   }
-  return best.valid() ? best : secondBest;
+  return best;
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -453,6 +554,13 @@ QList<CodecDriver> Codecs::determineRequiredCodecs(const PlaybackInfo& info)
 
   bool needAC3Encoder = false;
 
+  QLOG_INFO() << "Using system audio decoders:" << useSystemAudioDecoders();
+  QLOG_INFO() << "Using system video decoders:" << useSystemVideoDecoders();
+
+#if !defined(HAVE_CODEC_MANIFEST)
+  QLOG_INFO() << "Not using on-demand codecs.";
+#endif
+
   for (auto stream : info.streams)
   {
     if (!stream.isVideo && !stream.isAudio)
@@ -476,7 +584,7 @@ QList<CodecDriver> Codecs::determineRequiredCodecs(const PlaybackInfo& info)
     if (info.enableAC3Transcoding)
       needAC3Encoder = true;
 
-    CodecDriver best = selectBestCodec(stream);
+    CodecDriver best = selectBestDecoder(stream);
     if (best.valid())
     {
       result.append(best);
@@ -493,7 +601,7 @@ QList<CodecDriver> Codecs::determineRequiredCodecs(const PlaybackInfo& info)
     CodecDriver encoder = {};
     for (auto codec : codecs)
     {
-      if (codec.present)
+      if (codec.present && (!codec.isSystemCodec() || codec.isWhitelistedSystemAudioCodec()))
       {
         encoder = codec;
         break;

+ 9 - 0
src/player/CodecsComponent.h

@@ -36,6 +36,15 @@ struct CodecDriver {
   // Only applies to external drivers.
   QString getPath() const;
 
+  // Return whether the codec is provided by the OS or the hardware. They are
+  // distinct from FFmpeg-native codecs and usually have weaker capabilities.
+  // While they are also always available, they might fail in various ways
+  // depending on input media and OS version.
+  bool isSystemCodec() const;
+
+  bool isWhitelistedSystemAudioCodec() const;
+  bool isWhitelistedSystemVideoCodec() const;
+
   bool valid() { return format.size() > 0; }
 };
 

+ 23 - 0
src/player/PlayerComponent.cpp

@@ -265,6 +265,10 @@ void PlayerComponent::queueMedia(const QString& url, const QVariantMap& options,
   if (userAgent.size())
     extraArgs.insert("user-agent", userAgent);
 
+  // Make sure the list of requested codecs is reset.
+  extraArgs.insert("ad", "");
+  extraArgs.insert("vd", "");
+
   command << extraArgs;
 
   QLOG_DEBUG() << command;
@@ -959,6 +963,24 @@ PlaybackInfo PlayerComponent::getPlaybackInfo()
   return info;
 }
 
+/////////////////////////////////////////////////////////////////////////////////////////
+void PlayerComponent::setPreferredCodecs(const QList<CodecDriver>& codecs)
+{
+  QStringList items;
+  for (auto codec : codecs)
+  {
+    if (codec.type == CodecType::Decoder)
+    {
+      items << QString("lavc:") + codec.driver;
+    }
+  }
+  QString opt = items.join(",");
+  // For simplicity, we don't distinguish between audio and video. The player
+  // will ignore entries with mismatching media type.
+  mpv::qt::set_option_variant(m_mpv, "ad", opt);
+  mpv::qt::set_option_variant(m_mpv, "vd", opt);
+}
+
 // For QVariant.
 Q_DECLARE_METATYPE(std::function<void()>);
 
@@ -970,6 +992,7 @@ void PlayerComponent::startCodecsLoading(std::function<void()> resume)
   connect(fetcher, &CodecsFetcher::done, this, &PlayerComponent::onCodecsLoadingDone);
   Codecs::updateCachedCodecList();
   QList<CodecDriver> codecs = Codecs::determineRequiredCodecs(getPlaybackInfo());
+  setPreferredCodecs(codecs);
   fetcher->installCodecs(codecs);
 }
 

+ 4 - 0
src/player/PlayerComponent.h

@@ -183,6 +183,10 @@ private:
   void appendAudioFormat(QTextStream& info, const QString& property) const;
   void initializeCodecSupport();
   PlaybackInfo getPlaybackInfo();
+  // Make the player prefer certain codecs over others.
+  void setPreferredCodecs(const QList<CodecDriver>& codecs);
+  // Determine the required codecs and possibly download them.
+  // Call resume() when done.
   void startCodecsLoading(std::function<void()> resume);
 
   mpv::qt::Handle m_mpv;