Implement predicate-based decoder selection

This CL revamps the way a VideoDecoder or AudioDecoder is selected to
decode a given VideoDecoderConfig or AudioDecoderConfig, respectively.
The goal is to first filter and rank available decoders based on their
high-level attributes ("IsPlatformDecoder", "SupportsDecryption"),
rather than attempting to initialize them in the order they're
submitted. The result is that an initial decoder is selected more
efficiently, and the decoding system can gracefully transition between
platform and non-platform decoders if the config changes mid-stream.

The full design doc is available at:
go/predicate-based-decoder-selection

Bug: 684792
Change-Id: I8660e7315881b2bea44bf52afb4505f979a43728
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2247331
Commit-Queue: Will Cassella <[email protected]>
Reviewed-by: Xiaohan Wang <[email protected]>
Reviewed-by: Dale Curtis <[email protected]>
Reviewed-by: Brian White <[email protected]>
Cr-Commit-Position: refs/heads/master@{#784292}
diff --git a/media/base/BUILD.gn b/media/base/BUILD.gn
index eadef3b..002c230f 100644
--- a/media/base/BUILD.gn
+++ b/media/base/BUILD.gn
@@ -127,6 +127,8 @@
     "data_source.h",
     "decode_status.cc",
     "decode_status.h",
+    "decoder.cc",
+    "decoder.h",
     "decoder_buffer.cc",
     "decoder_buffer.h",
     "decoder_buffer_queue.cc",
diff --git a/media/base/audio_decoder.cc b/media/base/audio_decoder.cc
index ad272910..4ff425e9 100644
--- a/media/base/audio_decoder.cc
+++ b/media/base/audio_decoder.cc
@@ -12,10 +12,6 @@
 
 AudioDecoder::~AudioDecoder() = default;
 
-bool AudioDecoder::IsPlatformDecoder() const {
-  return false;
-}
-
 bool AudioDecoder::NeedsBitstreamConversion() const {
   return false;
 }
diff --git a/media/base/audio_decoder.h b/media/base/audio_decoder.h
index 378ca4e2a..d9a458db4 100644
--- a/media/base/audio_decoder.h
+++ b/media/base/audio_decoder.h
@@ -13,6 +13,7 @@
 #include "media/base/audio_decoder_config.h"
 #include "media/base/channel_layout.h"
 #include "media/base/decode_status.h"
+#include "media/base/decoder.h"
 #include "media/base/decoder_buffer.h"
 #include "media/base/media_export.h"
 #include "media/base/pipeline_status.h"
@@ -24,9 +25,9 @@
 class AudioBuffer;
 class CdmContext;
 
-class MEDIA_EXPORT AudioDecoder {
+class MEDIA_EXPORT AudioDecoder : public Decoder {
  public:
-  // Callback for VideoDecoder initialization.
+  // Callback for AudioDecoder initialization.
   using InitCB = base::OnceCallback<void(Status)>;
 
   // Callback for AudioDecoder to return a decoded frame whenever it becomes
@@ -43,20 +44,7 @@
   // Note: Since this is a destructor, |this| will be destroyed after this call.
   // Make sure the callbacks fired from this call doesn't post any task that
   // depends on |this|.
-  virtual ~AudioDecoder();
-
-  // Returns the name of the decoder for logging and decoder selection purposes.
-  // This name should be available immediately after construction (e.g. before
-  // Initialize() is called). It should also be stable in the sense that the
-  // name does not change across multiple constructions.
-  // TODO(xhwang): Rename this method since the name is not only for display.
-  virtual std::string GetDisplayName() const = 0;
-
-  // Returns true if the implementation is expected to be implemented by the
-  // platform. The value should be available immediately after construction and
-  // should not change within the lifetime of a decoder instance. The value is
-  // used only for logging.
-  virtual bool IsPlatformDecoder() const;
+  ~AudioDecoder() override;
 
   // Initializes an AudioDecoder with |config|, executing the |init_cb| upon
   // completion.
diff --git a/media/base/decoder.cc b/media/base/decoder.cc
new file mode 100644
index 0000000..50c2b8d
--- /dev/null
+++ b/media/base/decoder.cc
@@ -0,0 +1,21 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/base/decoder.h"
+
+namespace media {
+
+Decoder::Decoder() = default;
+
+Decoder::~Decoder() = default;
+
+bool Decoder::IsPlatformDecoder() const {
+  return false;
+}
+
+bool Decoder::SupportsDecryption() const {
+  return false;
+}
+
+}  // namespace media
diff --git a/media/base/decoder.h b/media/base/decoder.h
new file mode 100644
index 0000000..3c0d6c3
--- /dev/null
+++ b/media/base/decoder.h
@@ -0,0 +1,44 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_BASE_DECODER_H_
+#define MEDIA_BASE_DECODER_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "media/base/media_export.h"
+
+namespace media {
+
+class MEDIA_EXPORT Decoder {
+ public:
+  virtual ~Decoder();
+
+  // Returns true if the implementation is expected to be implemented by the
+  // platform. The value should be available immediately after construction and
+  // should not change within the lifetime of a decoder instance.
+  virtual bool IsPlatformDecoder() const;
+
+  // Returns true if the implementation supports decoding configs with
+  // encryption.
+  // TODO(crbug.com/1099488): Sometimes it's not possible to give a definitive
+  // yes or no answer unless more context is given. While this doesn't pose any
+  // problems, it does allow incompatible decoders to pass the filtering step in
+  // |DecoderSelector| potentially slowing down the selection process.
+  virtual bool SupportsDecryption() const;
+
+  // Returns the name of the decoder for logging and decoder selection purposes.
+  // This name should be available immediately after construction, and should
+  // also be stable in the sense that the name does not change across multiple
+  // constructions.
+  virtual std::string GetDisplayName() const = 0;
+
+ protected:
+  Decoder();
+};
+
+}  // namespace media
+
+#endif  // MEDIA_BASE_DECODER_H_
diff --git a/media/base/fake_demuxer_stream.cc b/media/base/fake_demuxer_stream.cc
index 61b463a..0fb35ec 100644
--- a/media/base/fake_demuxer_stream.cc
+++ b/media/base/fake_demuxer_stream.cc
@@ -31,10 +31,10 @@
 
 const int kStartTimestampMs = 0;
 const int kDurationMs = 30;
-const int kStartWidth = 320;
-const int kStartHeight = 240;
-const int kWidthDelta = 4;
-const int kHeightDelta = 3;
+const int kDefaultStartWidth = 320;
+const int kDefaultStartHeight = 240;
+const int kDefaultWidthDelta = 4;
+const int kDefaultHeightDelta = 3;
 const uint8_t kKeyId[] = {0x00, 0x01, 0x02, 0x03};
 const uint8_t kIv[] = {0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
@@ -42,11 +42,25 @@
 FakeDemuxerStream::FakeDemuxerStream(int num_configs,
                                      int num_buffers_in_one_config,
                                      bool is_encrypted)
+    : FakeDemuxerStream(
+          num_configs,
+          num_buffers_in_one_config,
+          is_encrypted,
+          gfx::Size(kDefaultStartWidth, kDefaultStartHeight),
+          gfx::Vector2dF(kDefaultWidthDelta, kDefaultHeightDelta)) {}
+
+FakeDemuxerStream::FakeDemuxerStream(int num_configs,
+                                     int num_buffers_in_one_config,
+                                     bool is_encrypted,
+                                     gfx::Size start_coded_size,
+                                     gfx::Vector2dF coded_size_delta)
     : task_runner_(base::ThreadTaskRunnerHandle::Get()),
       num_configs_(num_configs),
       num_buffers_in_one_config_(num_buffers_in_one_config),
       config_changes_(num_configs > 1),
       is_encrypted_(is_encrypted),
+      start_coded_size_(start_coded_size),
+      coded_size_delta_(coded_size_delta),
       read_to_hold_(-1) {
   DCHECK_GT(num_configs, 0);
   DCHECK_GT(num_buffers_in_one_config, 0);
@@ -63,7 +77,7 @@
   num_buffers_returned_ = 0;
   current_timestamp_ = base::TimeDelta::FromMilliseconds(kStartTimestampMs);
   duration_ = base::TimeDelta::FromMilliseconds(kDurationMs);
-  next_coded_size_ = gfx::Size(kStartWidth, kStartHeight);
+  next_size_ = start_coded_size_;
   next_read_num_ = 0;
 }
 
@@ -160,14 +174,14 @@
 }
 
 void FakeDemuxerStream::UpdateVideoDecoderConfig() {
-  const gfx::Rect kVisibleRect(kStartWidth, kStartHeight);
+  const gfx::Rect kVisibleRect(next_size_.width(), next_size_.height());
   video_decoder_config_.Initialize(
       kCodecVP8, VIDEO_CODEC_PROFILE_UNKNOWN,
       VideoDecoderConfig::AlphaMode::kIsOpaque, VideoColorSpace(),
-      kNoTransformation, next_coded_size_, kVisibleRect, next_coded_size_,
-      EmptyExtraData(),
+      kNoTransformation, next_size_, kVisibleRect, next_size_, EmptyExtraData(),
       is_encrypted_ ? EncryptionScheme::kCenc : EncryptionScheme::kUnencrypted);
-  next_coded_size_.Enlarge(kWidthDelta, kHeightDelta);
+  next_size_.set_width(next_size_.width() + coded_size_delta_.x());
+  next_size_.set_height(next_size_.height() + coded_size_delta_.y());
 }
 
 void FakeDemuxerStream::DoRead() {
diff --git a/media/base/fake_demuxer_stream.h b/media/base/fake_demuxer_stream.h
index fb742c5..9abb3789 100644
--- a/media/base/fake_demuxer_stream.h
+++ b/media/base/fake_demuxer_stream.h
@@ -26,6 +26,19 @@
   FakeDemuxerStream(int num_configs,
                     int num_buffers_in_one_config,
                     bool is_encrypted);
+  // Constructs an object that outputs |num_configs| different configs in
+  // sequence with |num_frames_in_one_config| buffers for each config. The
+  // output buffers are encrypted if |is_encrypted| is true.
+  // The starting config |coded_size| is specified by the
+  // |start_coded_size| parameter, and each config change increases/decreases it
+  // by the |coded_size_delta| parameter.
+  // The returned config always has equal |coded_size| and |visible_rect|
+  // properties.
+  FakeDemuxerStream(int num_configs,
+                    int num_buffers_in_one_config,
+                    bool is_encrypted,
+                    gfx::Size start_coded_size,
+                    gfx::Vector2dF coded_size_delta);
   ~FakeDemuxerStream() override;
 
   // DemuxerStream implementation.
@@ -82,6 +95,8 @@
   const int num_buffers_in_one_config_;
   const bool config_changes_;
   const bool is_encrypted_;
+  const gfx::Size start_coded_size_;
+  const gfx::Vector2dF coded_size_delta_;
 
   int num_configs_left_;
 
@@ -93,7 +108,7 @@
   base::TimeDelta current_timestamp_;
   base::TimeDelta duration_;
 
-  gfx::Size next_coded_size_;
+  gfx::Size next_size_;
   VideoDecoderConfig video_decoder_config_;
 
   ReadCB read_cb_;
diff --git a/media/base/mock_filters.cc b/media/base/mock_filters.cc
index 0e808c28..130bde4f 100644
--- a/media/base/mock_filters.cc
+++ b/media/base/mock_filters.cc
@@ -72,22 +72,56 @@
   liveness_ = liveness;
 }
 
-MockVideoDecoder::MockVideoDecoder(const std::string& decoder_name)
-    : decoder_name_(decoder_name) {
+MockVideoDecoder::MockVideoDecoder() : MockVideoDecoder("MockVideoDecoder") {}
+
+MockVideoDecoder::MockVideoDecoder(std::string decoder_name)
+    : MockVideoDecoder(false, false, std::move(decoder_name)) {}
+
+MockVideoDecoder::MockVideoDecoder(bool is_platform_decoder,
+                                   bool supports_decryption,
+                                   std::string decoder_name)
+    : is_platform_decoder_(is_platform_decoder),
+      supports_decryption_(supports_decryption),
+      decoder_name_(std::move(decoder_name)) {
   ON_CALL(*this, CanReadWithoutStalling()).WillByDefault(Return(true));
 }
 
 MockVideoDecoder::~MockVideoDecoder() = default;
 
+bool MockVideoDecoder::IsPlatformDecoder() const {
+  return is_platform_decoder_;
+}
+
+bool MockVideoDecoder::SupportsDecryption() const {
+  return supports_decryption_;
+}
+
 std::string MockVideoDecoder::GetDisplayName() const {
   return decoder_name_;
 }
 
-MockAudioDecoder::MockAudioDecoder(const std::string& decoder_name)
-    : decoder_name_(decoder_name) {}
+MockAudioDecoder::MockAudioDecoder() : MockAudioDecoder("MockAudioDecoder") {}
+
+MockAudioDecoder::MockAudioDecoder(std::string decoder_name)
+    : MockAudioDecoder(false, false, std::move(decoder_name)) {}
+
+MockAudioDecoder::MockAudioDecoder(bool is_platform_decoder,
+                                   bool supports_decryption,
+                                   std::string decoder_name)
+    : is_platform_decoder_(is_platform_decoder),
+      supports_decryption_(supports_decryption),
+      decoder_name_(decoder_name) {}
 
 MockAudioDecoder::~MockAudioDecoder() = default;
 
+bool MockAudioDecoder::IsPlatformDecoder() const {
+  return is_platform_decoder_;
+}
+
+bool MockAudioDecoder::SupportsDecryption() const {
+  return supports_decryption_;
+}
+
 std::string MockAudioDecoder::GetDisplayName() const {
   return decoder_name_;
 }
diff --git a/media/base/mock_filters.h b/media/base/mock_filters.h
index c0d4871..1929c6e 100644
--- a/media/base/mock_filters.h
+++ b/media/base/mock_filters.h
@@ -216,12 +216,19 @@
 
 class MockVideoDecoder : public VideoDecoder {
  public:
-  explicit MockVideoDecoder(
-      const std::string& decoder_name = "MockVideoDecoder");
+  MockVideoDecoder();
+  explicit MockVideoDecoder(std::string decoder_name);
+  MockVideoDecoder(bool is_platform_decoder,
+                   bool supports_decryption,
+                   std::string decoder_name);
   ~MockVideoDecoder() override;
 
-  // VideoDecoder implementation.
+  // Decoder implementation
+  bool IsPlatformDecoder() const override;
+  bool SupportsDecryption() const override;
   std::string GetDisplayName() const override;
+
+  // VideoDecoder implementation.
   void Initialize(const VideoDecoderConfig& config,
                   bool low_delay,
                   CdmContext* cdm_context,
@@ -248,18 +255,27 @@
   MOCK_CONST_METHOD0(NeedsBitstreamConversion, bool());
 
  private:
-  std::string decoder_name_;
+  const bool is_platform_decoder_;
+  const bool supports_decryption_;
+  const std::string decoder_name_;
   DISALLOW_COPY_AND_ASSIGN(MockVideoDecoder);
 };
 
 class MockAudioDecoder : public AudioDecoder {
  public:
-  explicit MockAudioDecoder(
-      const std::string& decoder_name = "MockAudioDecoder");
+  MockAudioDecoder();
+  explicit MockAudioDecoder(std::string decoder_name);
+  explicit MockAudioDecoder(bool is_platform_decoder,
+                            bool supports_decryption,
+                            std::string decoder_name);
   ~MockAudioDecoder() override;
 
-  // AudioDecoder implementation.
+  // Decoder implementation
+  bool IsPlatformDecoder() const override;
+  bool SupportsDecryption() const override;
   std::string GetDisplayName() const override;
+
+  // AudioDecoder implementation.
   void Initialize(const AudioDecoderConfig& config,
                   CdmContext* cdm_context,
                   InitCB init_cb,
@@ -278,7 +294,9 @@
   MOCK_METHOD1(Reset_, void(base::OnceClosure&));
 
  private:
-  std::string decoder_name_;
+  const bool is_platform_decoder_;
+  const bool supports_decryption_;
+  const std::string decoder_name_;
   DISALLOW_COPY_AND_ASSIGN(MockAudioDecoder);
 };
 
diff --git a/media/base/test_helpers.cc b/media/base/test_helpers.cc
index 140bcba..ed2b5f62 100644
--- a/media/base/test_helpers.cc
+++ b/media/base/test_helpers.cc
@@ -168,6 +168,7 @@
 
 static const gfx::Size kNormalSize(320, 240);
 static const gfx::Size kLargeSize(640, 480);
+static const gfx::Size kExtraLargeSize(15360, 8640);
 
 // static
 VideoDecoderConfig TestVideoConfig::Invalid() {
@@ -231,6 +232,18 @@
 }
 
 // static
+VideoDecoderConfig TestVideoConfig::ExtraLarge(VideoCodec codec) {
+  return GetTestConfig(codec, MinProfile(codec), VideoColorSpace::JPEG(),
+                       VIDEO_ROTATION_0, kExtraLargeSize, false);
+}
+
+// static
+VideoDecoderConfig TestVideoConfig::ExtraLargeEncrypted(VideoCodec codec) {
+  return GetTestConfig(codec, MinProfile(codec), VideoColorSpace::JPEG(),
+                       VIDEO_ROTATION_0, kExtraLargeSize, true);
+}
+
+// static
 gfx::Size TestVideoConfig::NormalCodedSize() {
   return kNormalSize;
 }
@@ -240,16 +253,41 @@
   return kLargeSize;
 }
 
+// static
+gfx::Size TestVideoConfig::ExtraLargeCodedSize() {
+  return kExtraLargeSize;
+}
+
 AudioDecoderConfig TestAudioConfig::Normal() {
   return AudioDecoderConfig(kCodecVorbis, kSampleFormatPlanarF32,
-                            CHANNEL_LAYOUT_STEREO, 44100, EmptyExtraData(),
-                            EncryptionScheme::kUnencrypted);
+                            CHANNEL_LAYOUT_STEREO, NormalSampleRateValue(),
+                            EmptyExtraData(), EncryptionScheme::kUnencrypted);
 }
 
 AudioDecoderConfig TestAudioConfig::NormalEncrypted() {
   return AudioDecoderConfig(kCodecVorbis, kSampleFormatPlanarF32,
-                            CHANNEL_LAYOUT_STEREO, 44100, EmptyExtraData(),
-                            EncryptionScheme::kCenc);
+                            CHANNEL_LAYOUT_STEREO, NormalSampleRateValue(),
+                            EmptyExtraData(), EncryptionScheme::kCenc);
+}
+
+AudioDecoderConfig TestAudioConfig::HighSampleRate() {
+  return AudioDecoderConfig(kCodecVorbis, kSampleFormatPlanarF32,
+                            CHANNEL_LAYOUT_STEREO, HighSampleRateValue(),
+                            EmptyExtraData(), EncryptionScheme::kUnencrypted);
+}
+
+AudioDecoderConfig TestAudioConfig::HighSampleRateEncrypted() {
+  return AudioDecoderConfig(kCodecVorbis, kSampleFormatPlanarF32,
+                            CHANNEL_LAYOUT_STEREO, HighSampleRateValue(),
+                            EmptyExtraData(), EncryptionScheme::kCenc);
+}
+
+int TestAudioConfig::NormalSampleRateValue() {
+  return 44100;
+}
+
+int TestAudioConfig::HighSampleRateValue() {
+  return 192000;
 }
 
 // static
diff --git a/media/base/test_helpers.h b/media/base/test_helpers.h
index 4fd250ca..d214340 100644
--- a/media/base/test_helpers.h
+++ b/media/base/test_helpers.h
@@ -106,9 +106,14 @@
   static VideoDecoderConfig Large(VideoCodec codec = kCodecVP8);
   static VideoDecoderConfig LargeEncrypted(VideoCodec codec = kCodecVP8);
 
+  // Returns a configuration that is larger in dimensions that Large().
+  static VideoDecoderConfig ExtraLarge(VideoCodec codec = kCodecVP8);
+  static VideoDecoderConfig ExtraLargeEncrypted(VideoCodec codec = kCodecVP8);
+
   // Returns coded size for Normal and Large config.
   static gfx::Size NormalCodedSize();
   static gfx::Size LargeCodedSize();
+  static gfx::Size ExtraLargeCodedSize();
 
  private:
   DISALLOW_COPY_AND_ASSIGN(TestVideoConfig);
@@ -120,6 +125,14 @@
  public:
   static AudioDecoderConfig Normal();
   static AudioDecoderConfig NormalEncrypted();
+
+  // Returns configurations that have a higher sample rate than Normal()
+  static AudioDecoderConfig HighSampleRate();
+  static AudioDecoderConfig HighSampleRateEncrypted();
+
+  // Returns coded sample rate for Normal and HighSampleRate config.
+  static int NormalSampleRateValue();
+  static int HighSampleRateValue();
 };
 
 // Provides pre-canned AudioParameters objects.
diff --git a/media/base/video_decoder.cc b/media/base/video_decoder.cc
index 626ca0f..80ac2f5a 100644
--- a/media/base/video_decoder.cc
+++ b/media/base/video_decoder.cc
@@ -17,10 +17,6 @@
 
 VideoDecoder::~VideoDecoder() = default;
 
-bool VideoDecoder::IsPlatformDecoder() const {
-  return false;
-}
-
 bool VideoDecoder::NeedsBitstreamConversion() const {
   return false;
 }
diff --git a/media/base/video_decoder.h b/media/base/video_decoder.h
index 0648870..145ee96d 100644
--- a/media/base/video_decoder.h
+++ b/media/base/video_decoder.h
@@ -11,6 +11,7 @@
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
 #include "media/base/decode_status.h"
+#include "media/base/decoder.h"
 #include "media/base/media_export.h"
 #include "media/base/pipeline_status.h"
 #include "media/base/status.h"
@@ -24,7 +25,7 @@
 class VideoDecoderConfig;
 class VideoFrame;
 
-class MEDIA_EXPORT VideoDecoder {
+class MEDIA_EXPORT VideoDecoder : public Decoder {
  public:
   // Callback for VideoDecoder initialization.
   using InitCB = base::OnceCallback<void(Status status)>;
@@ -39,22 +40,7 @@
   using DecodeCB = base::OnceCallback<void(DecodeStatus)>;
 
   VideoDecoder();
-  virtual ~VideoDecoder();
-
-  // Returns the name of the decoder for logging and decoder selection purposes.
-  // This name should be available immediately after construction (e.g. before
-  // Initialize() is called). It should also be stable in the sense that the
-  // name does not change across multiple constructions.
-  virtual std::string GetDisplayName() const = 0;
-
-  // Returns true if the implementation is expected to be implemented by the
-  // platform. The value should be available immediately after construction and
-  // should not change within the lifetime of a decoder instance. The value is
-  // used for logging and metrics recording.
-  //
-  // TODO(sandersd): Use this to decide when to switch to software decode for
-  // low-resolution videos. https://crbug.com/684792
-  virtual bool IsPlatformDecoder() const;
+  ~VideoDecoder() override;
 
   // Initializes a VideoDecoder with the given |config|, executing the
   // |init_cb| upon completion. |output_cb| is called for each output frame
@@ -135,6 +121,7 @@
   // [|limits::kMinVideoDecodeThreads|, |limits::kMaxVideoDecodeThreads|].
   static int GetRecommendedThreadCount(int desired_threads);
 
+ private:
   DISALLOW_COPY_AND_ASSIGN(VideoDecoder);
 };
 
diff --git a/media/filters/BUILD.gn b/media/filters/BUILD.gn
index 0bc8f30..4edf43d3 100644
--- a/media/filters/BUILD.gn
+++ b/media/filters/BUILD.gn
@@ -85,6 +85,7 @@
   ]
 
   deps = [
+    "//base/util/type_safety:type_safety",
     "//cc/base",  # For MathUtil.
     "//media:media_buildflags",
     "//media/base",
diff --git a/media/filters/audio_decoder_stream_unittest.cc b/media/filters/audio_decoder_stream_unittest.cc
index 38f2011..f842fbf4 100644
--- a/media/filters/audio_decoder_stream_unittest.cc
+++ b/media/filters/audio_decoder_stream_unittest.cc
@@ -20,6 +20,7 @@
 using ::base::test::RunCallback;
 using ::base::test::RunOnceCallback;
 using testing::_;
+using testing::AnyNumber;
 using testing::DoAll;
 using testing::Invoke;
 using testing::Return;
@@ -98,8 +99,9 @@
   std::vector<std::unique_ptr<AudioDecoder>> CreateMockAudioDecoder() {
     auto decoder = std::make_unique<MockAudioDecoder>();
     EXPECT_CALL(*decoder, Initialize_(_, _, _, _, _))
-        .WillOnce(DoAll(SaveArg<3>(&decoder_output_cb_),
-                        RunOnceCallback<2>(OkStatus())));
+        .Times(AnyNumber())
+        .WillRepeatedly(DoAll(SaveArg<3>(&decoder_output_cb_),
+                              RunOnceCallback<2>(OkStatus())));
     decoder_ = decoder.get();
 
     std::vector<std::unique_ptr<AudioDecoder>> result;
diff --git a/media/filters/decoder_selector.cc b/media/filters/decoder_selector.cc
index 9f510bb..0bdd9214 100644
--- a/media/filters/decoder_selector.cc
+++ b/media/filters/decoder_selector.cc
@@ -4,6 +4,7 @@
 
 #include "media/filters/decoder_selector.h"
 
+#include <algorithm>
 #include <utility>
 
 #include "base/bind.h"
@@ -25,8 +26,40 @@
 
 namespace media {
 
+namespace {
+
 const char kSelectDecoderTrace[] = "DecoderSelector::SelectDecoder";
 
+DecoderPriority GetDefaultVideoDecoderPriority(
+    const VideoDecoderConfig& config) {
+#if defined(OS_ANDROID)
+  constexpr auto kSoftwareDecoderHeightCutoff = 360;
+#else
+  constexpr auto kSoftwareDecoderHeightCutoff = 720;
+#endif
+
+  // We only do a height check to err on the side of hardware decoding
+  return config.visible_rect().height() < kSoftwareDecoderHeightCutoff
+             ? DecoderPriority::kPreferSoftwareDecoders
+             : DecoderPriority::kPreferPlatformDecoders;
+}
+
+DecoderPriority GetDefaultAudioDecoderPriority(
+    const AudioDecoderConfig& /*config*/) {
+  // Platform audio decoders are not currently prioritized or deprioritized
+  return DecoderPriority::kUnspecified;
+}
+
+void SetDefaultDecoderPriorityCB(VideoDecoderSelector::DecoderPriorityCB* out) {
+  *out = base::BindRepeating(GetDefaultVideoDecoderPriority);
+}
+
+void SetDefaultDecoderPriorityCB(AudioDecoderSelector::DecoderPriorityCB* out) {
+  *out = base::BindRepeating(GetDefaultAudioDecoderPriority);
+}
+
+}  // namespace
+
 template <DemuxerStream::Type StreamType>
 DecoderSelector<StreamType>::DecoderSelector(
     scoped_refptr<base::SingleThreadTaskRunner> task_runner,
@@ -34,7 +67,9 @@
     MediaLog* media_log)
     : task_runner_(std::move(task_runner)),
       create_decoders_cb_(std::move(create_decoders_cb)),
-      media_log_(media_log) {}
+      media_log_(media_log) {
+  SetDefaultDecoderPriorityCB(&decoder_priority_cb_);
+}
 
 template <DemuxerStream::Type StreamType>
 DecoderSelector<StreamType>::~DecoderSelector() {
@@ -85,7 +120,8 @@
   // start selection with the full list of potential decoders.
   if (!is_selecting_decoders_) {
     is_selecting_decoders_ = true;
-    decoders_ = create_decoders_cb_.Run();
+    decoder_selection_start_ = base::TimeTicks::Now();
+    CreateDecoders();
   }
 
   InitializeDecoder();
@@ -98,11 +134,24 @@
   DCHECK(!select_decoder_cb_);
   is_selecting_decoders_ = false;
 
+  const std::string decoder_type = is_platform_decoder_ ? "HW" : "SW";
+  const std::string stream_type =
+      StreamType == DemuxerStream::AUDIO ? "Audio" : "Video";
+
+  if (is_selecting_for_config_change_) {
+    is_selecting_for_config_change_ = false;
+    base::UmaHistogramTimes("Media.ConfigChangeDecoderSelectionTime." +
+                                stream_type + "." + decoder_type,
+                            base::TimeTicks::Now() - decoder_selection_start_);
+  } else {
+    // Initial selection
+    base::UmaHistogramTimes(
+        "Media.InitialDecoderSelectionTime." + stream_type + "." + decoder_type,
+        base::TimeTicks::Now() - decoder_selection_start_);
+  }
+
   if (is_codec_changing_) {
     is_codec_changing_ = false;
-    const std::string decoder_type = is_platform_decoder_ ? "HW" : "SW";
-    const std::string stream_type =
-        StreamType == DemuxerStream::AUDIO ? "Audio" : "Video";
     base::UmaHistogramTimes(
         "Media.MSE.CodecChangeTime." + stream_type + "." + decoder_type,
         base::TimeTicks::Now() - codec_change_start_);
@@ -117,6 +166,8 @@
   DVLOG(2) << __func__;
   DCHECK(task_runner_->BelongsToCurrentThread());
 
+  is_selecting_for_config_change_ = true;
+
   DecoderConfig config = traits_->GetDecoderConfig(stream_);
   if (config.codec() != config_.codec()) {
     is_codec_changing_ = true;
@@ -125,6 +176,38 @@
 }
 
 template <DemuxerStream::Type StreamType>
+void DecoderSelector<StreamType>::PrependDecoder(
+    std::unique_ptr<Decoder> decoder) {
+  DVLOG(2) << __func__;
+  DCHECK(task_runner_->BelongsToCurrentThread());
+
+  // Decoders inserted directly should be given priority over those returned by
+  // |create_decoders_cb_|.
+  decoders_.insert(decoders_.begin(), std::move(decoder));
+
+  if (is_selecting_decoders_)
+    FilterAndSortAvailableDecoders();
+}
+
+template <DemuxerStream::Type StreamType>
+void DecoderSelector<StreamType>::OverrideDecoderPriorityCBForTesting(
+    DecoderPriorityCB decoder_priority_cb) {
+  decoder_priority_cb_ = std::move(decoder_priority_cb);
+}
+
+template <DemuxerStream::Type StreamType>
+void DecoderSelector<StreamType>::CreateDecoders() {
+  // Post-insert decoders returned by `create_decoders_cb_`, so that
+  // any decoders added via `PrependDecoder()` are not overwritten and retain
+  // priority (even if they are ultimately de-ranked by
+  // `FilterAndSortAvailableDecoders()`)
+  auto new_decoders = create_decoders_cb_.Run();
+  std::move(new_decoders.begin(), new_decoders.end(),
+            std::inserter(decoders_, decoders_.end()));
+  FilterAndSortAvailableDecoders();
+}
+
+template <DemuxerStream::Type StreamType>
 void DecoderSelector<StreamType>::InitializeDecoder() {
   DVLOG(2) << __func__;
   DCHECK(task_runner_->BelongsToCurrentThread());
@@ -231,7 +314,7 @@
   DCHECK(!config_.is_encrypted());
 
   // Try decoder selection again now that DDS is being used.
-  decoders_ = create_decoders_cb_.Run();
+  CreateDecoders();
   InitializeDecoder();
 }
 
@@ -251,6 +334,35 @@
                      std::move(decrypting_demuxer_stream_)));
 }
 
+template <DemuxerStream::Type StreamType>
+void DecoderSelector<StreamType>::FilterAndSortAvailableDecoders() {
+  // Filter out any decoders that do not support decryption
+  if (config_.is_encrypted()) {
+    auto const non_decrypting = std::remove_if(
+        decoders_.begin(), decoders_.end(),
+        [](auto& decoder) { return !decoder->SupportsDecryption(); });
+    decoders_.erase(non_decrypting, decoders_.end());
+  }
+
+  // If platform decoders are prioritized for this config, shift all platform
+  // decoders to the front of the list (retaining their relative order).
+  const auto decoder_priority = decoder_priority_cb_.Run(config_);
+  switch (decoder_priority) {
+    case DecoderPriority::kUnspecified:
+      break;
+    case DecoderPriority::kPreferPlatformDecoders:
+    case DecoderPriority::kPreferSoftwareDecoders: {
+      auto prefer_platform_decoder =
+          decoder_priority == DecoderPriority::kPreferPlatformDecoders;
+      std::stable_partition(decoders_.begin(), decoders_.end(),
+                            [prefer_platform_decoder](auto& decoder) {
+                              return decoder->IsPlatformDecoder() ==
+                                     prefer_platform_decoder;
+                            });
+    } break;
+  }
+}
+
 // These forward declarations tell the compiler that we will use
 // DecoderSelector with these arguments, allowing us to keep these definitions
 // in our .cc without causing linker errors. This also means if anyone tries to
diff --git a/media/filters/decoder_selector.h b/media/filters/decoder_selector.h
index b7e4326..2070411 100644
--- a/media/filters/decoder_selector.h
+++ b/media/filters/decoder_selector.h
@@ -28,6 +28,12 @@
 class DecryptingDemuxerStream;
 class MediaLog;
 
+enum class DecoderPriority {
+  kUnspecified,
+  kPreferPlatformDecoders,
+  kPreferSoftwareDecoders,
+};
+
 // DecoderSelector handles construction and initialization of Decoders for a
 // DemuxerStream, and maintains the state required for decoder fallback.
 // The template parameter |StreamType| is the type of stream we will be
@@ -45,6 +51,11 @@
   using CreateDecodersCB =
       base::RepeatingCallback<std::vector<std::unique_ptr<Decoder>>()>;
 
+  // Evaluates what type of decoders should be prioritized for the given config.
+  // If |kUnspecified| is returned, nothing is prioritized.
+  using DecoderPriorityCB =
+      base::RepeatingCallback<DecoderPriority(const DecoderConfig&)>;
+
   // Emits the result of a single call to SelectDecoder(). Parameters are
   //   1: The initialized Decoder. nullptr if selection failed.
   //   2: The initialized DecryptingDemuxerStream, if one was created. This
@@ -91,16 +102,30 @@
   // Currently only for metric collection.
   void NotifyConfigChanged();
 
+  // Adds an additional decoder candidate to be considered when selecting a
+  // decoder. This decoder is inserted ahead of the decoders returned by
+  // |CreateDecodersCB| to give it priority over the default set, though it
+  // may be by deprioritized if |DecoderPriorityCB| considers another decoder a
+  // better candidate. This decoder should be uninitialized.
+  void PrependDecoder(std::unique_ptr<Decoder> decoder);
+
+  // Overrides the default function for evaluation platform decoder priority.
+  // Useful for writing tests in a platform-agnostic manner.
+  void OverrideDecoderPriorityCBForTesting(DecoderPriorityCB priority_cb);
+
  private:
+  void CreateDecoders();
   void InitializeDecoder();
   void OnDecoderInitializeDone(Status status);
   void ReturnNullDecoder();
   void InitializeDecryptingDemuxerStream();
   void OnDecryptingDemuxerStreamInitializeDone(PipelineStatus status);
   void RunSelectDecoderCB();
+  void FilterAndSortAvailableDecoders();
 
   scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
   CreateDecodersCB create_decoders_cb_;
+  DecoderPriorityCB decoder_priority_cb_;
   MediaLog* media_log_;
 
   StreamTraits* traits_ = nullptr;
@@ -122,6 +147,8 @@
   // Metrics.
   bool is_platform_decoder_ = false;
   bool is_codec_changing_ = false;
+  bool is_selecting_for_config_change_ = false;
+  base::TimeTicks decoder_selection_start_;
   base::TimeTicks codec_change_start_;
 
   base::WeakPtrFactory<DecoderSelector> weak_this_factory_{this};
diff --git a/media/filters/decoder_selector_unittest.cc b/media/filters/decoder_selector_unittest.cc
index d625e48..b498309 100644
--- a/media/filters/decoder_selector_unittest.cc
+++ b/media/filters/decoder_selector_unittest.cc
@@ -53,6 +53,19 @@
   kAlwaysSucceed,
 };
 
+bool DecoderCapabilitySupportsDecryption(DecoderCapability capability) {
+  switch (capability) {
+    case kAlwaysFail:
+      return false;
+    case kClearOnly:
+      return false;
+    case kEncryptedOnly:
+      return true;
+    case kAlwaysSucceed:
+      return true;
+  }
+}
+
 Status IsConfigSupported(DecoderCapability capability, bool is_encrypted) {
   switch (capability) {
     case kAlwaysFail:
@@ -69,6 +82,8 @@
 const char kNoDecoder[] = "";
 const char kDecoder1[] = "Decoder1";
 const char kDecoder2[] = "Decoder2";
+const char kDecoder3[] = "Decoder3";
+const char kDecoder4[] = "Decoder4";
 
 // Specializations for the AUDIO version of the test.
 class AudioDecoderSelectorTestParam {
@@ -89,6 +104,35 @@
     return std::make_unique<StreamTraits>(media_log, CHANNEL_LAYOUT_STEREO);
   }
 
+  static media::DecoderPriority MockDecoderPriorityCB(
+      const media::AudioDecoderConfig& config) {
+    return config.samples_per_second() >
+                   TestAudioConfig::NormalSampleRateValue()
+               ? media::DecoderPriority::kPreferPlatformDecoders
+               : media::DecoderPriority::kPreferSoftwareDecoders;
+  }
+  static media::DecoderPriority UnspecifiedDecoderPriorityCB(
+      const media::AudioDecoderConfig& /*config*/) {
+    return media::DecoderPriority::kUnspecified;
+  }
+
+  static void UseNormalClearDecoderConfig(
+      StrictMock<MockDemuxerStream>& stream) {
+    stream.set_audio_decoder_config(TestAudioConfig::Normal());
+  }
+  static void UseHighQualityClearDecoderConfig(
+      StrictMock<MockDemuxerStream>& stream) {
+    stream.set_audio_decoder_config(TestAudioConfig::HighSampleRate());
+  }
+  static void UseNormalEncryptedDecoderConfig(
+      StrictMock<MockDemuxerStream>& stream) {
+    stream.set_audio_decoder_config(TestAudioConfig::NormalEncrypted());
+  }
+  static void UseHighQualityEncryptedDecoderConfig(
+      StrictMock<MockDemuxerStream>& stream) {
+    stream.set_audio_decoder_config(TestAudioConfig::HighSampleRateEncrypted());
+  }
+
   // Decoder::Initialize() takes different parameters depending on the type.
   static void ExpectInitialize(MockDecoder* decoder,
                                DecoderCapability capability) {
@@ -101,6 +145,10 @@
               IsConfigSupported(capability, config.is_encrypted()));
         });
   }
+
+  static void ExpectNotInitialize(MockDecoder* decoder) {
+    EXPECT_CALL(*decoder, Initialize_(_, _, _, _, _)).Times(0);
+  }
 };
 
 // Allocate storage for the member variables.
@@ -127,6 +175,35 @@
     return std::make_unique<StreamTraits>(media_log);
   }
 
