CodecsComponent.cpp 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855
  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 <QResource>
  13. #include <QStandardPaths>
  14. #include <QSysInfo>
  15. #include <QCryptographicHash>
  16. #include "system/SystemComponent.h"
  17. #include "settings/SettingsComponent.h"
  18. #include "utils/Utils.h"
  19. #include "shared/Paths.h"
  20. #include "PlayerComponent.h"
  21. #include "QsLog.h"
  22. #define countof(x) (sizeof(x) / sizeof((x)[0]))
  23. // For QVariant. Mysteriously makes Qt happy.
  24. Q_DECLARE_METATYPE(CodecDriver);
  25. #ifdef HAVE_CODEC_MANIFEST
  26. #include "CodecManifest.h"
  27. #else
  28. #define CODEC_VERSION "dummy"
  29. #define SHLIB_PREFIX ""
  30. #define SHLIB_EXTENSION "dummy"
  31. // Codec.name is the name of the codec implementation, Codec.codecName the name of the codec
  32. struct Codec {const char* name; const char* codecName; const char* profiles; int external;};
  33. static const Codec Decoders[] = {
  34. {"dummy", "dummy", nullptr, 1},
  35. };
  36. static const Codec Encoders[] = {
  37. {"dummy", "dummy", nullptr, 1},
  38. };
  39. #endif
  40. // We might want to use Codec.quality to decide this one day.
  41. // But for now, it's better if we can quickly change these.
  42. static QSet<QString> g_systemVideoDecoderWhitelist = {
  43. // RPI
  44. "h264_mmal",
  45. "mpeg2_mmal",
  46. "mpeg4_mmal",
  47. "vc1_mmal",
  48. // Windows
  49. "h264_mf",
  50. "hevc_mf",
  51. "vc1_mf",
  52. "wmv1_mf",
  53. "wmv2_mf",
  54. "wmv3_mf",
  55. "mpeg2_mf",
  56. "mpeg4_mf",
  57. "msmpeg4v1_mf",
  58. "msmpeg4v2_mf",
  59. "msmpeg4v3_mf",
  60. };
  61. static QSet<QString> g_systemAudioDecoderWhitelist = {
  62. // OSX
  63. "aac_at",
  64. "ac3_at",
  65. "eac3_at",
  66. "mp1_at",
  67. "mp2_at",
  68. "mp3_at",
  69. // Windows
  70. "ac3_mf",
  71. "eac3_mf",
  72. "aac_mf",
  73. "mp1_mf",
  74. "mp2_mf",
  75. "mp3_mf",
  76. };
  77. static QSet<QString> g_systemAudioEncoderWhitelist = {
  78. };
  79. static QSize g_mediaFoundationH264MaxResolution;
  80. static QString g_codecVersion;
  81. static QList<CodecDriver> g_cachedCodecList;
  82. static QString g_deviceID;
  83. ///////////////////////////////////////////////////////////////////////////////////////////////////
  84. static QString getBuildType()
  85. {
  86. #ifdef Q_OS_MAC
  87. return "darwin-x86_64";
  88. #elif defined(TARGET_RPI)
  89. return "openelec-armv7";
  90. #else
  91. return SystemComponent::Get().getPlatformTypeString() + "-" +
  92. SystemComponent::Get().getPlatformArchString();
  93. #endif
  94. }
  95. ///////////////////////////////////////////////////////////////////////////////////////////////////
  96. QString Codecs::plexNameToFF(QString plex)
  97. {
  98. if (plex == "dca")
  99. return "dts";
  100. return plex;
  101. }
  102. ///////////////////////////////////////////////////////////////////////////////////////////////////
  103. QString Codecs::plexNameFromFF(QString ffname)
  104. {
  105. if (ffname == "dts")
  106. return "dca";
  107. return ffname;
  108. }
  109. ///////////////////////////////////////////////////////////////////////////////////////////////////
  110. static QString codecsRootPath()
  111. {
  112. return Paths::dataDir("Codecs") + QDir::separator();
  113. }
  114. ///////////////////////////////////////////////////////////////////////////////////////////////////
  115. static QString codecsPath()
  116. {
  117. return codecsRootPath() + g_codecVersion + "-" + getBuildType() + QDir::separator();
  118. }
  119. ///////////////////////////////////////////////////////////////////////////////////////////////////
  120. static int indexOfCodecInList(const QList<CodecDriver>& list, const CodecDriver& codec)
  121. {
  122. for (int n = 0; n < list.size(); n++)
  123. {
  124. if (Codecs::sameCodec(list[n], codec))
  125. return n;
  126. }
  127. return -1;
  128. }
  129. ///////////////////////////////////////////////////////////////////////////////////////////////////
  130. void Codecs::updateCachedCodecList()
  131. {
  132. g_cachedCodecList.clear();
  133. for (CodecType type : {CodecType::Decoder, CodecType::Encoder})
  134. {
  135. const Codec* list = (type == CodecType::Decoder) ? Decoders : Encoders;
  136. size_t count = (type == CodecType::Decoder) ? countof(Decoders) : countof(Encoders);
  137. for (size_t i = 0; i < count; i++)
  138. {
  139. CodecDriver codec = {};
  140. codec.type = type;
  141. codec.format = Codecs::plexNameToFF(list[i].codecName);
  142. codec.driver = list[i].name;
  143. codec.external = list[i].external;
  144. if (!codec.isSystemCodec())
  145. g_cachedCodecList.append(codec);
  146. }
  147. }
  148. // Set present flag for the installed codecs. Also, there could be codecs not
  149. // on the CodecManifest.h list (system codecs, or when compiled without
  150. // codec loading).
  151. QList<CodecDriver> installed = PlayerComponent::Get().installedCodecDrivers();
  152. // Surely O(n^2) won't be causing trouble, right?
  153. for (const CodecDriver& installedCodec : installed)
  154. {
  155. int index = indexOfCodecInList(g_cachedCodecList, installedCodec);
  156. if (index >= 0)
  157. g_cachedCodecList[index].present = true;
  158. else
  159. g_cachedCodecList.append(installedCodec);
  160. }
  161. }
  162. ///////////////////////////////////////////////////////////////////////////////////////////////////
  163. const QList<CodecDriver>& Codecs::getCachedCodecList()
  164. {
  165. return g_cachedCodecList;
  166. }
  167. ///////////////////////////////////////////////////////////////////////////////////////////////////
  168. QList<CodecDriver> Codecs::findCodecsByFormat(const QList<CodecDriver>& list, CodecType type, const QString& format)
  169. {
  170. QList<CodecDriver> result;
  171. for (const CodecDriver& codec : list)
  172. {
  173. if (codec.type == type && codec.format == format)
  174. result.append(codec);
  175. }
  176. return result;
  177. }
  178. ///////////////////////////////////////////////////////////////////////////////////////////////////
  179. QString CodecDriver::getMangledName() const
  180. {
  181. return driver + (type == CodecType::Decoder ? "_decoder" : "_encoder");
  182. }
  183. ///////////////////////////////////////////////////////////////////////////////////////////////////
  184. QString CodecDriver::getFileName() const
  185. {
  186. return SHLIB_PREFIX + getMangledName() + "." + SHLIB_EXTENSION;
  187. }
  188. ///////////////////////////////////////////////////////////////////////////////////////////////////
  189. QString CodecDriver::getPath() const
  190. {
  191. return QDir(codecsPath()).absoluteFilePath(getFileName());
  192. }
  193. ///////////////////////////////////////////////////////////////////////////////////////////////////
  194. bool CodecDriver::isSystemCodec() const
  195. {
  196. // MS Windows
  197. if (driver.endsWith("_mf"))
  198. return true;
  199. // OSX
  200. if (driver.endsWith("_at"))
  201. return true;
  202. // Linux on RPI
  203. if (driver.endsWith("_mmal"))
  204. return true;
  205. return false;
  206. }
  207. ///////////////////////////////////////////////////////////////////////////////////////////////////
  208. bool CodecDriver::isWhitelistedSystemAudioCodec() const
  209. {
  210. if (type == CodecType::Decoder)
  211. return g_systemAudioDecoderWhitelist.contains(driver);
  212. else
  213. return g_systemAudioEncoderWhitelist.contains(driver);
  214. }
  215. ///////////////////////////////////////////////////////////////////////////////////////////////////
  216. bool CodecDriver::isWhitelistedSystemVideoCodec() const
  217. {
  218. if (type == CodecType::Decoder)
  219. return g_systemVideoDecoderWhitelist.contains(driver);
  220. return false;
  221. }
  222. ///////////////////////////////////////////////////////////////////////////////////////////////////
  223. static bool useSystemAudioDecoders()
  224. {
  225. return true;
  226. }
  227. ///////////////////////////////////////////////////////////////////////////////////////////////////
  228. static bool useSystemVideoDecoders()
  229. {
  230. return SettingsComponent::Get().value(SETTINGS_SECTION_MAIN, "useSystemVideoCodecs").toBool();
  231. }
  232. /////////////////////////////////////////////////////////////////////////////////////////
  233. // Load the device ID, do some minimal verification, return "" on error.
  234. static QString loadDeviceID(QString filename)
  235. {
  236. QFile path(filename);
  237. if (!path.open(QFile::ReadOnly))
  238. return "";
  239. auto res = QString::fromLatin1(path.readAll());
  240. if (res.size() < 32 || res.size() > 512)
  241. res = ""; // mark as invalid
  242. return res;
  243. }
  244. /////////////////////////////////////////////////////////////////////////////////////////
  245. static QString findOldDeviceID()
  246. {
  247. // First we try to reuse the ID from other Plex products (i.e. PMS) or older paths.
  248. QStringList candidates = {
  249. #ifdef Q_OS_MAC
  250. QDir::home().path() + "/Library/Application Support/Plex/Codecs/.device-id",
  251. QDir::home().path() + "/Library/Application Support/Plex Media Server/Codecs/.device-id",
  252. #endif
  253. QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/Plex/Codecs/.device-id",
  254. QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/Plex/codecs/.device-id",
  255. QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/Plex Media Server/Codecs/.device-id",
  256. Paths::dataDir() + "/codecs/.device-id",
  257. };
  258. for (auto candidate : candidates)
  259. {
  260. auto id = loadDeviceID(candidate);
  261. if (!id.isEmpty())
  262. return id;
  263. }
  264. return "";
  265. }
  266. ///////////////////////////////////////////////////////////////////////////////////////////////////
  267. // Returns "" on error.
  268. static QString loadDeviceID()
  269. {
  270. QString deviceIDFilename = QDir(codecsRootPath()).absoluteFilePath(".device-id");
  271. QString id = loadDeviceID(deviceIDFilename);
  272. if (id.isEmpty())
  273. {
  274. id = findOldDeviceID();
  275. if (id.isEmpty())
  276. {
  277. id = QUuid::createUuid().toString();
  278. // The UUID should be e.g. "8f6ad954-0cb9-4dbb-a5e5-e0b085f07cf8"
  279. if (id.startsWith("{"))
  280. id = id.mid(1);
  281. if (id.endsWith("}"))
  282. id = id.mid(0, id.size() - 1);
  283. }
  284. Utils::safelyWriteFile(deviceIDFilename, id.toLatin1());
  285. // We load it again to make sure writing it succeeded. If it doesn't, we'll
  286. // error out at a later point.
  287. id = loadDeviceID(deviceIDFilename);
  288. }
  289. return id;
  290. }
  291. ///////////////////////////////////////////////////////////////////////////////////////////////////
  292. static QString getFFmpegVersion()
  293. {
  294. auto mpv = mpv::qt::Handle::FromRawHandle(mpv_create());
  295. if (!mpv || mpv_initialize(mpv) < 0)
  296. return "";
  297. return mpv::qt::get_property(mpv, "ffmpeg-version").toString();
  298. }
  299. ///////////////////////////////////////////////////////////////////////////////////////////////////
  300. void Codecs::preinitCodecs()
  301. {
  302. // Extract the CI codecs version we set with --extra-version when compiling FFmpeg.
  303. QString ffmpegVersion = getFFmpegVersion();
  304. int sep = ffmpegVersion.indexOf(',');
  305. if (sep >= 0)
  306. g_codecVersion = ffmpegVersion.mid(sep + 1);
  307. else
  308. g_codecVersion = CODEC_VERSION;
  309. QString path = codecsPath();
  310. QDir("").mkpath(path);
  311. // Follows the convention used by av_get_token().
  312. QString escapedPath = path.replace("\\", "\\\\").replace(":", "\\:").replace("'", "\\'");
  313. // This must be run before any threads are started etc. (for safety).
  314. #ifdef Q_OS_WIN
  315. SetEnvironmentVariableW(L"FFMPEG_EXTERNAL_LIBS", escapedPath.toStdWString().c_str());
  316. #else
  317. qputenv("FFMPEG_EXTERNAL_LIBS", escapedPath.toUtf8().data());
  318. #endif
  319. g_deviceID = loadDeviceID();
  320. }
  321. ///////////////////////////////////////////////////////////////////////////////////////////////////
  322. static bool probeDecoder(QString decoder, QString resourceName)
  323. {
  324. QResource resource(resourceName);
  325. QLOG_DEBUG() << "Testing decoding of" << resource.fileName();
  326. if (!resource.isValid())
  327. return false;
  328. auto mpv = mpv::qt::Handle::FromRawHandle(mpv_create());
  329. if (!mpv || mpv_initialize(mpv) < 0)
  330. return false;
  331. // Disable any output.
  332. mpv::qt::set_property(mpv, "vo", "null");
  333. // Force the decoder. The ",-" means that if the first entry fails, the next codec in the global
  334. // codec list will not be tried, and decoding fails.
  335. mpv::qt::set_property(mpv, "vd", "lavc:" + decoder + ",-");
  336. // Attempt decoding, and return success.
  337. auto data = QByteArray::fromRawData((const char *)resource.data(), resource.size());
  338. if (resource.isCompressed())
  339. data = qUncompress(data);
  340. auto hex = data.toHex();
  341. mpv::qt::command(mpv, QVariantList{"loadfile", "hex://" + QString::fromLatin1(hex)});
  342. bool result = false;
  343. while (1) {
  344. mpv_event *event = mpv_wait_event(mpv, 0);
  345. if (event->event_id == MPV_EVENT_SHUTDOWN)
  346. break;
  347. if (event->event_id == MPV_EVENT_END_FILE)
  348. {
  349. mpv_event_end_file *endFile = (mpv_event_end_file *)event->data;
  350. result = endFile->reason == MPV_END_FILE_REASON_EOF;
  351. break;
  352. }
  353. }
  354. QLOG_DEBUG() << "Result:" << result;
  355. return result;
  356. }
  357. ///////////////////////////////////////////////////////////////////////////////////////////////////
  358. static void probeCodecs()
  359. {
  360. if (g_deviceID.isEmpty())
  361. throw FatalException("Could not read device-id.");
  362. #ifdef Q_OS_WIN32
  363. if (useSystemVideoDecoders())
  364. {
  365. if (probeDecoder("h264_mf", ":/testmedia/high_4096x2304.h264"))
  366. g_mediaFoundationH264MaxResolution = QSize(4096, 2304);
  367. else if (probeDecoder("h264_mf", ":/testmedia/high_4096x2160.h264"))
  368. g_mediaFoundationH264MaxResolution = QSize(4096, 2160);
  369. else if (probeDecoder("h264_mf", ":/testmedia/high_4096x1080.h264"))
  370. g_mediaFoundationH264MaxResolution = QSize(4096, 1080);
  371. else
  372. g_systemVideoDecoderWhitelist.remove("h264_mf");
  373. QLOG_DEBUG() << "h264_mf max. resolution:" << g_mediaFoundationH264MaxResolution;
  374. }
  375. #endif
  376. #ifdef Q_OS_MAC
  377. // Unsupported, but avoid picking up broken Perian decoders.
  378. if (QSysInfo::MacintoshVersion <= QSysInfo::MV_10_10)
  379. g_systemAudioDecoderWhitelist.remove("ac3_at");
  380. #endif
  381. Codecs::updateCachedCodecList();
  382. }
  383. ///////////////////////////////////////////////////////////////////////////////////////////////////
  384. static void deleteOldCodecs()
  385. {
  386. QStringList neededPaths = {
  387. codecsPath(),
  388. };
  389. for (auto entry : QDir(codecsRootPath()).entryList(QDir::Dirs | QDir::NoDotAndDotDot))
  390. {
  391. QDir entryPath = codecsRootPath();
  392. if (!entryPath.cd(entry))
  393. continue;
  394. bool needed = false;
  395. for (auto neededPath : neededPaths)
  396. {
  397. if (entryPath.absolutePath() == QDir(neededPath).absolutePath())
  398. {
  399. needed = true;
  400. break;
  401. }
  402. }
  403. if (needed)
  404. continue;
  405. // Same version, but different platform -> just keep it.
  406. if (entry.startsWith(g_codecVersion + "-"))
  407. continue;
  408. QLOG_DEBUG() << "Deleting old directory: " << entryPath.absolutePath();
  409. entryPath.removeRecursively();
  410. }
  411. }
  412. ///////////////////////////////////////////////////////////////////////////////////////////////////
  413. void Codecs::initCodecs()
  414. {
  415. deleteOldCodecs();
  416. probeCodecs();
  417. }
  418. ///////////////////////////////////////////////////////////////////////////////////////////////////
  419. bool CodecsFetcher::codecNeedsDownload(const CodecDriver& codec)
  420. {
  421. if (codec.present)
  422. return false;
  423. if (!codec.external)
  424. {
  425. QLOG_ERROR() << "Codec" << codec.driver << "does not exist and is not downloadable.";
  426. return false;
  427. }
  428. for (int n = 0; n < m_Codecs.size(); n++)
  429. {
  430. if (Codecs::sameCodec(codec, m_Codecs[n]))
  431. return false;
  432. }
  433. QFile codecFile(codec.getPath());
  434. if (codecFile.exists())
  435. {
  436. QLOG_ERROR() << "Codec" << codec.driver << "exists on disk as" << codec.getPath()
  437. << "but is not known as installed - broken codec?";
  438. if (!codecFile.remove())
  439. return false;
  440. QLOG_ERROR() << "Retrying download.";
  441. }
  442. return true;
  443. }
  444. ///////////////////////////////////////////////////////////////////////////////////////////////////
  445. void CodecsFetcher::installCodecs(const QList<CodecDriver>& codecs)
  446. {
  447. foreach (CodecDriver codec, codecs)
  448. {
  449. if (codecNeedsDownload(codec))
  450. m_Codecs.enqueue(codec);
  451. }
  452. startNext();
  453. }
  454. ///////////////////////////////////////////////////////////////////////////////////////////////////
  455. static Downloader::HeaderList getPlexHeaders()
  456. {
  457. Downloader::HeaderList headers;
  458. QString auth = SystemComponent::Get().authenticationToken();
  459. if (auth.size())
  460. headers.append({"X-Plex-Token", auth});
  461. headers.append({"X-Plex-Product", "Plex Media Player"});
  462. headers.append({"X-Plex-Platform", "Konvergo"});
  463. return headers;
  464. }
  465. ///////////////////////////////////////////////////////////////////////////////////////////////////
  466. void CodecsFetcher::startNext()
  467. {
  468. if (m_Codecs.isEmpty())
  469. {
  470. emit done(this);
  471. return;
  472. }
  473. CodecDriver codec = m_Codecs.dequeue();
  474. QString host = "https://plex.tv";
  475. QUrl url = QUrl(host + "/api/codecs/" + codec.getMangledName());
  476. QUrlQuery query;
  477. query.addQueryItem("deviceId", g_deviceID);
  478. query.addQueryItem("version", g_codecVersion);
  479. query.addQueryItem("build", getBuildType());
  480. query.addQueryItem("oldestPreviousVersion", SettingsComponent::Get().oldestPreviousVersion());
  481. url.setQuery(query);
  482. Downloader *downloader = new Downloader(QVariant::fromValue(codec), url, getPlexHeaders(), this);
  483. connect(downloader, &Downloader::done, this, &CodecsFetcher::codecInfoDownloadDone);
  484. }
  485. ///////////////////////////////////////////////////////////////////////////////////////////////////
  486. bool CodecsFetcher::processCodecInfoReply(const QByteArray& data, const CodecDriver& codec)
  487. {
  488. QLOG_INFO() << "Got reply:" << QString::fromUtf8(data);
  489. QDomDocument dom;
  490. if (!dom.setContent(data))
  491. {
  492. QLOG_ERROR() << "XML parsing error.";
  493. return false;
  494. }
  495. QDomNodeList list = dom.elementsByTagName("MediaContainer");
  496. if (list.count() != 1)
  497. {
  498. QLOG_ERROR() << "MediaContainer XML element not found.";
  499. return false;
  500. }
  501. list = dom.elementsByTagName("Codec");
  502. if (list.count() != 1)
  503. {
  504. QLOG_ERROR() << "Codec XML element not found.";
  505. return false;
  506. }
  507. QDomNamedNodeMap attrs = list.at(0).attributes();
  508. QString url = attrs.namedItem("url").toAttr().value();
  509. if (!url.size())
  510. {
  511. QLOG_ERROR() << "No URL found.";
  512. return false;
  513. }
  514. QString hash = attrs.namedItem("fileSha").toAttr().value();
  515. m_currentHash = QByteArray::fromHex(hash.toUtf8());
  516. // it's hardcoded to SHA-1
  517. if (!m_currentHash.size()) {
  518. QLOG_ERROR() << "Hash value in unexpected format or missing:" << hash;
  519. return false;
  520. }
  521. Downloader *downloader = new Downloader(QVariant::fromValue(codec), url, getPlexHeaders(), this);
  522. connect(downloader, &Downloader::done, this, &CodecsFetcher::codecDownloadDone);
  523. return true;
  524. }
  525. ///////////////////////////////////////////////////////////////////////////////////////////////////
  526. void CodecsFetcher::codecInfoDownloadDone(QVariant userData, bool success, const QByteArray& data)
  527. {
  528. CodecDriver codec = userData.value<CodecDriver>();
  529. if (!success || !processCodecInfoReply(data, codec))
  530. {
  531. QLOG_ERROR() << "Codec download failed.";
  532. startNext();
  533. }
  534. }
  535. ///////////////////////////////////////////////////////////////////////////////////////////////////
  536. void CodecsFetcher::processCodecDownloadDone(const QByteArray& data, const CodecDriver& codec)
  537. {
  538. QByteArray hash = QCryptographicHash::hash(data, QCryptographicHash::Sha1);
  539. if (hash != m_currentHash)
  540. {
  541. QLOG_ERROR() << "Checksum mismatch: got" << hash.toHex() << "expected" << m_currentHash.toHex();
  542. return;
  543. }
  544. QLOG_INFO() << "Storing codec as" << codec.getPath();
  545. if (!Utils::safelyWriteFile(codec.getPath(), data))
  546. {
  547. QLOG_ERROR() << "Writing codec file failed.";
  548. return;
  549. }
  550. // This causes libmpv and eventually libavcodec to rescan and load new codecs.
  551. Codecs::updateCachedCodecList();
  552. for (const CodecDriver& item : Codecs::getCachedCodecList())
  553. {
  554. if (Codecs::sameCodec(item, codec) && !item.present)
  555. {
  556. QLOG_ERROR() << "Codec could not be loaded after installing it.";
  557. return;
  558. }
  559. }
  560. QLOG_INFO() << "Codec download and installation succeeded.";
  561. }
  562. ///////////////////////////////////////////////////////////////////////////////////////////////////
  563. void CodecsFetcher::codecDownloadDone(QVariant userData, bool success, const QByteArray& data)
  564. {
  565. CodecDriver codec = userData.value<CodecDriver>();
  566. QLOG_INFO() << "Codec" << codec.driver << "request finished.";
  567. if (success)
  568. {
  569. processCodecDownloadDone(data, codec);
  570. }
  571. else
  572. {
  573. QLOG_ERROR() << "Codec download HTTP request failed.";
  574. }
  575. startNext();
  576. }
  577. ///////////////////////////////////////////////////////////////////////////////////////////////////
  578. Downloader::Downloader(QVariant userData, const QUrl& url, const HeaderList& headers, QObject* parent)
  579. : QObject(parent), m_userData(userData), m_lastProgress(-1)
  580. {
  581. QLOG_INFO() << "HTTP request:" << url.toDisplayString();
  582. m_currentStartTime.start();
  583. connect(&m_WebCtrl, &QNetworkAccessManager::finished, this, &Downloader::networkFinished);
  584. QNetworkRequest request(url);
  585. for (int n = 0; n < headers.size(); n++)
  586. request.setRawHeader(headers[n].first.toUtf8(), headers[n].second.toUtf8());
  587. QNetworkReply *reply = m_WebCtrl.get(request);
  588. if (reply)
  589. {
  590. connect(reply, &QNetworkReply::downloadProgress, this, &Downloader::downloadProgress);
  591. }
  592. }
  593. ///////////////////////////////////////////////////////////////////////////////////////////////////
  594. void Downloader::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
  595. {
  596. if (bytesTotal > 0)
  597. {
  598. int progress = bytesReceived * 100 / bytesTotal;
  599. if (m_lastProgress < 0 || progress > m_lastProgress + 10)
  600. {
  601. m_lastProgress = progress;
  602. QLOG_INFO() << "HTTP request at" << progress << "% (" << bytesReceived << "/" << bytesTotal << ")";
  603. }
  604. }
  605. }
  606. ///////////////////////////////////////////////////////////////////////////////////////////////////
  607. void Downloader::networkFinished(QNetworkReply* pReply)
  608. {
  609. QLOG_INFO() << "HTTP finished after" << (m_currentStartTime.elapsed() + 500) / 1000
  610. << "seconds for a request of" << pReply->size() << "bytes.";
  611. if (pReply->error() == QNetworkReply::NoError)
  612. {
  613. emit done(m_userData, true, pReply->readAll());
  614. }
  615. else
  616. {
  617. QLOG_ERROR() << "HTTP download error:" << pReply->errorString();
  618. emit done(m_userData, false, QByteArray());
  619. }
  620. pReply->deleteLater();
  621. m_WebCtrl.clearAccessCache(); // make sure the TCP connection is closed
  622. }
  623. ///////////////////////////////////////////////////////////////////////////////////////////////////
  624. static CodecDriver selectBestDecoder(const StreamInfo& stream)
  625. {
  626. QList<CodecDriver> codecs = Codecs::findCodecsByFormat(Codecs::getCachedCodecList(), CodecType::Decoder, stream.codec);
  627. CodecDriver best = {};
  628. int bestScore = -1;
  629. for (auto codec : codecs)
  630. {
  631. int score = -1;
  632. if (codec.isSystemCodec())
  633. {
  634. // we always want to avoid using non-whitelisted system codecs
  635. // on the other hand, always prefer whitelisted system codecs
  636. if ((codec.isWhitelistedSystemAudioCodec() && useSystemAudioDecoders()) ||
  637. (codec.isWhitelistedSystemVideoCodec() && useSystemVideoDecoders()))
  638. score = 10;
  639. if (codec.format == "h264")
  640. {
  641. // Avoid using system video decoders for h264 profiles usually not supported.
  642. if (stream.profile != "" && stream.profile != "main" && stream.profile != "baseline" && stream.profile != "high")
  643. score = 1;
  644. }
  645. if (codec.driver == "h264_mf")
  646. {
  647. if (!stream.videoResolution.isEmpty())
  648. {
  649. QSize res = stream.videoResolution;
  650. if (res.width() > g_mediaFoundationH264MaxResolution.width() ||
  651. res.height() > g_mediaFoundationH264MaxResolution.height())
  652. score = 1;
  653. }
  654. }
  655. if (codec.driver == "aac_mf")
  656. {
  657. // Arbitrary but documented and enforced 6 channel limit by MS.
  658. if (stream.audioChannels > 6)
  659. score = 1;
  660. // Another arbitrary limit.
  661. if (stream.audioSampleRate > 0 && (stream.audioSampleRate < 8000 || stream.audioSampleRate > 48000))
  662. score = 1;
  663. }
  664. }
  665. else
  666. {
  667. // prefer codecs which do not have to be downloaded over others
  668. if (codec.present)
  669. score = 15;
  670. else
  671. score = 5;
  672. }
  673. if (score > bestScore)
  674. {
  675. best = codec;
  676. bestScore = score;
  677. }
  678. }
  679. return best;
  680. }
  681. ///////////////////////////////////////////////////////////////////////////////////////////////////
  682. QList<CodecDriver> Codecs::determineRequiredCodecs(const PlaybackInfo& info)
  683. {
  684. QList<CodecDriver> result;
  685. bool needAC3Encoder = false;
  686. QLOG_INFO() << "Using system audio decoders:" << useSystemAudioDecoders();
  687. QLOG_INFO() << "Using system video decoders:" << useSystemVideoDecoders();
  688. #if !defined(HAVE_CODEC_MANIFEST)
  689. QLOG_INFO() << "Not using on-demand codecs.";
  690. #endif
  691. for (auto stream : info.streams)
  692. {
  693. if (!stream.isVideo && !stream.isAudio)
  694. continue;
  695. if (!stream.codec.size())
  696. {
  697. QLOG_ERROR() << "unidentified codec";
  698. continue;
  699. }
  700. // We could do this if we'd find a nice way to enable passthrough by default:
  701. #if 0
  702. // Can passthrough be used? If so, don't request a codec.
  703. if (info.audioPassthroughCodecs.contains(stream.codec))
  704. continue;
  705. #endif
  706. // (Would be nice to check audioChannels here to not request the encoder
  707. // when playing stereo - but unfortunately, the ac3 encoder is loaded first,
  708. // and only removed when detecting stereo input)
  709. if (info.enableAC3Transcoding)
  710. needAC3Encoder = true;
  711. CodecDriver best = selectBestDecoder(stream);
  712. if (best.valid())
  713. {
  714. result.append(best);
  715. }
  716. else
  717. {
  718. QLOG_ERROR() << "no decoder for" << stream.codec;
  719. }
  720. }
  721. if (needAC3Encoder)
  722. {
  723. QList<CodecDriver> codecs = Codecs::findCodecsByFormat(Codecs::getCachedCodecList(), CodecType::Encoder, "ac3");
  724. CodecDriver encoder = {};
  725. for (auto codec : codecs)
  726. {
  727. if (codec.present && (!codec.isSystemCodec() || codec.isWhitelistedSystemAudioCodec()))
  728. {
  729. encoder = codec;
  730. break;
  731. }
  732. if (codec.external)
  733. encoder = codec; // fallback
  734. }
  735. if (encoder.valid())
  736. {
  737. result.append(encoder);
  738. }
  739. else
  740. {
  741. QLOG_ERROR() << "no AC3 encoder available";
  742. }
  743. }
  744. return result;
  745. }