Bug 1263665 - Block libav < 54.35.1 - r=jya,gijs r=smaug, a=al
authorGerald Squelart <gsquelart@mozilla.com>
Wed, 05 Oct 2016 15:04:04 -0700
changeset 356119 a2e2e689b1e709da59b07708d61b4aeb10457438
parent 356118 47f70e2af768c0a48ac6c938a1cf89f1e9d1138c
child 356120 3f5b661f1ce474dd006107ec75bf82f110437a74
push id6570
push userraliiev@mozilla.com
push dateMon, 14 Nov 2016 12:26:13 +0000
treeherdermozilla-beta@f455459b2ae5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjya, gijs, smaug, al
bugs1263665
milestone51.0a2
Bug 1263665 - Block libav < 54.35.1 - r=jya,gijs r=smaug, a=al And display user notification that libav may be vulnerable and should be updated. Note: New strings are hard-coded, as translation cannot be done in time. MozReview-Commit-ID: HgTXlDnj2Gw
browser/base/content/browser-media.js
browser/base/content/test/general/browser_decoderDoctor.js
dom/media/DecoderDoctorDiagnostics.cpp
dom/media/MediaPrefs.h
dom/media/platforms/ffmpeg/FFmpegLibWrapper.cpp
dom/media/platforms/ffmpeg/FFmpegLibWrapper.h
dom/media/platforms/ffmpeg/FFmpegRuntimeLinker.cpp
dom/media/platforms/ffmpeg/FFmpegRuntimeLinker.h
dom/media/platforms/ffmpeg/ffvpx/FFVPXRuntimeLinker.cpp
dom/webidl/DecoderDoctorNotification.webidl
modules/libpref/init/all.js
testing/profiles/prefs_general.js
--- a/browser/base/content/browser-media.js
+++ b/browser/base/content/browser-media.js
@@ -230,16 +230,22 @@ let gDecoderDoctorHandler = {
       }
       if (AppConstants.isPlatformAndVersionAtLeast("win", "6")) {
         return gNavigatorBundle.getString("decoder.noHWAccelerationVista.message");
       }
       if (AppConstants.platform == "linux") {
         return gNavigatorBundle.getString("decoder.noCodecsLinux.message");
       }
     }
+    if (type == "unsupported-libavcodec" &&
+        AppConstants.platform == "linux") {
+      // Note: Hard-coded string in aurora and beta because translation cannot
+      // be achieved in time.
+      return "libavcodec may be vulnerable or is not supported, and should be updated to play video.";
+    }
     return "";
   },
 
   receiveMessage({target: browser, data: data}) {
     let box = gBrowser.getNotificationBox(browser);
     let notificationId = "decoder-doctor-notification";
     if (box.getNotificationWithValue(notificationId)) {
       return;
--- a/browser/base/content/test/general/browser_decoderDoctor.js
+++ b/browser/base/content/test/general/browser_decoderDoctor.js
@@ -90,8 +90,22 @@ add_task(function* test_platform_decoder
   } else {
     message = gNavigatorBundle.getString("decoder.noHWAcceleration.message");
   }
 
   yield test_decoder_doctor_notification("platform-decoder-not-found",
                                          message,
                                          {noLearnMoreButton: isLinux});
 });
+
+add_task(function* test_unsupported_libavcodec() {
+  // This is only sent on Linux.
+  if (AppConstants.platform != "linux") {
+    return;
+  }
+
+  // Note: Hard-coded string in aurora and beta because translation cannot
+  // be achieved in time.
+  let message = "libavcodec may be vulnerable or is not supported, and should be updated to play video.";
+  yield test_decoder_doctor_notification("unsupported-libavcodec",
+                                         message,
+                                         {noLearnMoreButton: true});
+});
--- a/dom/media/DecoderDoctorDiagnostics.cpp
+++ b/dom/media/DecoderDoctorDiagnostics.cpp
@@ -14,16 +14,20 @@
 #include "nsIDocument.h"
 #include "nsIObserverService.h"
 #include "nsIScriptError.h"
 #include "nsITimer.h"
 #include "nsIWeakReference.h"
 #include "nsPluginHost.h"
 #include "VideoUtils.h"
 