+  static media::DecoderPriority MockDecoderPriorityCB(
+      const media::VideoDecoderConfig& config) {
+    return config.visible_rect().height() >
+                   TestVideoConfig::NormalCodedSize().height()
+               ? media::DecoderPriority::kPreferPlatformDecoders
+               : media::DecoderPriority::kPreferSoftwareDecoders;
+  }
+  static media::DecoderPriority UnspecifiedDecoderPriorityCB(
+      const media::VideoDecoderConfig& /*config*/) {
+    return media::DecoderPriority::kUnspecified;
+  }
+
+  static void UseNormalClearDecoderConfig(
+      StrictMock<MockDemuxerStream>& stream) {
+    stream.set_video_decoder_config(TestVideoConfig::Normal());
+  }
+  static void UseHighQualityClearDecoderConfig(
+      StrictMock<MockDemuxerStream>& stream) {
+    stream.set_video_decoder_config(TestVideoConfig::Large());
+  }
+  static void UseNormalEncryptedDecoderConfig(
+      StrictMock<MockDemuxerStream>& stream) {
+    stream.set_video_decoder_config(TestVideoConfig::NormalEncrypted());
+  }
+  static void UseHighQualityEncryptedDecoderConfig(
+      StrictMock<MockDemuxerStream>& stream) {
+    stream.set_video_decoder_config(TestVideoConfig::LargeEncrypted());
+  }
+
   static void ExpectInitialize(MockDecoder* decoder,
                                DecoderCapability capability) {
     EXPECT_CALL(*decoder, Initialize_(_, _, _, _, _, _))
@@ -138,6 +215,10 @@
                   IsConfigSupported(capability, config.is_encrypted()));
             });
   }
