CodecsComponent.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623
  1. #include "CodecsComponent.h"
  2. #include <QString>
  3. #include <Qt>
  4. #include <QDir>
  5. #include <QDomAttr>
  6. #include <QDomDocument>
  7. #include <QDomNode>
  8. #include <QCoreApplication>
  9. #include <QUuid>
  10. #include <QUrl>
  11. #include <QUrlQuery>
  12. #include "system/SystemComponent.h"
  13. #include "settings/SettingsComponent.h"
  14. #include "utils/Utils.h"
  15. #include "shared/Paths.h"
  16. #include "PlayerComponent.h"
  17. #include "QsLog.h"
  18. #define countof(x) (sizeof(x) / sizeof((x)[0]))
  19. // For QVariant. Mysteriously makes Qt happy.
  20. Q_DECLARE_METATYPE(CodecDriver);
  21. #ifdef HAVE_CODEC_MANIFEST
  22. #include "CodecManifest.h"
  23. #else
  24. #define CODEC_VERSION "dummy"
  25. #define SHLIB_PREFIX ""
  26. #define SHLIB_EXTENSION "dummy"
  27. // Codec.name is the name of the codec implementation, Codec.codecName the name of the codec
  28. struct Codec {const char* name; const char* codecName; const char* profiles; int external;};
  29. static const Codec Decoders[] = {
  30. {"dummy", "dummy", nullptr, 1},
  31. };
  32. static const Codec Encoders[] = {
  33. {"dummy", "dummy", nullptr, 1},
  34. };
  35. #endif
  36. // We might want to use Codec.quality to decide this one day.
  37. // But for now, it's better if we can quickly change these.
  38. static QSet<QString> g_systemVideoDecoderWhitelist = {
  39. // definitely work
  40. "h264_mmal",
  41. "mpeg2_mmal",
  42. "mpeg4_mmal",
  43. "vc1_mmal",
  44. // still sketchy at best, partially broken
  45. "h264_mf",
  46. "hevc_mf",
  47. "vc1_mf",
  48. "wmv1_mf",
  49. "wmv2_mf",
  50. "wmv3_mf",
  51. "mpeg4_mf",
  52. "msmpeg4v1_mf",
  53. "msmpeg4v2_mf",
  54. "msmpeg4v3_mf",
  55. };
  56. static QSet<QString> g_systemAudioDecoderWhitelist = {
  57. // should work well
  58. "aac_at",
  59. "ac3_at",
  60. "mp1_at",
  61. "mp2_at",
  62. "mp3_at",
  63. "ac3_mf",
  64. "eac3_mf",
  65. "aac_mf",
  66. "mp1_mf",
  67. "mp2_mf",
  68. "mp3_mf",
  69. };
  70. static QSet<QString> g_systemAudioEncoderWhitelist = {
  71. };
  72. static QString g_codecVersion;
  73. static QList<CodecDriver> g_cachedCodecList;
  74. ///////////////////////////////////////////////////////////////////////////////////////////////////
  75. static QString getBuildType()
  76. {
  77. #ifdef Q_OS_MAC
  78. return "darwin-x86_64";
  79. #else
  80. return SystemComponent::Get().getPlatformTypeString() + "-" +
  81. SystemComponent::Get().getPlatformArchString();
  82. #endif
  83. }
  84. ///////////////////////////////////////////////////////////////////////////////////////////////////
  85. QString Codecs::plexNameToFF(QString plex)
  86. {
  87. if (plex == "dca")
  88. return "dts";
  89. return plex;
  90. }
  91. ///////////////////////////////////////////////////////////////////////////////////////////////////
  92. static QString codecsRootPath()
  93. {
  94. return Paths::dataDir("codecs") + QDir::separator();
  95. }
  96. ///////////////////////////////////////////////////////////////////////////////////////////////////
  97. static QString codecsPath()
  98. {
  99. return codecsRootPath() + getBuildType() + "-" + g_codecVersion + QDir::separator();
  100. }
  101. ///////////////////////////////////////////////////////////////////////////////////////////////////
  102. static int indexOfCodecInList(const QList<CodecDriver>& list, const CodecDriver& codec)
  103. {
  104. for (int n = 0; n < list.size(); n++)
  105. {
  106. if (Codecs::sameCodec(list[n], codec))
  107. return n;
  108. }
  109. return -1;
  110. }
  111. ///////////////////////////////////////////////////////////////////////////////////////////////////
  112. void Codecs::updateCachedCodecList()
  113. {
  114. g_cachedCodecList.clear();
  115. for (CodecType type : {CodecType::Decoder, CodecType::Encoder})
  116. {
  117. const Codec* list = (type == CodecType::Decoder) ? Decoders : Encoders;
  118. size_t count = (type == CodecType::Decoder) ? countof(Decoders) : countof(Encoders);
  119. for (size_t i = 0; i < count; i++)
  120. {
  121. CodecDriver codec = {};
  122. codec.type = type;
  123. codec.format = Codecs::plexNameToFF(list[i].codecName);
  124. codec.driver = list[i].name;
  125. codec.external = list[i].external;
  126. if (!codec.isSystemCodec())
  127. g_cachedCodecList.append(codec);
  128. }
  129. }
  130. // Set present flag for the installed codecs. Also, there could be codecs not
  131. // on the CodecManifest.h list (system codecs, or when compiled without
  132. // codec loading).
  133. QList<CodecDriver> installed = PlayerComponent::Get().installedCodecs();
  134. // Surely O(n^2) won't be causing trouble, right?
  135. for (const CodecDriver& installedCodec : installed)
  136. {
  137. int index = indexOfCodecInList(g_cachedCodecList, installedCodec);
  138. if (index >= 0)
  139. g_cachedCodecList[index].present = true;
  140. else
  141. g_cachedCodecList.append(installedCodec);
  142. }
  143. }
  144. ///////////////////////////////////////////////////////////////////////////////////////////////////
  145. const QList<CodecDriver>& Codecs::getCachecCodecList()
  146. {
  147. return g_cachedCodecList;
  148. }
  149. ///////////////////////////////////////////////////////////////////////////////////////////////////
  150. QList<CodecDriver> Codecs::findCodecsByFormat(const QList<CodecDriver>& list, CodecType type, const QString& format)
  151. {
  152. QList<CodecDriver> result;
  153. for (const CodecDriver& codec : list)
  154. {
  155. if (codec.type == type && codec.format == format)
  156. result.append(codec);
  157. }
  158. return result;
  159. }
  160. ///////////////////////////////////////////////////////////////////////////////////////////////////
  161. QString CodecDriver::getMangledName() const
  162. {
  163. return driver + (type == CodecType::Decoder ? "_decoder" : "_encoder");
  164. }
  165. ///////////////////////////////////////////////////////////////////////////////////////////////////
  166. QString CodecDriver::getFileName() const
  167. {
  168. return SHLIB_PREFIX + getMangledName() + "-" + g_codecVersion + "-" + getBuildType() + "." + SHLIB_EXTENSION;
  169. }
  170. ///////////////////////////////////////////////////////////////////////////////////////////////////
  171. QString CodecDriver::getPath() const
  172. {
  173. return QDir(codecsPath()).absoluteFilePath(getFileName());
  174. }
  175. ///////////////////////////////////////////////////////////////////////////////////////////////////
  176. bool CodecDriver::isSystemCodec() const
  177. {
  178. // MS Windows
  179. if (driver.endsWith("_mf"))
  180. return true;
  181. // OSX
  182. if (driver.endsWith("_at"))
  183. return true;
  184. // Linux on RPI
  185. if (driver.endsWith("_mmal"))
  186. return true;
  187. return false;
  188. }
  189. ///////////////////////////////////////////////////////////////////////////////////////////////////
  190. bool CodecDriver::isWhitelistedSystemAudioCodec() const
  191. {
  192. if (type == CodecType::Decoder)
  193. return g_systemAudioDecoderWhitelist.contains(driver);
  194. else
  195. return g_systemAudioEncoderWhitelist.contains(driver);
  196. }
  197. ///////////////////////////////////////////////////////////////////////////////////////////////////
  198. bool CodecDriver::isWhitelistedSystemVideoCodec() const
  199. {
  200. if (type == CodecType::Decoder)
  201. return g_systemVideoDecoderWhitelist.contains(driver);
  202. return false;
  203. }
  204. ///////////////////////////////////////////////////////////////////////////////////////////////////
  205. // Returns "" on error.
  206. static QString getDeviceID()
  207. {
  208. QFile path(QDir(codecsRootPath()).absoluteFilePath(".device-id"));
  209. if (path.exists())
  210. {
  211. // TODO: Would fail consistently if the file is not readable. Should a new ID be generated?
  212. // What should we do if the file contains binary crap, not a text UUID?
  213. path.open(QFile::ReadOnly);
  214. return QString::fromLatin1(path.readAll());
  215. }
  216. QString newUuid = QUuid::createUuid().toString();
  217. // The UUID should be e.g. "8f6ad954-0cb9-4dbb-a5e5-e0b085f07cf8"
  218. if (newUuid.startsWith("{"))
  219. newUuid = newUuid.mid(1);
  220. if (newUuid.endsWith("}"))
  221. newUuid = newUuid.mid(0, newUuid.size() - 1);
  222. if (!Utils::safelyWriteFile(path.fileName(), newUuid.toLatin1()))
  223. return "";
  224. return newUuid;
  225. }
  226. ///////////////////////////////////////////////////////////////////////////////////////////////////
  227. static QString getFFmpegVersion()
  228. {
  229. auto mpv = mpv::qt::Handle::FromRawHandle(mpv_create());
  230. if (!mpv)
  231. return "";
  232. if (mpv_initialize(mpv) < 0)
  233. return "";
  234. return mpv::qt::get_property_variant(mpv, "ffmpeg-version").toString();
  235. }
  236. ///////////////////////////////////////////////////////////////////////////////////////////////////
  237. void Codecs::preinitCodecs()
  238. {
  239. // Extract the CI codecs version we set with --extra-version when compiling FFmpeg.
  240. QString ffmpegVersion = getFFmpegVersion();
  241. int sep = ffmpegVersion.indexOf(',');
  242. if (sep >= 0)
  243. g_codecVersion = ffmpegVersion.mid(sep + 1);
  244. else
  245. g_codecVersion = CODEC_VERSION;
  246. QString path = codecsPath();
  247. QDir("").mkpath(path);
  248. // Follows the convention used by av_get_token().
  249. QString escapedPath = path.replace("\\", "\\\\").replace(":", "\\:");
  250. // This must be run before any threads are started etc. (for safety).
  251. #ifdef Q_OS_WIN
  252. SetEnvironmentVariableW(L"FFMPEG_EXTERNAL_LIBS", escapedPath.toStdWString().c_str());
  253. #else
  254. qputenv("FFMPEG_EXTERNAL_LIBS", escapedPath.toUtf8().data());
  255. #endif
  256. getDeviceID();
  257. }
  258. ///////////////////////////////////////////////////////////////////////////////////////////////////
  259. bool CodecsFetcher::codecNeedsDownload(const CodecDriver& codec)
  260. {
  261. if (codec.present)
  262. return false;
  263. if (!codec.external)
  264. {
  265. QLOG_ERROR() << "Codec" << codec.driver << "does not exist and is not downloadable.";
  266. return false;
  267. }
  268. for (int n = 0; n < m_Codecs.size(); n++)
  269. {
  270. if (Codecs::sameCodec(codec, m_Codecs[n]))
  271. return false;
  272. }
  273. if (QFile(codec.getPath()).exists())
  274. {
  275. QLOG_ERROR() << "Codec" << codec.driver << "exists on disk as" << codec.getPath()
  276. << "but is not known as installed - broken codec? Skipping download.";
  277. return false;
  278. }
  279. return true;
  280. }
  281. ///////////////////////////////////////////////////////////////////////////////////////////////////
  282. void CodecsFetcher::installCodecs(const QList<CodecDriver>& codecs)
  283. {
  284. foreach (CodecDriver codec, codecs)
  285. {
  286. if (codecNeedsDownload(codec))
  287. m_Codecs.enqueue(codec);
  288. }
  289. startNext();
  290. }
  291. ///////////////////////////////////////////////////////////////////////////////////////////////////
  292. static Downloader::HeaderList getPlexHeaders()
  293. {
  294. Downloader::HeaderList headers;
  295. QString auth = SystemComponent::Get().authenticationToken();
  296. if (auth.size())
  297. headers.append({"X-Plex-Token", auth});
  298. headers.append({"X-Plex-Product", "Plex Media Player"});
  299. headers.append({"X-Plex-Platform", "Konvergo"});
  300. return headers;
  301. }
  302. ///////////////////////////////////////////////////////////////////////////////////////////////////
  303. void CodecsFetcher::startNext()
  304. {
  305. if (m_Codecs.isEmpty())
  306. {
  307. emit done(this);
  308. return;
  309. }
  310. CodecDriver codec = m_Codecs.dequeue();
  311. QString host = "https://plex.tv";
  312. QUrl url = QUrl(host + "/api/codecs/" + codec.getMangledName());
  313. QUrlQuery query;
  314. query.addQueryItem("deviceId", getDeviceID());
  315. query.addQueryItem("version", g_codecVersion);
  316. query.addQueryItem("build", getBuildType());
  317. url.setQuery(query);
  318. QLOG_INFO() << "Codec info request:" << url.toString();
  319. Downloader *downloader = new Downloader(QVariant::fromValue(codec), url, getPlexHeaders(), this);
  320. connect(downloader, &Downloader::done, this, &CodecsFetcher::codecInfoDownloadDone);
  321. }
  322. ///////////////////////////////////////////////////////////////////////////////////////////////////
  323. bool CodecsFetcher::processCodecInfoReply(const QByteArray& data, const CodecDriver& codec)
  324. {
  325. QLOG_INFO() << "Got reply:" << QString::fromUtf8(data);
  326. QDomDocument dom;
  327. if (!dom.setContent(data))
  328. {
  329. QLOG_ERROR() << "XML parsing error.";
  330. return false;
  331. }
  332. QDomNodeList list = dom.elementsByTagName("MediaContainer");
  333. if (list.count() != 1)
  334. {
  335. QLOG_ERROR() << "MediaContainer XML element not found.";
  336. return false;
  337. }
  338. list = dom.elementsByTagName("Codec");
  339. if (list.count() != 1)
  340. {
  341. QLOG_ERROR() << "Codec XML element not found.";
  342. return false;
  343. }
  344. QDomNamedNodeMap attrs = list.at(0).attributes();
  345. QString url = attrs.namedItem("url").toAttr().value();
  346. if (!url.size())
  347. {
  348. QLOG_ERROR() << "No URL found.";
  349. return false;
  350. }
  351. QString hash = attrs.namedItem("fileSha").toAttr().value();
  352. m_currentHash = QByteArray::fromHex(hash.toUtf8());
  353. // it's hardcoded to SHA-1
  354. if (!m_currentHash.size()) {
  355. QLOG_ERROR() << "Hash value in unexpected format or missing:" << hash;
  356. return false;
  357. }
  358. QLOG_INFO() << "Downloading codec:" << url;
  359. Downloader *downloader = new Downloader(QVariant::fromValue(codec), url, getPlexHeaders(), this);
  360. connect(downloader, &Downloader::done, this, &CodecsFetcher::codecDownloadDone);
  361. return true;
  362. }
  363. ///////////////////////////////////////////////////////////////////////////////////////////////////
  364. void CodecsFetcher::codecInfoDownloadDone(QVariant userData, bool success, const QByteArray& data)
  365. {
  366. CodecDriver codec = userData.value<CodecDriver>();
  367. if (!success || !processCodecInfoReply(data, codec))
  368. {
  369. QLOG_ERROR() << "Codec download failed.";
  370. startNext();
  371. }
  372. }
  373. ///////////////////////////////////////////////////////////////////////////////////////////////////
  374. void CodecsFetcher::processCodecDownloadDone(const QByteArray& data, const CodecDriver& codec)
  375. {
  376. QByteArray hash = QCryptographicHash::hash(data, QCryptographicHash::Sha1);
  377. if (hash != m_currentHash)
  378. {
  379. QLOG_ERROR() << "Checksum mismatch: got" << hash.toHex() << "expected" << m_currentHash.toHex();
  380. return;
  381. }
  382. if (!Utils::safelyWriteFile(codec.getPath(), data))
  383. {
  384. QLOG_ERROR() << "Writing codec file failed.";
  385. return;
  386. }
  387. // This causes libmpv and eventually libavcodec to rescan and load new codecs.
  388. Codecs::updateCachedCodecList();
  389. for (const CodecDriver& item : Codecs::getCachecCodecList())
  390. {
  391. if (Codecs::sameCodec(item, codec) && !item.present)
  392. {
  393. QLOG_ERROR() << "Codec could not be loaded after installing it.";
  394. return;
  395. }
  396. }
  397. QLOG_INFO() << "Codec download and installation succeeded.";
  398. }
  399. ///////////////////////////////////////////////////////////////////////////////////////////////////
  400. void CodecsFetcher::codecDownloadDone(QVariant userData, bool success, const QByteArray& data)
  401. {
  402. CodecDriver codec = userData.value<CodecDriver>();
  403. QLOG_INFO() << "Codec" << codec.driver << "request finished.";
  404. if (success)
  405. {
  406. processCodecDownloadDone(data, codec);
  407. }
  408. else
  409. {
  410. QLOG_ERROR() << "Codec download HTTP request failed.";
  411. }
  412. startNext();
  413. }
  414. ///////////////////////////////////////////////////////////////////////////////////////////////////
  415. Downloader::Downloader(QVariant userData, const QUrl& url, const HeaderList& headers, QObject* parent)
  416. : QObject(parent), m_userData(userData)
  417. {
  418. connect(&m_WebCtrl, &QNetworkAccessManager::finished, this, &Downloader::networkFinished);
  419. QNetworkRequest request(url);
  420. for (int n = 0; n < headers.size(); n++)
  421. request.setRawHeader(headers[n].first.toUtf8(), headers[n].second.toUtf8());
  422. m_WebCtrl.get(request);
  423. }
  424. ///////////////////////////////////////////////////////////////////////////////////////////////////
  425. void Downloader::networkFinished(QNetworkReply* pReply)
  426. {
  427. if (pReply->error() == QNetworkReply::NoError)
  428. {
  429. emit done(m_userData, true, pReply->readAll());
  430. }
  431. else
  432. {
  433. emit done(m_userData, false, QByteArray());
  434. }
  435. pReply->deleteLater();
  436. m_WebCtrl.clearAccessCache(); // make sure the TCP connection is closed
  437. }
  438. ///////////////////////////////////////////////////////////////////////////////////////////////////
  439. static bool useSystemAudioDecoders()
  440. {
  441. return SettingsComponent::Get().value(SETTINGS_SECTION_MAIN, "useSystemAudioCodecs2").toBool();
  442. }
  443. ///////////////////////////////////////////////////////////////////////////////////////////////////
  444. static bool useSystemVideoDecoders()
  445. {
  446. return SettingsComponent::Get().value(SETTINGS_SECTION_MAIN, "useSystemVideoCodecs").toBool();
  447. }
  448. ///////////////////////////////////////////////////////////////////////////////////////////////////
  449. static CodecDriver selectBestDecoder(const StreamInfo& stream)
  450. {
  451. QList<CodecDriver> codecs = Codecs::findCodecsByFormat(Codecs::getCachecCodecList(), CodecType::Decoder, stream.codec);
  452. CodecDriver best = {};
  453. int bestScore = -1;
  454. for (auto codec : codecs)
  455. {
  456. int score = -1;
  457. if (codec.isSystemCodec())
  458. {
  459. // we always want to avoid using non-whitelisted system codecs
  460. // on the other hand, always prefer whitelisted system codecs
  461. if ((codec.isWhitelistedSystemAudioCodec() && useSystemAudioDecoders()) ||
  462. (codec.isWhitelistedSystemVideoCodec() && useSystemVideoDecoders()))
  463. score = 15;
  464. }
  465. else
  466. {
  467. // prefer codecs which do not have to be downloaded over others
  468. if (codec.present)
  469. score = 10;
  470. else
  471. score = 5;
  472. }
  473. if (score > bestScore)
  474. {
  475. best = codec;
  476. bestScore = score;
  477. }
  478. }
  479. return best;
  480. }
  481. ///////////////////////////////////////////////////////////////////////////////////////////////////
  482. QList<CodecDriver> Codecs::determineRequiredCodecs(const PlaybackInfo& info)
  483. {
  484. QList<CodecDriver> result;
  485. bool needAC3Encoder = false;
  486. QLOG_INFO() << "Using system audio decoders:" << useSystemAudioDecoders();
  487. QLOG_INFO() << "Using system video decoders:" << useSystemVideoDecoders();
  488. #if !defined(HAVE_CODEC_MANIFEST)
  489. QLOG_INFO() << "Not using on-demand codecs.";
  490. #endif
  491. for (auto stream : info.streams)
  492. {
  493. if (!stream.isVideo && !stream.isAudio)
  494. continue;
  495. if (!stream.codec.size())
  496. {
  497. QLOG_ERROR() << "unidentified codec";
  498. continue;
  499. }
  500. // We could do this if we'd find a nice way to enable passthrough by default:
  501. #if 0
  502. // Can passthrough be used? If so, don't request a codec.
  503. if (info.audioPassthroughCodecs.contains(stream.codec))
  504. continue;
  505. #endif
  506. // (Would be nice to check audioChannels here to not request the encoder
  507. // when playing stereo - but unfortunately, the ac3 encoder is loaded first,
  508. // and only removed when detecting stereo input)
  509. if (info.enableAC3Transcoding)
  510. needAC3Encoder = true;
  511. CodecDriver best = selectBestDecoder(stream);
  512. if (best.valid())
  513. {
  514. result.append(best);
  515. }
  516. else
  517. {
  518. QLOG_ERROR() << "no decoder for" << stream.codec;
  519. }
  520. }
  521. if (needAC3Encoder)
  522. {
  523. QList<CodecDriver> codecs = Codecs::findCodecsByFormat(Codecs::getCachecCodecList(), CodecType::Encoder, "ac3");
  524. CodecDriver encoder = {};
  525. for (auto codec : codecs)
  526. {
  527. if (codec.present && (!codec.isSystemCodec() || codec.isWhitelistedSystemAudioCodec()))
  528. {
  529. encoder = codec;
  530. break;
  531. }
  532. if (codec.external)
  533. encoder = codec; // fallback
  534. }
  535. if (encoder.valid())
  536. {
  537. result.append(encoder);
  538. }
  539. else
  540. {
  541. QLOG_ERROR() << "no AC3 encoder available";
  542. }
  543. }
  544. return result;
  545. }