+#if defined(MOZ_FFMPEG)
+#include "FFmpegRuntimeLinker.h"
+#endif
+
 #if defined(XP_WIN)
 #include "mozilla/WindowsVersion.h"
 #endif
 
 static mozilla::LazyLogModule sDecoderDoctorLog("DecoderDoctor");
 #define DD_LOG(level, arg, ...) MOZ_LOG(sDecoderDoctorLog, level, (arg, ##__VA_ARGS__))
 #define DD_DEBUG(arg, ...) DD_LOG(mozilla::LogLevel::Debug, arg, ##__VA_ARGS__)
 #define DD_INFO(arg, ...) DD_LOG(mozilla::LogLevel::Info, arg, ##__VA_ARGS__)
@@ -262,26 +266,30 @@ static const NotificationAndReportString
   { dom::DecoderDoctorNotificationType::Platform_decoder_not_found,
     "MediaPlatformDecoderNotFound" };
 static const NotificationAndReportStringId sMediaCannotPlayNoDecoders =
   { dom::DecoderDoctorNotificationType::Cannot_play,
     "MediaCannotPlayNoDecoders" };
 static const NotificationAndReportStringId sMediaNoDecoders =
   { dom::DecoderDoctorNotificationType::Can_play_but_some_missing_decoders,
     "MediaNoDecoders" };
+static const NotificationAndReportStringId sUnsupportedLibavcodec =
+  { dom::DecoderDoctorNotificationType::Unsupported_libavcodec,
+    "MediaUnsupportedLibavcodec" };
 
 static const NotificationAndReportStringId*
 sAllNotificationsAndReportStringIds[] =
 {
   &sMediaWidevineNoWMFNoSilverlight,
   &sMediaWMFNeeded,
   &sMediaUnsupportedBeforeWindowsVista,
   &sMediaPlatformDecoderNotFound,
   &sMediaCannotPlayNoDecoders,
-  &sMediaNoDecoders
+  &sMediaNoDecoders,
+  &sUnsupportedLibavcodec,
 };
 
 static void
 DispatchNotification(nsISupports* aSubject,
                      const NotificationAndReportStringId& aNotification,
                      bool aIsSolved,
                      const nsAString& aFormats)
 {
@@ -325,23 +333,33 @@ DecoderDoctorDocumentWatcher::ReportAnal
 
   // Report non-solved issues to console.
   if (!aIsSolved) {
     // 'params' will only be forwarded for non-empty strings.
     const char16_t* params[1] = { aParams.Data() };
     DD_DEBUG("DecoderDoctorDocumentWatcher[%p, doc=%p]::ReportAnalysis() ReportToConsole - aMsg='%s' params[0]='%s'",
              this, mDocument, aNotification.mReportStringId,
              aParams.IsEmpty() ? "<no params>" : NS_ConvertUTF16toUTF8(params[0]).get());
-    nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
-                                    NS_LITERAL_CSTRING("Media"),
-                                    mDocument,
-                                    nsContentUtils::eDOM_PROPERTIES,
-                                    aNotification.mReportStringId,
-                                    aParams.IsEmpty() ? nullptr : params,
-                                    aParams.IsEmpty() ? 0 : 1);
+    if (&aNotification != &sUnsupportedLibavcodec) {
+      nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+                                      NS_LITERAL_CSTRING("Media"),
+                                      mDocument,
+                                      nsContentUtils::eDOM_PROPERTIES,
+                                      aNotification.mReportStringId,
+                                      aParams.IsEmpty() ? nullptr : params,
+                                      aParams.IsEmpty() ? 0 : 1);
+    } else {
+      // Special case for MediaUnsupportedLibavcodec for aurora and beta, as
+      // translation cannot be done in time.
+      nsContentUtils::ReportToConsoleNonLocalized(
+        NS_LITERAL_STRING("The video on this page can't be played. Your system has an unsupported version of libavcodec"),
+        nsIScriptError::warningFlag,
+        NS_LITERAL_CSTRING("Media"),
+        mDocument);
+    }
   }
 
   // "media.decoder-doctor.notifications-allowed" controls which notifications
   // may be dispatched to the front-end. It either contains:
   // - '*' -> Allow everything.
   // - Comma-separater list of ids -> Allow if aReportStringId (from
   //                                  dom.properties) is one of them.
   // - Nothing (missing or empty) -> Disable everything.