+
+  static void ExpectNotInitialize(MockDecoder* decoder) {
+    EXPECT_CALL(*decoder, Initialize_(_, _, _, _, _, _)).Times(0);
+  }
 };
 
 // Allocate storate for the member variables.
@@ -161,6 +242,27 @@
   using Decoder = typename StreamTraits::DecoderType;
   using MockDecoder = typename TypeParam::MockDecoder;
   using Output = typename TypeParam::Output;
+  using Selector = DecoderSelector<TypeParam::kStreamType>;
+
+  struct MockDecoderArgs {
+    static MockDecoderArgs Create(std::string decoder_name,
+                                  DecoderCapability capability) {
+      MockDecoderArgs result;
+      result.decoder_name = std::move(decoder_name);
+      result.capability = capability;
+      result.supports_decryption =
+          DecoderCapabilitySupportsDecryption(capability);
+      result.is_platform_decoder = false;
+      result.expect_not_initialized = false;
+      return result;
+    }
+
+    std::string decoder_name;
+    DecoderCapability capability;
+    bool supports_decryption;
+    bool is_platform_decoder;
+    bool expect_not_initialized;
+  };
 
   DecoderSelectorTest()
       : traits_(TypeParam::CreateStreamTraits(&media_log_)),
@@ -189,11 +291,22 @@
     use_decrypting_decoder_ = true;
   }
 
