Browse Source

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 years ago
parent
commit
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;