@@ -561,21 +579,44 @@ DecoderDoctorDocumentWatcher::Synthesize
                   this, mDocument, NS_ConvertUTF16toUTF8(formatsRequiringWMF).get());
           ReportAnalysis(sMediaUnsupportedBeforeWindowsVista, false, formatsRequiringWMF);
         }
         return;
       }
 #endif
 #if defined(MOZ_FFMPEG)
       if (!formatsRequiringFFMpeg.IsEmpty()) {
-        DD_INFO("DecoderDoctorDocumentWatcher[%p, doc=%p]::SynthesizeAnalysis() - unplayable formats: %s -> Cannot play media because platform decoder was not found",
-                this, mDocument, NS_ConvertUTF16toUTF8(formatsRequiringFFMpeg).get());
-        ReportAnalysis(sMediaPlatformDecoderNotFound,
-                       false, formatsRequiringFFMpeg);
-        return;
+        switch (FFmpegRuntimeLinker::LinkStatusCode()) {
+          case FFmpegRuntimeLinker::LinkStatus_INVALID_FFMPEG_CANDIDATE:
+          case FFmpegRuntimeLinker::LinkStatus_UNUSABLE_LIBAV57:
+          case FFmpegRuntimeLinker::LinkStatus_INVALID_LIBAV_CANDIDATE:
+          case FFmpegRuntimeLinker::LinkStatus_OBSOLETE_FFMPEG:
+          case FFmpegRuntimeLinker::LinkStatus_OBSOLETE_LIBAV:
+          case FFmpegRuntimeLinker::LinkStatus_INVALID_CANDIDATE:
+            DD_INFO("DecoderDoctorDocumentWatcher[%p, doc=%p]::SynthesizeAnalysis() - unplayable formats: %s -> Cannot play media because of unsupported %s (Reason: %s)",
+                    this, mDocument,
+                    NS_ConvertUTF16toUTF8(formatsRequiringFFMpeg).get(),
+                    FFmpegRuntimeLinker::LinkStatusLibraryName(),
+                    FFmpegRuntimeLinker::LinkStatusString());
+            ReportAnalysis(sUnsupportedLibavcodec,
+                           false, formatsRequiringFFMpeg);
+            return;
+          case FFmpegRuntimeLinker::LinkStatus_INIT:
+            MOZ_FALLTHROUGH_ASSERT("Unexpected LinkStatus_INIT");
+          case FFmpegRuntimeLinker::LinkStatus_SUCCEEDED:
+            MOZ_FALLTHROUGH_ASSERT("Unexpected LinkStatus_SUCCEEDED");
+          case FFmpegRuntimeLinker::LinkStatus_NOT_FOUND:
+            DD_INFO("DecoderDoctorDocumentWatcher[%p, doc=%p]::SynthesizeAnalysis() - unplayable formats: %s -> Cannot play media because platform decoder was not found (Reason: %s)",
+                    this, mDocument,
+                    NS_ConvertUTF16toUTF8(formatsRequiringFFMpeg).get(),
+                    FFmpegRuntimeLinker::LinkStatusString());
+            ReportAnalysis(sMediaPlatformDecoderNotFound,
+                           false, formatsRequiringFFMpeg);
+            return;
+        }
       }
 #endif
       DD_INFO("DecoderDoctorDocumentWatcher[%p, doc=%p]::SynthesizeAnalysis() - Cannot play media, unplayable formats: %s",
               this, mDocument, NS_ConvertUTF16toUTF8(unplayableFormats).get());
       ReportAnalysis(sMediaCannotPlayNoDecoders, false, unplayableFormats);
       return;
     }
 
--- a/dom/media/MediaPrefs.h
+++ b/dom/media/MediaPrefs.h
@@ -106,16 +106,17 @@ private:
 #endif
 #ifdef MOZ_WIDGET_ANDROID
   DECL_MEDIA_PREF("media.android-media-codec.enabled",        PDMAndroidMediaCodecEnabled, bool, false);
   DECL_MEDIA_PREF("media.android-media-codec.preferred",      PDMAndroidMediaCodecPreferred, bool, false);
   DECL_MEDIA_PREF("media.android-remote-codec.enabled",       PDMAndroidRemoteCodecEnabled, bool, false);
 #endif
 #ifdef MOZ_FFMPEG
   DECL_MEDIA_PREF("media.ffmpeg.enabled",                     PDMFFmpegEnabled, bool, true);
+  DECL_MEDIA_PREF("media.libavcodec.allow-obsolete",          LibavcodecAllowObsolete, bool, false);
 #endif
 #ifdef MOZ_FFVPX
   DECL_MEDIA_PREF("media.ffvpx.enabled",                      PDMFFVPXEnabled, bool, true);
 #endif
 #ifdef XP_WIN
   DECL_MEDIA_PREF("media.wmf.enabled",                        PDMWMFEnabled, bool, true);
   DECL_MEDIA_PREF("media.decoder-doctor.wmf-disabled-is-failure", DecoderDoctorWMFDisabledIsFailure, bool, false);
   DECL_MEDIA_PREF("media.webm.intel_decoder.enabled",         PDMWMFIntelDecoderEnabled, bool, false);