-  void AddMockDecoder(const std::string& decoder_name,
-                      DecoderCapability capability) {
+  void AddMockDecoder(std::string decoder_name, DecoderCapability capability) {
+    auto args = MockDecoderArgs::Create(std::move(decoder_name), capability);
+    AddMockDecoder(std::move(args));
+  }
+
+  void AddMockPlatformDecoder(const std::string& decoder_name,
+                              DecoderCapability capability) {
+    auto args = MockDecoderArgs::Create(std::move(decoder_name), capability);
+    args.is_platform_decoder = true;
+    AddMockDecoder(std::move(args));
+  }
+
+  void AddMockDecoder(MockDecoderArgs args) {
     // Actual decoders are created in CreateDecoders(), which may be called
     // multiple times by the DecoderSelector.
-    mock_decoders_to_create_.emplace_back(decoder_name, capability);
+    mock_decoders_to_create_.push_back(std::move(args));
   }
 
   std::vector<std::unique_ptr<Decoder>> CreateDecoders() {
@@ -207,10 +320,16 @@
     }
 #endif  // !defined(OS_ANDROID)
 
-    for (const auto& info : mock_decoders_to_create_) {
+    for (const auto& args : mock_decoders_to_create_) {
       std::unique_ptr<StrictMock<MockDecoder>> decoder =
-          std::make_unique<StrictMock<MockDecoder>>(info.first);
-      TypeParam::ExpectInitialize(decoder.get(), info.second);
+          std::make_unique<StrictMock<MockDecoder>>(args.is_platform_decoder,
+                                                    args.supports_decryption,
+                                                    args.decoder_name);
+      if (args.expect_not_initialized) {
+        TypeParam::ExpectNotInitialize(decoder.get());
+      } else {
+        TypeParam::ExpectInitialize(decoder.get(), args.capability);
+      }
       decoders.push_back(std::move(decoder));
     }
 
@@ -248,42 +367,28 @@
   }
 
   void CreateDecoderSelector() {
-    decoder_selector_ =
-        std::make_unique<DecoderSelector<TypeParam::kStreamType>>(
-            task_environment_.GetMainThreadTaskRunner(),
-            base::BindRepeating(&Self::CreateDecoders, base::Unretained(this)),
-            &media_log_);
+    decoder_selector_ = std::make_unique<Selector>(
+        task_environment_.GetMainThreadTaskRunner(),
+        base::BindRepeating(&Self::CreateDecoders, base::Unretained(this)),
+        &media_log_);
+    decoder_selector_->OverrideDecoderPriorityCBForTesting(
+        base::BindRepeating(TypeParam::MockDecoderPriorityCB));
     decoder_selector_->Initialize(
         traits_.get(), &demuxer_stream_, cdm_context_.get(),
         base::BindRepeating(&Self::OnWaiting, base::Unretained(this)));
   }
 
   void UseClearDecoderConfig() {
-    switch (TypeParam::kStreamType) {
-      case DemuxerStream::AUDIO:
-        demuxer_stream_.set_audio_decoder_config(TestAudioConfig::Normal());
-        break;
-      case DemuxerStream::VIDEO:
-        demuxer_stream_.set_video_decoder_config(TestVideoConfig::Normal());
-        break;
-      default:
-        NOTREACHED();
-    }
+    TypeParam::UseNormalClearDecoderConfig(demuxer_stream_);
   }
-
+  void UseHighQualityClearDecoderConfig() {
+    TypeParam::UseHighQualityClearDecoderConfig(demuxer_stream_);
+  }
   void UseEncryptedDecoderConfig() {
-    switch (TypeParam::kStreamType) {
-      case DemuxerStream::AUDIO:
-        demuxer_stream_.set_audio_decoder_config(
-            TestAudioConfig::NormalEncrypted());
-        break;
-      case DemuxerStream::VIDEO:
-        demuxer_stream_.set_video_decoder_config(
-            TestVideoConfig::NormalEncrypted());
-        break;
-      default:
-        NOTREACHED();
-    }
+    TypeParam::UseNormalEncryptedDecoderConfig(demuxer_stream_);
+  }
+  void UseHighQualityEncryptedDecoderConfig() {
+    TypeParam::UseHighQualityEncryptedDecoderConfig(demuxer_stream_);
   }
 
   void SelectDecoder() {
@@ -303,11 +408,10 @@
   std::unique_ptr<StrictMock<MockCdmContext>> cdm_context_;
   std::unique_ptr<NiceMock<MockDecryptor>> decryptor_;
 
-  std::unique_ptr<DecoderSelector<TypeParam::kStreamType>> decoder_selector_;
+  std::unique_ptr<Selector> decoder_selector_;
 
   bool use_decrypting_decoder_ = false;
-  std::vector<std::pair<std::string, DecoderCapability>>
-      mock_decoders_to_create_;
+  std::vector<MockDecoderArgs> mock_decoders_to_create_;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(DecoderSelectorTest);
@@ -388,8 +492,180 @@
   this->SelectDecoder();
 }
 
+// Tests that platform decoders are prioritized for
+// high-quality configs, retaining their relative order.
+TYPED_TEST(DecoderSelectorTest, ClearStream_PrioritizePlatformDecoders) {
+  this->AddMockPlatformDecoder(kDecoder1, kAlwaysSucceed);
+  this->AddMockDecoder(kDecoder2, kAlwaysSucceed);
+  this->AddMockPlatformDecoder(kDecoder3, kAlwaysSucceed);
+  this->AddMockDecoder(kDecoder4, kAlwaysSucceed);
+
+  this->UseHighQualityClearDecoderConfig();
+  this->CreateDecoderSelector();
+
+  EXPECT_CALL(*this, OnDecoderSelected(kDecoder1, IsNull()));
+  this->SelectDecoder();
+  EXPECT_CALL(*this, OnDecoderSelected(kDecoder3, IsNull()));
+  this->SelectDecoder();
+  EXPECT_CALL(*this, OnDecoderSelected(kDecoder2, IsNull()));
+  this->SelectDecoder();
+  EXPECT_CALL(*this, OnDecoderSelected(kDecoder4, IsNull()));
+  this->SelectDecoder();
+
+  EXPECT_CALL(*this, OnDecoderSelected(kNoDecoder, IsNull()));
+  this->SelectDecoder();
+}
+
+// Tests that non-platform decoders are prioritized for
+// normal-quality configs, retaining their relative order.
+TYPED_TEST(DecoderSelectorTest, ClearStream_DeprioritizePlatformDecoders) {
+  this->AddMockPlatformDecoder(kDecoder1, kAlwaysSucceed);
+  this->AddMockDecoder(kDecoder2, kAlwaysSucceed);
+  this->AddMockPlatformDecoder(kDecoder3, kAlwaysSucceed);
+  this->AddMockDecoder(kDecoder4, kAlwaysSucceed);
+
+  this->UseClearDecoderConfig();
+  this->CreateDecoderSelector();
+
+  EXPECT_CALL(*this, OnDecoderSelected(kDecoder2, IsNull()));
+  this->SelectDecoder();
+  EXPECT_CALL(*this, OnDecoderSelected(kDecoder4, IsNull()));
+  this->SelectDecoder();
+  EXPECT_CALL(*this, OnDecoderSelected(kDecoder1, IsNull()));
+  this->SelectDecoder();
+  EXPECT_CALL(*this, OnDecoderSelected(kDecoder3, IsNull()));
+  this->SelectDecoder();
+
+  EXPECT_CALL(*this, OnDecoderSelected(kNoDecoder, IsNull()));
+  this->SelectDecoder();
+}
+
+// Tests that platform and non-platform decoders remain in the order they are
+// given for a priority callback returning 'kNop'.
+TYPED_TEST(DecoderSelectorTest,
+           ClearStream_NopPriorityCallbackRetainsGivenOrder) {
+  this->AddMockPlatformDecoder(kDecoder1, kAlwaysSucceed);
+  this->AddMockDecoder(kDecoder2, kAlwaysSucceed);
+  this->AddMockPlatformDecoder(kDecoder3, kAlwaysSucceed);
+  this->AddMockDecoder(kDecoder4, kAlwaysSucceed);
+
+  this->UseClearDecoderConfig();
+  this->CreateDecoderSelector();
+  this->decoder_selector_->OverrideDecoderPriorityCBForTesting(
+      base::BindRepeating(TypeParam::UnspecifiedDecoderPriorityCB));
+
+  EXPECT_CALL(*this, OnDecoderSelected(kDecoder1, IsNull()));
+  this->SelectDecoder();
+  EXPECT_CALL(*this, OnDecoderSelected(kDecoder2, IsNull()));
+  this->SelectDecoder();
+  EXPECT_CALL(*this, OnDecoderSelected(kDecoder3, IsNull()));
+  this->SelectDecoder();
+  EXPECT_CALL(*this, OnDecoderSelected(kDecoder4, IsNull()));
+  this->SelectDecoder();
+
+  EXPECT_CALL(*this, OnDecoderSelected(kNoDecoder, IsNull()));
+  this->SelectDecoder();
+}
+
 // Tests for encrypted streams.
 
+// Tests that non-decrypting decoders are filtered out by DecoderSelector
+// before being initialized.
+TYPED_TEST(DecoderSelectorTest,
+           EncryptedStream_NoDecryptor_DecodersNotInitialized) {
+  using MockDecoderArgs =
+      typename DecoderSelectorTest<TypeParam>::MockDecoderArgs;
+
+  auto args = MockDecoderArgs::Create(kDecoder1, kClearOnly);
+  args.expect_not_initialized = true;
+  this->AddMockDecoder(std::move(args));
+
+  args = MockDecoderArgs::Create(kDecoder2, kClearOnly);
+  args.expect_not_initialized = true;
+  this->AddMockDecoder(std::move(args));
+
+  this->UseEncryptedDecoderConfig();
+  this->CreateDecoderSelector();
+
+  EXPECT_CALL(*this, OnDecoderSelected(kNoDecoder, IsNull()));
+  this->SelectDecoder();
+}
+
+// Tests that for an encrypted stream, platform decoders are prioritized for
+// high-quality configs, retaining their relative order.
+TYPED_TEST(DecoderSelectorTest, EncryptedStream_PrioritizePlatformDecoders) {
+  this->AddMockPlatformDecoder(kDecoder1, kAlwaysSucceed);
+  this->AddMockDecoder(kDecoder2, kAlwaysSucceed);
+  this->AddMockPlatformDecoder(kDecoder3, kAlwaysSucceed);
+  this->AddMockDecoder(kDecoder4, kAlwaysSucceed);
+
+  this->UseHighQualityEncryptedDecoderConfig();
+  this->CreateDecoderSelector();
+
+  EXPECT_CALL(*this, OnDecoderSelected(kDecoder1, IsNull()));
+  this->SelectDecoder();
+  EXPECT_CALL(*this, OnDecoderSelected(kDecoder3, IsNull()));
+  this->SelectDecoder();
+  EXPECT_CALL(*this, OnDecoderSelected(kDecoder2, IsNull()));
+  this->SelectDecoder();
+  EXPECT_CALL(*this, OnDecoderSelected(kDecoder4, IsNull()));
+  this->SelectDecoder();
+
+  EXPECT_CALL(*this, OnDecoderSelected(kNoDecoder, IsNull()));
+  this->SelectDecoder();
+}
+
+// Tests that for an encrypted stream, non-platform decoders are prioritized for
+// normal-quality configs, retaining their relative order.
+TYPED_TEST(DecoderSelectorTest, EncryptedStream_DeprioritizePlatformDecoders) {
+  this->AddMockPlatformDecoder(kDecoder1, kAlwaysSucceed);
+  this->AddMockDecoder(kDecoder2, kAlwaysSucceed);
+  this->AddMockPlatformDecoder(kDecoder3, kAlwaysSucceed);
+  this->AddMockDecoder(kDecoder4, kAlwaysSucceed);
+
+  this->UseEncryptedDecoderConfig();
+  this->CreateDecoderSelector();
+
+  EXPECT_CALL(*this, OnDecoderSelected(kDecoder2, IsNull()));
+  this->SelectDecoder();
+  EXPECT_CALL(*this, OnDecoderSelected(kDecoder4, IsNull()));
+  this->SelectDecoder();
+  EXPECT_CALL(*this, OnDecoderSelected(kDecoder1, IsNull()));
+  this->SelectDecoder();
+  EXPECT_CALL(*this, OnDecoderSelected(kDecoder3, IsNull()));
+  this->SelectDecoder();
+
+  EXPECT_CALL(*this, OnDecoderSelected(kNoDecoder, IsNull()));
+  this->SelectDecoder();
+}
+
+// Tests that platform and non-platform decoders remain in the order they are
+// given for a priority callback returning 'kNop'.
+TYPED_TEST(DecoderSelectorTest,
+           EncryptedStream_NopPriorityCallbackRetainsGivenOrder) {
+  this->AddMockPlatformDecoder(kDecoder1, kAlwaysSucceed);
+  this->AddMockDecoder(kDecoder2, kAlwaysSucceed);
+  this->AddMockPlatformDecoder(kDecoder3, kAlwaysSucceed);
+  this->AddMockDecoder(kDecoder4, kAlwaysSucceed);
+
+  this->UseEncryptedDecoderConfig();
+  this->CreateDecoderSelector();
+  this->decoder_selector_->OverrideDecoderPriorityCBForTesting(
+      base::BindRepeating(TypeParam::UnspecifiedDecoderPriorityCB));
+
+  EXPECT_CALL(*this, OnDecoderSelected(kDecoder1, IsNull()));
+  this->SelectDecoder();
+  EXPECT_CALL(*this, OnDecoderSelected(kDecoder2, IsNull()));
+  this->SelectDecoder();
+  EXPECT_CALL(*this, OnDecoderSelected(kDecoder3, IsNull()));
+  this->SelectDecoder();
+  EXPECT_CALL(*this, OnDecoderSelected(kDecoder4, IsNull()));
+  this->SelectDecoder();
+
+  EXPECT_CALL(*this, OnDecoderSelected(kNoDecoder, IsNull()));
+  this->SelectDecoder();
+}
+
 TYPED_TEST(DecoderSelectorTest, EncryptedStream_NoDecryptor_OneClearDecoder) {
   this->AddMockDecoder(kDecoder1, kClearOnly);
   this->CreateCdmContext(kNoDecryptor);
diff --git a/media/filters/decoder_stream.cc b/media/filters/decoder_stream.cc
index e128269..0c3e5cf 100644
--- a/media/filters/decoder_stream.cc
+++ b/media/filters/decoder_stream.cc
@@ -278,7 +278,11 @@
 
 template <DemuxerStream::Type StreamType>
 int DecoderStream<StreamType>::GetMaxDecodeRequests() const {
-  return decoder_->GetMaxDecodeRequests();
+  // The decoder is owned by |decoder_selector_| during reinitialization, so
+  // during that time we disallow decode requests.
+  return state_ != STATE_REINITIALIZING_DECODER
+             ? decoder_->GetMaxDecodeRequests()
+             : 0;
 }
 
 template <>
@@ -287,6 +291,20 @@
 }
 
 template <DemuxerStream::Type StreamType>
+int DecoderStream<StreamType>::GetMaxReadyOutputs() const {
+  // The decoder is owned by |decoder_selector_| during reinitialization, so
+  // during that time we assume the minimum viable number of max ready outputs.
+  return state_ != STATE_REINITIALIZING_DECODER
+             ? decoder_->GetMaxDecodeRequests()
+             : 1;
+}
+
+template <>
+int DecoderStream<DemuxerStream::AUDIO>::GetMaxReadyOutputs() const {
+  return 1;
+}
+
+template <DemuxerStream::Type StreamType>
 bool DecoderStream<StreamType>::CanDecodeMore() const {
   DCHECK(task_runner_->BelongsToCurrentThread());
 
@@ -343,8 +361,6 @@
     DCHECK(init_cb_);
     DCHECK(!read_cb_);
     DCHECK(!reset_cb_);
-  } else if (state_ == STATE_REINITIALIZING_DECODER) {
-    DCHECK(decoder_);
   }
 
   auto* original_stream = stream_;
@@ -825,39 +841,8 @@
   DCHECK_EQ(pending_decode_requests_, 0);
 
   state_ = STATE_REINITIALIZING_DECODER;
-
-  // TODO(sandersd): Detect whether a new decoder is required before
-  // attempting reinitialization.
-  traits_->InitializeDecoder(
-      decoder_.get(), traits_->GetDecoderConfig(stream_),
-      stream_->liveness() == DemuxerStream::LIVENESS_LIVE, cdm_context_,
-      base::BindOnce(&DecoderStream<StreamType>::OnDecoderReinitialized,
-                     weak_factory_.GetWeakPtr()),
-      base::BindRepeating(&DecoderStream<StreamType>::OnDecodeOutputReady,
-                          fallback_weak_factory_.GetWeakPtr()),
-      waiting_cb_);
-}
-
-template <DemuxerStream::Type StreamType>
-void DecoderStream<StreamType>::OnDecoderReinitialized(Status status) {
-  FUNCTION_DVLOG(2) << ": success = " << status.is_ok();
-  DCHECK(task_runner_->BelongsToCurrentThread());
-  DCHECK_EQ(state_, STATE_REINITIALIZING_DECODER);
-
-  // ReinitializeDecoder() can be called in two cases:
-  // 1, Flushing decoder finished (see OnDecodeOutputReady()).
-  // 2, Reset() was called during flushing decoder (see OnDecoderReset()).
-  // Also, Reset() can be called during pending ReinitializeDecoder().
-  // This function needs to handle them all!
-
-  if (!status.is_ok()) {
-    // Reinitialization failed. Try to fall back to one of the remaining
-    // decoders. This will consume at least one decoder so doing it more than
-    // once is safe.
-    SelectDecoder();
-  } else {
-    CompleteDecoderReinitialization(true);
-  }
+  decoder_selector_.PrependDecoder(std::move(decoder_));
+  SelectDecoder();
 }
 
 template <DemuxerStream::Type StreamType>
@@ -962,7 +947,7 @@
     return;
 
   // If there's too many ready outputs, we're done.
-  if (ready_outputs_.size() >= static_cast<size_t>(GetMaxDecodeRequests()))
+  if (ready_outputs_.size() >= static_cast<size_t>(GetMaxReadyOutputs()))
     return;
 
   // Retain a copy to avoid dangling reference in OnPreparedOutputReady().
diff --git a/media/filters/decoder_stream.h b/media/filters/decoder_stream.h
index a29bbf08..1e4d3331c 100644
--- a/media/filters/decoder_stream.h
+++ b/media/filters/decoder_stream.h
@@ -14,6 +14,7 @@
 #include "base/containers/circular_deque.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
+#include "base/util/type_safety/pass_key.h"
 #include "media/base/audio_decoder.h"
 #include "media/base/audio_timestamp_helper.h"
 #include "media/base/demuxer_stream.h"
@@ -68,9 +69,6 @@
                 MediaLog* media_log);
   virtual ~DecoderStream();
 
-  // Returns the string representation of the StreamType for logging purpose.
-  std::string GetStreamTypeString();
-
   // Initializes the DecoderStream and returns the initialization result
   // through |init_cb|. Note that |init_cb| is always called asynchronously.
   // |cdm_context| can be used to handle encrypted stream. Can be null if the