--- a/dom/media/platforms/ffmpeg/FFmpegLibWrapper.cpp
+++ b/dom/media/platforms/ffmpeg/FFmpegLibWrapper.cpp
@@ -1,14 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "FFmpegLibWrapper.h"
 #include "FFmpegLog.h"
+#include "MediaPrefs.h"
 #include "mozilla/PodOperations.h"
 #include "mozilla/Types.h"
 #include "prlink.h"
 
 #define AV_LOG_DEBUG    48
 
 namespace mozilla
 {
@@ -18,39 +19,52 @@ FFmpegLibWrapper::FFmpegLibWrapper()
   PodZero(this);
 }
 
 FFmpegLibWrapper::~FFmpegLibWrapper()
 {
   Unlink();
 }
 
-bool
+FFmpegLibWrapper::LinkResult
 FFmpegLibWrapper::Link()
 {
   if (!mAVCodecLib || !mAVUtilLib) {
     Unlink();
-    return false;
+    return LinkResult::NoProvidedLib;
   }
 
   avcodec_version =
     (decltype(avcodec_version))PR_FindSymbol(mAVCodecLib, "avcodec_version");
   if (!avcodec_version) {
     Unlink();
-    return false;
+    return LinkResult::NoAVCodecVersion;
   }
   uint32_t version = avcodec_version();
-  mVersion = (version >> 16) & 0xff;
-  uint32_t micro = version & 0xff;
-  if (mVersion == 57 && micro < 100) {
-    // a micro version >= 100 indicates that it's FFmpeg (as opposed to LibAV).
-    // Due to current AVCodecContext binary incompatibility we can only
-    // support FFmpeg 57 at this stage.
-    Unlink();
-    return false;
+  uint32_t macro = (version >> 16) & 0xFFu;
+  mVersion = static_cast<int>(macro);
+  uint32_t micro = version & 0xFFu;
+  // A micro version >= 100 indicates that it's FFmpeg (as opposed to LibAV).
+  bool isFFMpeg = micro >= 100;
+  if (!isFFMpeg) {
+    if (macro == 57) {
+      // Due to current AVCodecContext binary incompatibility we can only
+      // support FFmpeg 57 at this stage.
+      Unlink();
+      return LinkResult::CannotUseLibAV57;
+    }
+#ifdef MOZ_FFMPEG
+    if (version < (54u << 16 | 35u << 8 | 1u)
+        && !MediaPrefs::LibavcodecAllowObsolete()) {
+      // Refuse any libavcodec version prior to 54.35.1.
+      // (Unless media.libavcodec.allow-obsolete==true)
+      Unlink();
+      return LinkResult::BlockedOldLibAVVersion;
+    }
+#endif
   }
 
   enum {
     AV_FUNC_AVUTIL_MASK = 1 << 8,
     AV_FUNC_53 = 1 << 0,
     AV_FUNC_54 = 1 << 1,
     AV_FUNC_55 = 1 << 2,
     AV_FUNC_56 = 1 << 3,
@@ -59,17 +73,17 @@ FFmpegLibWrapper::Link()
     AV_FUNC_AVUTIL_54 = AV_FUNC_54 | AV_FUNC_AVUTIL_MASK,
     AV_FUNC_AVUTIL_55 = AV_FUNC_55 | AV_FUNC_AVUTIL_MASK,
     AV_FUNC_AVUTIL_56 = AV_FUNC_56 | AV_FUNC_AVUTIL_MASK,
     AV_FUNC_AVUTIL_57 = AV_FUNC_57 | AV_FUNC_AVUTIL_MASK,
     AV_FUNC_AVCODEC_ALL = AV_FUNC_53 | AV_FUNC_54 | AV_FUNC_55 | AV_FUNC_56 | AV_FUNC_57,
     AV_FUNC_AVUTIL_ALL = AV_FUNC_AVCODEC_ALL | AV_FUNC_AVUTIL_MASK
   };
 
-  switch (mVersion) {
+  switch (macro) {
     case 53:
       version = AV_FUNC_53;
       break;
     case 54:
       version = AV_FUNC_54;
       break;
     case 55:
       version = AV_FUNC_55;
@@ -78,25 +92,31 @@ FFmpegLibWrapper::Link()
       version = AV_FUNC_56;
       break;
     case 57:
       version = AV_FUNC_57;
       break;
     default:
       FFMPEG_LOG("Unknown avcodec version");
       Unlink();
-      return false;
+      return isFFMpeg
+             ? ((macro > 57)
+                ? LinkResult::UnknownFutureFFMpegVersion
+                : LinkResult::UnknownOlderFFMpegVersion)
+             // All LibAV versions<54.35.1 are blocked, therefore we must be
+             // dealing with a later one.
+             : LinkResult::UnknownFutureLibAVVersion;
   }
 
 #define AV_FUNC(func, ver)                                                     \
-  if ((ver) & version) {                                                      \
+  if ((ver) & version) {                                                       \
     if (!(func = (decltype(func))PR_FindSymbol(((ver) & AV_FUNC_AVUTIL_MASK) ? mAVUtilLib : mAVCodecLib, #func))) { \
       FFMPEG_LOG("Couldn't load function " # func);                            \
       Unlink();                                                                \
-      return false;                                                            \
+      return isFFMpeg ? LinkResult::MissingFFMpegFunction : LinkResult::MissingLibAVFunction; \
     }                                                                          \
   } else {                                                                     \
     func = (decltype(func))nullptr;                                            \
   }
   AV_FUNC(av_lockmgr_register, AV_FUNC_AVCODEC_ALL)
   AV_FUNC(avcodec_alloc_context3, AV_FUNC_AVCODEC_ALL)
   AV_FUNC(avcodec_close, AV_FUNC_AVCODEC_ALL)
   AV_FUNC(avcodec_decode_audio4, AV_FUNC_AVCODEC_ALL)
@@ -120,17 +140,17 @@ FFmpegLibWrapper::Link()
   AV_FUNC(av_frame_unref, (AV_FUNC_AVUTIL_55 | AV_FUNC_AVUTIL_56 | AV_FUNC_AVUTIL_57))
 #undef AV_FUNC
 
   avcodec_register_all();
 #ifdef DEBUG
   av_log_set_level(AV_LOG_DEBUG);
 #endif
 
-  return true;
+  return LinkResult::Success;
 }
 
 void
 FFmpegLibWrapper::Unlink()
 {
   if (av_lockmgr_register) {
     // Registering a null lockmgr cause the destruction of libav* global mutexes
     // as the default lockmgr that allocated them will be deregistered.
--- a/dom/media/platforms/ffmpeg/FFmpegLibWrapper.h
+++ b/dom/media/platforms/ffmpeg/FFmpegLibWrapper.h
@@ -18,20 +18,33 @@ struct PRLibrary;
 namespace mozilla
 {
 
 struct FFmpegLibWrapper
 {
   FFmpegLibWrapper();
   ~FFmpegLibWrapper();
 
-  // Attempt to resolve all symbols. Return true of successful.
+  enum class LinkResult
+  {
+    Success,
+    NoProvidedLib,
+    NoAVCodecVersion,
+    CannotUseLibAV57,
+    BlockedOldLibAVVersion,
+    UnknownFutureLibAVVersion,
+    UnknownFutureFFMpegVersion,
+    UnknownOlderFFMpegVersion,
+    MissingFFMpegFunction,
+    MissingLibAVFunction,
+  };
+  // Examine mAVCodecLib and mAVUtilLib, and attempt to resolve all symbols.
   // Upon failure, the entire object will be reset and any attached libraries
   // will be unlinked.
-  bool Link();
+  LinkResult Link();
 
   // Reset the wrapper and unlink all attached libraries.
   void Unlink();
 
   // indicate the version of libavcodec linked to.
   // 0 indicates that the function wasn't initialized with Link().
   int mVersion;
 
--- a/dom/media/platforms/ffmpeg/FFmpegRuntimeLinker.cpp
+++ b/dom/media/platforms/ffmpeg/FFmpegRuntimeLinker.cpp
@@ -10,16 +10,17 @@
 #include "FFmpegLog.h"
 #include "prlink.h"
 
 namespace mozilla
 {
 
 FFmpegRuntimeLinker::LinkStatus FFmpegRuntimeLinker::sLinkStatus =
   LinkStatus_INIT;
+const char* FFmpegRuntimeLinker::sLinkStatusLibraryName = "";
 
 template <int V> class FFmpegDecoderModule
 {
 public:
   static already_AddRefed<PlatformDecoderModule> Create(FFmpegLibWrapper*);
 };
 
 static FFmpegLibWrapper sLibAV;
@@ -40,42 +41,88 @@ static const char* sLibs[] = {
   "libavcodec.so.54",
   "libavcodec.so.53",
 #endif
 };
 
 /* static */ bool
 FFmpegRuntimeLinker::Init()
 {
-  if (sLinkStatus) {
+  if (sLinkStatus != LinkStatus_INIT) {
     return sLinkStatus == LinkStatus_SUCCEEDED;
   }
 
+  // While going through all possible libs, this status will be updated with a
+  // more precise error if possible.
+  sLinkStatus = LinkStatus_NOT_FOUND;
+
   for (size_t i = 0; i < ArrayLength(sLibs); i++) {
     const char* lib = sLibs[i];
     PRLibSpec lspec;
     lspec.type = PR_LibSpec_Pathname;
     lspec.value.pathname = lib;
     sLibAV.mAVCodecLib = PR_LoadLibraryWithFlags(lspec, PR_LD_NOW | PR_LD_LOCAL);
     if (sLibAV.mAVCodecLib) {
       sLibAV.mAVUtilLib = sLibAV.mAVCodecLib;
-      if (sLibAV.Link()) {
-        sLinkStatus = LinkStatus_SUCCEEDED;
-        return true;
+      switch (sLibAV.Link()) {
+        case FFmpegLibWrapper::LinkResult::Success:
+          sLinkStatus = LinkStatus_SUCCEEDED;
+          sLinkStatusLibraryName = lib;
+          return true;
+        case FFmpegLibWrapper::LinkResult::NoProvidedLib:
+          MOZ_ASSERT_UNREACHABLE("Incorrectly-setup sLibAV");
+          break;
+        case FFmpegLibWrapper::LinkResult::NoAVCodecVersion:
+          if (sLinkStatus > LinkStatus_INVALID_CANDIDATE) {
+            sLinkStatus = LinkStatus_INVALID_CANDIDATE;
+            sLinkStatusLibraryName = lib;
+          }
+          break;
+        case FFmpegLibWrapper::LinkResult::CannotUseLibAV57:
+          if (sLinkStatus > LinkStatus_UNUSABLE_LIBAV57) {
+            sLinkStatus = LinkStatus_UNUSABLE_LIBAV57;
+            sLinkStatusLibraryName = lib;
+          }
+          break;
+        case FFmpegLibWrapper::LinkResult::BlockedOldLibAVVersion:
+          if (sLinkStatus > LinkStatus_OBSOLETE_LIBAV) {
+            sLinkStatus = LinkStatus_OBSOLETE_LIBAV;
+            sLinkStatusLibraryName = lib;
+          }
+          break;
+        case FFmpegLibWrapper::LinkResult::UnknownFutureLibAVVersion:
+        case FFmpegLibWrapper::LinkResult::MissingLibAVFunction:
+          if (sLinkStatus > LinkStatus_INVALID_LIBAV_CANDIDATE) {
+            sLinkStatus = LinkStatus_INVALID_LIBAV_CANDIDATE;
+            sLinkStatusLibraryName = lib;
+          }
+          break;
+        case FFmpegLibWrapper::LinkResult::UnknownFutureFFMpegVersion:
+        case FFmpegLibWrapper::LinkResult::MissingFFMpegFunction:
+          if (sLinkStatus > LinkStatus_INVALID_FFMPEG_CANDIDATE) {
+            sLinkStatus = LinkStatus_INVALID_FFMPEG_CANDIDATE;
+            sLinkStatusLibraryName = lib;
+          }
+          break;
+        case FFmpegLibWrapper::LinkResult::UnknownOlderFFMpegVersion:
+          if (sLinkStatus > LinkStatus_OBSOLETE_FFMPEG) {
+            sLinkStatus = LinkStatus_OBSOLETE_FFMPEG;
+            sLinkStatusLibraryName = lib;
+          }
+          break;
       }
     }
   }
 
   FFMPEG_LOG("H264/AAC codecs unsupported without [");
   for (size_t i = 0; i < ArrayLength(sLibs); i++) {
     FFMPEG_LOG("%s %s", i ? "," : " ", sLibs[i]);
   }
   FFMPEG_LOG(" ]\n");
 
-  sLinkStatus = LinkStatus_FAILED;
   return false;
 }
 
 /* static */ already_AddRefed<PlatformDecoderModule>
 FFmpegRuntimeLinker::CreateDecoderModule()
 {
   if (!Init()) {
     return nullptr;
@@ -87,9 +134,36 @@ FFmpegRuntimeLinker::CreateDecoderModule
     case 55:
     case 56: module = FFmpegDecoderModule<55>::Create(&sLibAV); break;
     case 57: module = FFmpegDecoderModule<57>::Create(&sLibAV); break;
     default: module = nullptr;
   }
   return module.forget();
 }
 
+/* static */ const char*
+FFmpegRuntimeLinker::LinkStatusString()
+{
+  switch (sLinkStatus) {
+    case LinkStatus_INIT:
+      return "Libavcodec not initialized yet";
+    case LinkStatus_SUCCEEDED:
+      return "Libavcodec linking succeeded";
+    case LinkStatus_INVALID_FFMPEG_CANDIDATE:
+      return "Invalid FFMpeg libavcodec candidate";
+    case LinkStatus_UNUSABLE_LIBAV57:
+      return "Unusable LibAV's libavcodec 57";
+    case LinkStatus_INVALID_LIBAV_CANDIDATE:
+      return "Invalid LibAV libavcodec candidate";
+    case LinkStatus_OBSOLETE_FFMPEG:
+      return "Obsolete FFMpeg libavcodec candidate";
+    case LinkStatus_OBSOLETE_LIBAV:
+      return "Obsolete LibAV libavcodec candidate";
+    case LinkStatus_INVALID_CANDIDATE:
+      return "Invalid libavcodec candidate";
+    case LinkStatus_NOT_FOUND:
+      return "Libavcodec not found";
+  }
+  MOZ_ASSERT_UNREACHABLE("Unknown sLinkStatus value");
+  return "?";
+}
+
 } // namespace mozilla
--- a/dom/media/platforms/ffmpeg/FFmpegRuntimeLinker.h
+++ b/dom/media/platforms/ffmpeg/FFmpegRuntimeLinker.h
@@ -12,20 +12,35 @@
 namespace mozilla
 {
 
 class FFmpegRuntimeLinker
 {
 public:
   static bool Init();
   static already_AddRefed<PlatformDecoderModule> CreateDecoderModule();
+  enum LinkStatus
+  {
+    LinkStatus_INIT = 0,  // Never been linked.
+    LinkStatus_SUCCEEDED, // Found a usable library.
+    // The following error statuses are sorted from most to least preferred
+    // (i.e., if more than one happens, the top one is chosen.)
+    LinkStatus_INVALID_FFMPEG_CANDIDATE, // Found ffmpeg with unexpected contents.
+    LinkStatus_UNUSABLE_LIBAV57, // Found LibAV 57, which we cannot use.
+    LinkStatus_INVALID_LIBAV_CANDIDATE, // Found libav with unexpected contents.
+    LinkStatus_OBSOLETE_FFMPEG,
+    LinkStatus_OBSOLETE_LIBAV,
+    LinkStatus_INVALID_CANDIDATE, // Found some lib with unexpected contents.
+    LinkStatus_NOT_FOUND, // Haven't found any library with an expected name.
+  };
+  static LinkStatus LinkStatusCode() { return sLinkStatus; }
+  static const char* LinkStatusString();
+  // Library name to which the sLinkStatus applies, or "" if not applicable.
+  static const char* LinkStatusLibraryName() { return sLinkStatusLibraryName; }
 
 private:
-  static enum LinkStatus {
-    LinkStatus_INIT = 0,
-    LinkStatus_FAILED,
-    LinkStatus_SUCCEEDED
-  } sLinkStatus;
+  static LinkStatus sLinkStatus;
+  static const char* sLinkStatusLibraryName;
 };
 
 }
 
 #endif // __FFmpegRuntimeLinker_h__
--- a/dom/media/platforms/ffmpeg/ffvpx/FFVPXRuntimeLinker.cpp
+++ b/dom/media/platforms/ffmpeg/ffvpx/FFVPXRuntimeLinker.cpp
@@ -89,17 +89,17 @@ FFVPXRuntimeLinker::Init()
   }
   sFFVPXLib.mAVUtilLib = MozAVLink(libname);
   PR_FreeLibraryName(libname);
   libname = PR_GetLibraryName(rootPath.get(), "mozavcodec");
   if (libname) {
     sFFVPXLib.mAVCodecLib = MozAVLink(libname);
     PR_FreeLibraryName(libname);
   }
-  if (sFFVPXLib.Link()) {
+  if (sFFVPXLib.Link() == FFmpegLibWrapper::LinkResult::Success) {
     sLinkStatus = LinkStatus_SUCCEEDED;
     return true;
   }
   return false;
 }
 
 /* static */ already_AddRefed<PlatformDecoderModule>
 FFVPXRuntimeLinker::CreateDecoderModule()
--- a/dom/webidl/DecoderDoctorNotification.webidl
+++ b/dom/webidl/DecoderDoctorNotification.webidl
@@ -2,17 +2,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/.
  */
 
 enum DecoderDoctorNotificationType {
   "cannot-play",
   "platform-decoder-not-found",
-  "can-play-but-some-missing-decoders"
+  "can-play-but-some-missing-decoders",
+  "unsupported-libavcodec",
 };
 
 dictionary DecoderDoctorNotification {
   required DecoderDoctorNotificationType type;
   // True when the issue has been solved.
   required boolean isSolved;
   // Key from dom.properties, used for telemetry and prefs.
   required DOMString decoderDoctorReportId;
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -350,16 +350,17 @@ pref("media.wmf.disable-d3d11-for-dlls",
 pref("media.wmf.disable-d3d9-for-dlls", "igdumd64.dll: 8.15.10.2189, 8.15.10.2119, 8.15.10.2104, 8.15.10.2102, 8.771.1.0; atiumd64.dll: 7.14.10.833, 7.14.10.867, 7.14.10.885, 7.14.10.903, 7.14.10.911, 8.14.10.768, 9.14.10.1001, 9.14.10.1017, 9.14.10.1080, 9.14.10.1128, 9.14.10.1162, 9.14.10.1171, 9.14.10.1183, 9.14.10.1197, 9.14.10.945, 9.14.10.972, 9.14.10.984, 9.14.10.996");
 #endif
 #if defined(MOZ_FFMPEG)
 #if defined(XP_MACOSX)
 pref("media.ffmpeg.enabled", false);
 #else
 pref("media.ffmpeg.enabled", true);
 #endif
+pref("media.libavcodec.allow-obsolete", false);
 #endif
 #if defined(MOZ_FFVPX)
 pref("media.ffvpx.enabled", true);
 #endif
 pref("media.gmp.decoder.enabled", false);
 pref("media.gmp.decoder.aac", 0);
 pref("media.gmp.decoder.h264", 0);
 #ifdef MOZ_RAW
@@ -386,17 +387,17 @@ pref("media.apple.mp4.enabled", true);
 // media.gmp.storage.version.observed, and if the versions don't match,
 // we clear storage and set media.gmp.storage.version.observed=expected.
 // This provides a mechanism to clear GMP storage when non-compatible
 // changes are made.
 pref("media.gmp.storage.version.expected", 1);
 
 // Filter what triggers user notifications.
 // See DecoderDoctorDocumentWatcher::ReportAnalysis for details.
-pref("media.decoder-doctor.notifications-allowed", "MediaWMFNeeded,MediaWidevineNoWMFNoSilverlight");
+pref("media.decoder-doctor.notifications-allowed", "MediaWMFNeeded,MediaWidevineNoWMFNoSilverlight,MediaUnsupportedLibavcodec");
 // Whether we report partial failures.
 pref("media.decoder-doctor.verbose", false);
 // Whether DD should consider WMF-disabled a WMF failure, useful for testing.
 pref("media.decoder-doctor.wmf-disabled-is-failure", false);
 
 // Whether to suspend decoding of videos in background tabs.
 #ifdef NIGHTLY_BUILD
 pref("media.suspend-bkgnd-video.enabled", true);
--- a/testing/profiles/prefs_general.js
+++ b/testing/profiles/prefs_general.js
@@ -346,8 +346,12 @@ user_pref("browser.urlbar.suggest.search
 // tests that don't expect it to be there.
 user_pref("browser.urlbar.userMadeSearchSuggestionsChoice", true);
 
 user_pref("dom.audiochannel.mutedByDefault", false);
 
 user_pref("webextensions.tests", true);
 user_pref("startup.homepage_welcome_url", "about:blank");
 user_pref("startup.homepage_welcome_url.additional", "");
+
+// Don't block old libavcodec libraries when testing, because our test systems
+// cannot easily be upgraded.
+user_pref("media.libavcodec.allow-obsolete", true);