@@ -100,12 +98,6 @@
   // an Output.
   bool CanReadWithoutStalling() const;
 
-  // Returns maximum concurrent decode requests for the current |decoder_|.
-  int GetMaxDecodeRequests() const;
-
-  // Returns true if one more decode request can be submitted to the decoder.
-  bool CanDecodeMore() const;
-
   base::TimeDelta AverageDuration() const;
 
   // Indicates that outputs need preparation (e.g., copying into GPU buffers)
@@ -154,6 +146,11 @@
     return fallback_buffers_.size();
   }
 
+  DecoderSelector<StreamType>& GetDecoderSelectorForTesting(
+      util::PassKey<class VideoDecoderStreamTest>) {
+    return decoder_selector_;
+  }
+
  private:
   enum State {
     STATE_UNINITIALIZED,
@@ -165,6 +162,18 @@
     STATE_ERROR,
   };
 
+  // Returns the string representation of the StreamType for logging purpose.
+  std::string GetStreamTypeString();
+
+  // Returns maximum concurrent decode requests for the current |decoder_|.
+  int GetMaxDecodeRequests() const;
+
+  // Returns the maximum number of outputs we should keep ready at any one time.
+  int GetMaxReadyOutputs() const;
+
+  // Returns true if one more decode request can be submitted to the decoder.
+  bool CanDecodeMore() const;
+
   void SelectDecoder();
 
   // Called when |decoder_selector| selected the |selected_decoder|.
@@ -206,9 +215,6 @@
 
   void ReinitializeDecoder();
 
-  // Callback for Decoder reinitialization.
-  void OnDecoderReinitialized(Status status);
-
   void CompleteDecoderReinitialization(bool success);
 
   void ResetDecoder();
diff --git a/media/filters/decrypting_audio_decoder.cc b/media/filters/decrypting_audio_decoder.cc
index b0ff022..1945c25c 100644
--- a/media/filters/decrypting_audio_decoder.cc
+++ b/media/filters/decrypting_audio_decoder.cc
@@ -39,6 +39,10 @@
     MediaLog* media_log)
     : task_runner_(task_runner), media_log_(media_log) {}
 
+bool DecryptingAudioDecoder::SupportsDecryption() const {
+  return true;
+}
+
 std::string DecryptingAudioDecoder::GetDisplayName() const {
   return "DecryptingAudioDecoder";
 }
diff --git a/media/filters/decrypting_audio_decoder.h b/media/filters/decrypting_audio_decoder.h
index 0adf658..55d2fc4 100644
--- a/media/filters/decrypting_audio_decoder.h
+++ b/media/filters/decrypting_audio_decoder.h
@@ -38,8 +38,11 @@
       MediaLog* media_log);
   ~DecryptingAudioDecoder() override;
 
-  // AudioDecoder implementation.
+  // Decoder implementation
+  bool SupportsDecryption() const override;
   std::string GetDisplayName() const override;
+
+  // AudioDecoder implementation.
   void Initialize(const AudioDecoderConfig& config,
                   CdmContext* cdm_context,
                   InitCB init_cb,
diff --git a/media/filters/decrypting_video_decoder.cc b/media/filters/decrypting_video_decoder.cc
index 3ce8f1b..929a567 100644
--- a/media/filters/decrypting_video_decoder.cc
+++ b/media/filters/decrypting_video_decoder.cc
@@ -90,6 +90,10 @@
                    &DecryptingVideoDecoder::FinishInitialization, weak_this_)));
 }
 
+bool DecryptingVideoDecoder::SupportsDecryption() const {
+  return true;
+}
+
 void DecryptingVideoDecoder::Decode(scoped_refptr<DecoderBuffer> buffer,
                                     DecodeCB decode_cb) {
   DVLOG(3) << "Decode()";
diff --git a/media/filters/decrypting_video_decoder.h b/media/filters/decrypting_video_decoder.h
index 71a0ac0..f5e4eeb 100644
--- a/media/filters/decrypting_video_decoder.h
+++ b/media/filters/decrypting_video_decoder.h
@@ -35,6 +35,8 @@
       MediaLog* media_log);
   ~DecryptingVideoDecoder() override;
 
+  bool SupportsDecryption() const override;
+
   // VideoDecoder implementation.
   std::string GetDisplayName() const override;
   void Initialize(const VideoDecoderConfig& config,
diff --git a/media/filters/fake_video_decoder.cc b/media/filters/fake_video_decoder.cc
index fa47c5f..5ef6326 100644
--- a/media/filters/fake_video_decoder.cc
+++ b/media/filters/fake_video_decoder.cc
@@ -47,10 +47,22 @@
   supports_encrypted_config_ = true;
 }
 
+void FakeVideoDecoder::SetIsPlatformDecoder(bool value) {
+  is_platform_decoder_ = value;
+}
+
 base::WeakPtr<FakeVideoDecoder> FakeVideoDecoder::GetWeakPtr() {
   return weak_factory_.GetWeakPtr();
 }
 
+bool FakeVideoDecoder::SupportsDecryption() const {
+  return supports_encrypted_config_;
+}
+
+bool FakeVideoDecoder::IsPlatformDecoder() const {
+  return is_platform_decoder_;
+}
+
 std::string FakeVideoDecoder::GetDisplayName() const {
   return decoder_name_;
 }
diff --git a/media/filters/fake_video_decoder.h b/media/filters/fake_video_decoder.h
index 868f5b96..2b134049 100644
--- a/media/filters/fake_video_decoder.h
+++ b/media/filters/fake_video_decoder.h
@@ -41,8 +41,16 @@
   // Enables encrypted config supported. Must be called before Initialize().
   void EnableEncryptedConfigSupport();
 
-  // VideoDecoder implementation.
+  // Sets whether this decoder is a platform decoder. Must be called before
+  // Initialize().
+  void SetIsPlatformDecoder(bool value);
+
+  // Decoder implementation.
+  bool SupportsDecryption() const override;
+  bool IsPlatformDecoder() const override;
   std::string GetDisplayName() const override;
+
+  // VideoDecoder implementation
   void Initialize(const VideoDecoderConfig& config,
                   bool low_delay,
                   CdmContext* cdm_context,
@@ -105,6 +113,7 @@
   const int max_parallel_decoding_requests_;
   BytesDecodedCB bytes_decoded_cb_;
 
+  bool is_platform_decoder_ = false;
   bool supports_encrypted_config_ = false;
 
   State state_;
diff --git a/media/filters/fuchsia/fuchsia_video_decoder.cc b/media/filters/fuchsia/fuchsia_video_decoder.cc
index befca61..76df009 100644
--- a/media/filters/fuchsia/fuchsia_video_decoder.cc
+++ b/media/filters/fuchsia/fuchsia_video_decoder.cc
@@ -173,9 +173,12 @@
                       bool enable_sw_decoding);
   ~FuchsiaVideoDecoder() override;
 
-  // VideoDecoder implementation.
-  std::string GetDisplayName() const override;
+  // Decoder implementation.
   bool IsPlatformDecoder() const override;
+  bool SupportsDecryption() const override;
+  std::string GetDisplayName() const override;
+
+  // VideoDecoder implementation.
   void Initialize(const VideoDecoderConfig& config,
                   bool low_delay,
                   CdmContext* cdm_context,
@@ -327,14 +330,18 @@
   ReleaseOutputBuffers();
 }
 
-std::string FuchsiaVideoDecoder::GetDisplayName() const {
-  return "FuchsiaVideoDecoder";
-}
-
 bool FuchsiaVideoDecoder::IsPlatformDecoder() const {
   return true;
 }
 
+bool FuchsiaVideoDecoder::SupportsDecryption() const {
+  return true;
+}
+
+std::string FuchsiaVideoDecoder::GetDisplayName() const {
+  return "FuchsiaVideoDecoder";
+}
+
 void FuchsiaVideoDecoder::Initialize(const VideoDecoderConfig& config,
                                      bool low_delay,
                                      CdmContext* cdm_context,
diff --git a/media/filters/media_file_checker.cc b/media/filters/media_file_checker.cc
index 643a19b..c65d808 100644
--- a/media/filters/media_file_checker.cc
+++ b/media/filters/media_file_checker.cc
@@ -21,9 +21,11 @@
 
 namespace media {
 
-static const int64_t kMaxCheckTimeInSeconds = 5;
+namespace {
 
-static void OnMediaFileCheckerError(bool* called) {
+constexpr int64_t kMaxCheckTimeInSeconds = 5;
+
+void OnMediaFileCheckerError(bool* called) {
   *called = false;
 }
 
@@ -32,6 +34,8 @@
   std::unique_ptr<FFmpegDecodingLoop> loop;
 };
 
+}  // namespace
+
 MediaFileChecker::MediaFileChecker(base::File file) : file_(std::move(file)) {}
 
 MediaFileChecker::~MediaFileChecker() = default;
diff --git a/media/filters/video_decoder_stream_unittest.cc b/media/filters/video_decoder_stream_unittest.cc
index ffd8951f..01e5be7 100644
--- a/media/filters/video_decoder_stream_unittest.cc
+++ b/media/filters/video_decoder_stream_unittest.cc
@@ -42,6 +42,7 @@
 
 namespace media {
 
+namespace {
 const int kNumConfigs = 4;
 const int kNumBuffersInOneConfig = 5;
 constexpr base::TimeDelta kPrepareDelay = base::TimeDelta::FromMilliseconds(5);
@@ -50,6 +51,15 @@
   return std::string("VideoDecoder") + base::NumberToString(i);
 }
 
+DecoderPriority MockDecoderPriority(const VideoDecoderConfig& config) {
+  return config.visible_rect().height() >=
+                 TestVideoConfig::LargeCodedSize().height()
+             ? DecoderPriority::kPreferPlatformDecoders
+             : DecoderPriority::kPreferSoftwareDecoders;
+}
+
+}  // namespace
+
 struct VideoDecoderStreamTestParams {
   VideoDecoderStreamTestParams(bool is_encrypted,
                                bool has_decryptor,
@@ -74,10 +84,7 @@
       public testing::WithParamInterface<VideoDecoderStreamTestParams> {
  public:
   VideoDecoderStreamTest()
-      : demuxer_stream_(new FakeDemuxerStream(kNumConfigs,
-                                              kNumBuffersInOneConfig,
-                                              GetParam().is_encrypted)),
-        is_initialized_(false),
+      : is_initialized_(false),
         num_decoded_frames_(0),
         pending_initialize_(false),
         pending_read_(false),
@@ -94,6 +101,10 @@
     video_decoder_stream_->set_decoder_change_observer_for_testing(
         base::BindRepeating(&VideoDecoderStreamTest::OnDecoderChanged,
                             base::Unretained(this)));
+    video_decoder_stream_
+        ->GetDecoderSelectorForTesting(util::PassKey<VideoDecoderStreamTest>())
+        .OverrideDecoderPriorityCBForTesting(
+            base::BindRepeating(MockDecoderPriority));
     if (GetParam().has_prepare) {
       video_decoder_stream_->SetPrepareCB(base::BindRepeating(
           &VideoDecoderStreamTest::PrepareFrame, base::Unretained(this)));
@@ -139,6 +150,13 @@
     DCHECK(!pending_stop_);
   }
 
+  void CreateDemuxerStream(gfx::Size start_size, gfx::Vector2dF size_delta) {
+    DCHECK(!demuxer_stream_);
+    demuxer_stream_ = std::make_unique<FakeDemuxerStream>(
+        kNumConfigs, kNumBuffersInOneConfig, GetParam().is_encrypted,
+        start_size, size_delta);
+  }
+
   void PrepareFrame(scoped_refptr<VideoFrame> frame,
                     VideoDecoderStream::OutputReadyCB output_ready_cb) {
     // Simulate some delay in return of the output.
@@ -198,15 +216,18 @@
       decoders.push_back(std::move(decoder));
     }
 
-    for (const auto& i : decoder_indices_to_fail_init_)
+    for (const auto i : decoder_indices_to_fail_init_)
       decoders_[i]->SimulateFailureToInit();
 
-    for (const auto& i : decoder_indices_to_hold_init_)
+    for (const auto i : decoder_indices_to_hold_init_)
       decoders_[i]->HoldNextInit();
 
-    for (const auto& i : decoder_indices_to_hold_decode_)
+    for (const auto i : decoder_indices_to_hold_decode_)
       decoders_[i]->HoldDecode();
 
+    for (const auto i : platform_decoder_indices_)
+      decoders_[i]->SetIsPlatformDecoder(true);
+
     return decoders;
   }
 
@@ -214,13 +235,14 @@
     decoder_indices_to_fail_init_.clear();
     decoder_indices_to_hold_init_.clear();
     decoder_indices_to_hold_decode_.clear();
+    platform_decoder_indices_.clear();
   }
 
   // On next decoder selection, fail initialization on decoders specified by
   // |decoder_indices|.
-  void FailDecoderInitOnSelection(const std::vector<int>& decoder_indices) {
-    decoder_indices_to_fail_init_ = decoder_indices;
-    for (int i : decoder_indices) {
+  void FailDecoderInitOnSelection(std::vector<int> decoder_indices) {
+    decoder_indices_to_fail_init_ = std::move(decoder_indices);
+    for (int i : decoder_indices_to_fail_init_) {
       if (!decoders_.empty() && decoders_[i] && decoders_[i].get() != decoder_)
         decoders_[i]->SimulateFailureToInit();
     }
@@ -228,9 +250,9 @@
 
   // On next decoder selection, hold initialization on decoders specified by
   // |decoder_indices|.
-  void HoldDecoderInitOnSelection(const std::vector<int>& decoder_indices) {
-    decoder_indices_to_hold_init_ = decoder_indices;
-    for (int i : decoder_indices) {
+  void HoldDecoderInitOnSelection(std::vector<int> decoder_indices) {
+    decoder_indices_to_hold_init_ = std::move(decoder_indices);
+    for (int i : decoder_indices_to_hold_init_) {
       if (!decoders_.empty() && decoders_[i] && decoders_[i].get() != decoder_)
         decoders_[i]->HoldNextInit();
     }
@@ -239,14 +261,22 @@
   // After next decoder selection, hold decode on decoders specified by
   // |decoder_indices|. This is needed because after decoder selection decode
   // may be resumed immediately and it'll be too late to hold decode then.
-  void HoldDecodeAfterSelection(const std::vector<int>& decoder_indices) {
-    decoder_indices_to_hold_decode_ = decoder_indices;
-    for (int i : decoder_indices) {
+  void HoldDecodeAfterSelection(std::vector<int> decoder_indices) {
+    decoder_indices_to_hold_decode_ = std::move(decoder_indices);
+    for (int i : decoder_indices_to_hold_decode_) {
       if (!decoders_.empty() && decoders_[i] && decoders_[i].get() != decoder_)
         decoders_[i]->HoldDecode();
     }
   }
 
+  void EnablePlatformDecoders(std::vector<int> decoder_indices) {
+    platform_decoder_indices_ = std::move(decoder_indices);
+    for (int i : platform_decoder_indices_) {
+      if (!decoders_.empty() && decoders_[i] && decoders_[i].get() != decoder_)
+        decoders_[i]->SetIsPlatformDecoder(true);
+    }
+  }
+
   // Updates the |decoder_| currently being used by VideoDecoderStream.
   void OnDecoderChanged(VideoDecoder* decoder) {
     if (!decoder) {
@@ -281,6 +311,11 @@
   }
 
   void Initialize() {
+    if (!demuxer_stream_) {
+      demuxer_stream_ = std::make_unique<FakeDemuxerStream>(
+          kNumConfigs, kNumBuffersInOneConfig, GetParam().is_encrypted);
+    }
+
     pending_initialize_ = true;
     video_decoder_stream_->Initialize(
         demuxer_stream_.get(),
@@ -491,9 +526,10 @@
   std::vector<int> decoder_indices_to_fail_init_;
   std::vector<int> decoder_indices_to_hold_init_;
   std::vector<int> decoder_indices_to_hold_decode_;
+  std::vector<int> platform_decoder_indices_;
 
   // The current decoder used by |video_decoder_stream_|.
-  FakeVideoDecoder* decoder_;
+  FakeVideoDecoder* decoder_ = nullptr;
 
   bool is_initialized_;
   int num_decoded_frames_;
@@ -580,6 +616,54 @@
   Read();
 }
 
+// Tests that the decoder stream will switch from a software decoder to a
+// hardware decoder if the config size increases
+TEST_P(VideoDecoderStreamTest, ConfigChangeSwToHw) {
+  EnablePlatformDecoders({1});
+
+  // Create a demuxer stream with a config that increases in size
+  auto const size_delta =
+      TestVideoConfig::LargeCodedSize() - TestVideoConfig::NormalCodedSize();
+  auto const width_delta = size_delta.width() / (kNumConfigs - 1);
+  auto const height_delta = size_delta.height() / (kNumConfigs - 1);
+  CreateDemuxerStream(TestVideoConfig::NormalCodedSize(),
+                      gfx::Vector2dF(width_delta, height_delta));
+  Initialize();
+
+  // Initially we should be using a software decoder
+  EXPECT_TRUE(decoder_);
+  EXPECT_FALSE(decoder_->IsPlatformDecoder());
+
+  ReadAllFrames();
+
+  // We should end up on a hardware decoder
+  EXPECT_TRUE(decoder_->IsPlatformDecoder());
+}
+
+// Tests that the decoder stream will switch from a hardware decoder to a
+// software decoder if the config size decreases
+TEST_P(VideoDecoderStreamTest, ConfigChangeHwToSw) {
+  EnablePlatformDecoders({1});
+
+  // Create a demuxer stream with a config that progressively decreases in size
+  auto const size_delta =
+      TestVideoConfig::LargeCodedSize() - TestVideoConfig::NormalCodedSize();
+  auto const width_delta = size_delta.width() / kNumConfigs;
+  auto const height_delta = size_delta.height() / kNumConfigs;
+  CreateDemuxerStream(TestVideoConfig::LargeCodedSize(),
+                      gfx::Vector2dF(-width_delta, -height_delta));
+  Initialize();
+
+  // We should initially be using a hardware decoder
+  EXPECT_TRUE(decoder_);
+  EXPECT_TRUE(decoder_->IsPlatformDecoder());
+
+  ReadAllFrames();
+
+  // We should end up on a software decoder
+  EXPECT_FALSE(decoder_->IsPlatformDecoder());
+}
+
 TEST_P(VideoDecoderStreamTest, Read_ProperMetadata) {
   // For testing simplicity, omit parallel decode tests with a delay in frames.
   if (GetParam().parallel_decoding > 1 && GetParam().decoding_delay > 0)
diff --git a/media/mojo/clients/mojo_audio_decoder.cc b/media/mojo/clients/mojo_audio_decoder.cc
index a638130..626b0ef 100644
--- a/media/mojo/clients/mojo_audio_decoder.cc
+++ b/media/mojo/clients/mojo_audio_decoder.cc
@@ -33,14 +33,18 @@
   DVLOG(1) << __func__;
 }
 
-std::string MojoAudioDecoder::GetDisplayName() const {
-  return "MojoAudioDecoder";
-}
-
 bool MojoAudioDecoder::IsPlatformDecoder() const {
   return true;
 }
 
+bool MojoAudioDecoder::SupportsDecryption() const {
+  return true;
+}
+
+std::string MojoAudioDecoder::GetDisplayName() const {
+  return "MojoAudioDecoder";
+}
+
 void MojoAudioDecoder::FailInit(InitCB init_cb, Status err) {
   task_runner_->PostTask(FROM_HERE,
                          base::BindOnce(std::move(init_cb), std::move(err)));
diff --git a/media/mojo/clients/mojo_audio_decoder.h b/media/mojo/clients/mojo_audio_decoder.h
index 907139b..a7db243 100644
--- a/media/mojo/clients/mojo_audio_decoder.h
+++ b/media/mojo/clients/mojo_audio_decoder.h
@@ -31,9 +31,12 @@
                    mojo::PendingRemote<mojom::AudioDecoder> remote_decoder);
   ~MojoAudioDecoder() final;
 
-  // AudioDecoder implementation.
-  std::string GetDisplayName() const final;
+  // Decoder implementation
   bool IsPlatformDecoder() const final;
+  bool SupportsDecryption() const final;
+  std::string GetDisplayName() const final;
+
+  // AudioDecoder implementation.
   void Initialize(const AudioDecoderConfig& config,
                   CdmContext* cdm_context,
                   InitCB init_cb,
diff --git a/media/mojo/clients/mojo_video_decoder.cc b/media/mojo/clients/mojo_video_decoder.cc
index cc5d5aa..130488c 100644
--- a/media/mojo/clients/mojo_video_decoder.cc
+++ b/media/mojo/clients/mojo_video_decoder.cc
@@ -133,6 +133,10 @@
   return true;
 }
 
+bool MojoVideoDecoder::SupportsDecryption() const {
+  return true;
+}
+
 std::string MojoVideoDecoder::GetDisplayName() const {
   return "MojoVideoDecoder";
 }
diff --git a/media/mojo/clients/mojo_video_decoder.h b/media/mojo/clients/mojo_video_decoder.h
index 449b60a..87f6989 100644
--- a/media/mojo/clients/mojo_video_decoder.h
+++ b/media/mojo/clients/mojo_video_decoder.h
@@ -56,9 +56,12 @@
       const gfx::ColorSpace& target_color_space);
   ~MojoVideoDecoder() final;
 
-  // VideoDecoder implementation.
-  std::string GetDisplayName() const final;
+  // Decoder implementation
   bool IsPlatformDecoder() const final;
+  bool SupportsDecryption() const final;
+  std::string GetDisplayName() const final;
+
+  // VideoDecoder implementation.
   void Initialize(const VideoDecoderConfig& config,
                   bool low_delay,
                   CdmContext* cdm_context,
diff --git a/third_party/blink/renderer/modules/webcodecs/decoder_selector_test.cc b/third_party/blink/renderer/modules/webcodecs/decoder_selector_test.cc
index 2ff7dd6..d79fdba 100644
--- a/third_party/blink/renderer/modules/webcodecs/decoder_selector_test.cc
+++ b/third_party/blink/renderer/modules/webcodecs/decoder_selector_test.cc
@@ -154,7 +154,9 @@
 
     for (const auto& info : mock_decoders_to_create_) {
       std::unique_ptr<StrictMock<MockDecoder>> decoder =
-          std::make_unique<StrictMock<MockDecoder>>(info.first);
+          std::make_unique<StrictMock<MockDecoder>>(
+              /*is_platform_decoder=*/false, /*supports_decryption=*/true,
+              info.first);
       TypeParam::ExpectInitialize(decoder.get(), info.second,
                                   last_set_decoder_config_);
       decoders.push_back(std::move(decoder));
diff --git a/third_party/blink/renderer/modules/webcodecs/video_decoder_broker_test.cc b/third_party/blink/renderer/modules/webcodecs/video_decoder_broker_test.cc
index 9504632..8e3b2ed 100644
--- a/third_party/blink/renderer/modules/webcodecs/video_decoder_broker_test.cc
+++ b/third_party/blink/renderer/modules/webcodecs/video_decoder_broker_test.cc
@@ -325,7 +325,8 @@
   ConstructDecoder(*execution_context);
   EXPECT_EQ(GetDisplayName(), "EmptyWebCodecsVideoDecoder");
 
-  media::VideoDecoderConfig config = media::TestVideoConfig::Normal();
+  // Use an extra-large video to ensure we don't get a software decoder
+  media::VideoDecoderConfig config = media::TestVideoConfig::ExtraLarge();
   InitializeDecoder(config);
   EXPECT_EQ(GetDisplayName(), "MojoVideoDecoder");
 
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index e9db725..d01cac1d 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -77435,6 +77435,18 @@
   </summary>
 </histogram>
 
+<histogram base="true" name="Media.ConfigChangeDecoderSelectionTime" units="ms"
+    expires_after="2021-06-25">
+  <owner>[email protected]</owner>
+  <owner>[email protected]</owner>
+  <summary>
+    Duration of decoder selection during config changes, measured from first
+    call to 'DecoderSelector::SelectDecoder' to
+    'DecoderSelector::FinalizeSelection'. The HW/SW suffix indicates the type of
+    the decoder that was ultimately selected.
+  </summary>
+</histogram>
+
 <histogram name="Media.Controls.CTR" enum="MediaControlsCTREvent"
     expires_after="2020-04-05">
   <owner>[email protected]</owner>
@@ -78777,6 +78789,18 @@
   </summary>
 </histogram>
 
+<histogram base="true" name="Media.InitialDecoderSelectionTime" units="ms"
+    expires_after="2021-06-25">
+  <owner>[email protected]</owner>
+  <owner>[email protected]</owner>
+  <summary>
+    Duration of initial decoder selection. Measured from first call to
+    'DecoderSelector::SelectDecoder' to 'DecoderSelector::FinalizeSelection'.
+    The HW/SW suffix indicates the type of the decoder that was ultimately
+    selected.
+  </summary>
+</histogram>
+
 <histogram name="Media.Initialize.Windows" enum="WinGetLastError"
     expires_after="2015-06-08">
   <obsolete>
@@ -203173,6 +203197,8 @@
   <suffix name="Audio.SW" label="Software audio decoder"/>
   <suffix name="Video.HW" label="Platform video decoder"/>
   <suffix name="Video.SW" label="Software video decoder"/>
+  <affected-histogram name="Media.ConfigChangeDecoderSelectionTime"/>
+  <affected-histogram name="Media.InitialDecoderSelectionTime"/>
   <affected-histogram name="Media.MSE.CodecChangeTime"/>
 </histogram_suffixes>