Allow batch video decoding using standard pipeline 53/320653/2
authorJakub Gajownik <j.gajownik2@samsung.com>
Mon, 18 Nov 2024 11:15:04 +0000 (12:15 +0100)
committerj.gajownik2 <j.gajownik2@samsung.com>
Wed, 20 Nov 2024 09:23:50 +0000 (10:23 +0100)
Ported patches from upstream Chromium:
* Support video decode batching for non-MF renderer path.
  https://chromium-review.googlesource.com/c/chromium/src/+/4544387
* Allow batch decoding only when output picture buffers are pre-allocated.
  https://chromium-review.googlesource.com/c/chromium/src/+/5862888
* media: Re-enable Batch decoding in the renderer
  https://chromium-review.googlesource.com/c/chromium/src/+/5892966

Bug: https://jira-eu.sec.samsung.net/browse/VDGAME-624
Change-Id: I2da025a4b5c8dff435de17ab79eb33322e0e0179
Signed-off-by: Jakub Gajownik <j.gajownik2@samsung.com>
media/base/fake_demuxer_stream.cc
media/base/fake_demuxer_stream.h
media/base/media_switches.cc
media/base/media_switches.h
media/filters/decoder_stream.cc
media/filters/decoder_stream.h
media/filters/fake_video_decoder.cc
media/filters/fake_video_decoder.h
media/filters/video_decoder_stream_unittest.cc

index 20fa5b274674e7445b4409f600bc55ebda161dd7..a14cf78172becd707fe339c87d979060f56aa931 100644 (file)
@@ -81,7 +81,7 @@ void FakeDemuxerStream::Initialize() {
 }
 
 // Only return one buffer at a time so we ignore the count.
-void FakeDemuxerStream::Read(uint32_t /*count*/, ReadCB read_cb) {
+void FakeDemuxerStream::Read(uint32_t count, ReadCB read_cb) {
   DCHECK(task_runner_->BelongsToCurrentThread());
   DCHECK(!read_cb_);
 
@@ -91,7 +91,7 @@ void FakeDemuxerStream::Read(uint32_t /*count*/, ReadCB read_cb) {
     return;
 
   DCHECK(read_to_hold_ == -1 || read_to_hold_ > next_read_num_);
-  DoRead();
+  DoRead(count);
 }
 
 AudioDecoderConfig FakeDemuxerStream::audio_decoder_config() {
@@ -180,9 +180,10 @@ void FakeDemuxerStream::UpdateVideoDecoderConfig() {
   next_size_.set_height(next_size_.height() + coded_size_delta_.y());
 }
 
-void FakeDemuxerStream::DoRead() {
+void FakeDemuxerStream::DoRead(int read_count) {
   DCHECK(task_runner_->BelongsToCurrentThread());
   DCHECK(read_cb_);
+  DCHECK_GE(read_count, 1);
 
   next_read_num_++;
 
@@ -200,25 +201,31 @@ void FakeDemuxerStream::DoRead() {
     return;
   }
 
-  scoped_refptr<DecoderBuffer> buffer = CreateFakeVideoBufferForTest(
-      video_decoder_config_, current_timestamp_, duration_);
-
-  // TODO(xhwang): Output out-of-order buffers if needed.
-  if (is_encrypted_) {
-    buffer->set_decrypt_config(DecryptConfig::CreateCencConfig(
-        std::string(kKeyId, kKeyId + std::size(kKeyId)),
-        std::string(kIv, kIv + std::size(kIv)), std::vector<SubsampleEntry>()));
-  }
-  buffer->set_timestamp(current_timestamp_);
-  buffer->set_duration(duration_);
-  current_timestamp_ += duration_;
+  DemuxerStream::DecoderBufferVector output_buffers;
+  for (int i = 0; i < read_count; ++i) {
+    scoped_refptr<DecoderBuffer> buffer = CreateFakeVideoBufferForTest(
+        video_decoder_config_, current_timestamp_, duration_);
+
+    // TODO(xhwang): Output out-of-order buffers if needed.
+    if (is_encrypted_) {
+      buffer->set_decrypt_config(DecryptConfig::CreateCencConfig(
+          std::string(kKeyId, kKeyId + std::size(kKeyId)),
+          std::string(kIv, kIv + std::size(kIv)),
+          std::vector<SubsampleEntry>()));
+    }
+    buffer->set_timestamp(current_timestamp_);
+    buffer->set_duration(duration_);
+    current_timestamp_ += duration_;
 
-  num_buffers_left_in_current_config_--;
-  if (num_buffers_left_in_current_config_ == 0)
-    num_configs_left_--;
+    num_buffers_left_in_current_config_--;
+    if (num_buffers_left_in_current_config_ == 0) {
+      num_configs_left_--;
+    }
 
-  num_buffers_returned_++;
-  std::move(read_cb_).Run(kOk, {std::move(buffer)});
+    num_buffers_returned_++;
+    output_buffers.emplace_back(std::move(buffer));
+  }
+  std::move(read_cb_).Run(kOk, std::move(output_buffers));
 }
 
 FakeMediaResource::FakeMediaResource(int num_video_configs,
index 5cd44ab0f9f60e76a2243cdf1372dbf0386c4eac..83a844f3279c850ac3db684a077e9b4c83a824ca 100644 (file)
@@ -90,7 +90,7 @@ class FakeDemuxerStream : public DemuxerStream {
 
  private:
   void UpdateVideoDecoderConfig();
-  void DoRead();
+  void DoRead(int read_count = 1);
 
   scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
 
index 5b05e20b8df1d6238baa5c95196fd10d82cc598e..d0eec076244b34483c46649184bb7f4454cb1043 100644 (file)
@@ -1226,6 +1226,12 @@ BASE_FEATURE(kBresenhamCadence,
              "BresenhamCadence",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+// Controls whether to pre-dispatch more decode tasks when pending decodes is
+// smaller than maximum supported decodes as advertiszed by decoder.
+BASE_FEATURE(kVideoDecodeBatching,
+             "VideoDecodeBatching",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+
 bool IsChromeWideEchoCancellationEnabled() {
 #if BUILDFLAG(CHROME_WIDE_ECHO_CANCELLATION)
   return base::FeatureList::IsEnabled(kChromeWideEchoCancellation);
index b312a6732a200cacac11922d8d0464e48a12bc4e..0fa2a384a7b3709717b248cfa4e447bf9fd45119 100644 (file)
@@ -339,6 +339,8 @@ MEDIA_EXPORT BASE_DECLARE_FEATURE(kUseOutOfProcessVideoDecoding);
 MEDIA_EXPORT BASE_DECLARE_FEATURE(kUseOutOfProcessVideoEncoding);
 #endif  // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
 
+MEDIA_EXPORT BASE_DECLARE_FEATURE(kVideoDecodeBatching);
+
 // Based on a |command_line| and the current platform, returns the effective
 // autoplay policy. In other words, it will take into account the default policy
 // if none is specified via the command line and options passed for testing.
index 7114d07fbbee812fc3f5dd1b4d060fbb81bffcde..5c592dc3fd10f5e80a3ec0566ada35bf446f724e 100644 (file)
@@ -283,6 +283,17 @@ bool DecoderStream<DemuxerStream::AUDIO>::CanReadWithoutStalling() const {
   return true;
 }
 
+// A false return value indicates that the decoder is not a platform decoder, or
+// it is still unknown (e.g. during initialization).
+template <DemuxerStream::Type StreamType>
+bool DecoderStream<StreamType>::IsPlatformDecoder() const {
+  // The decoder is owned by |decoder_selector_| during reinitialization, so
+  // during that time we return false to indicate decoder type unknown.
+  return state_ != STATE_REINITIALIZING_DECODER
+             ? decoder_->IsPlatformDecoder()
+             : false;
+}
+
 template <DemuxerStream::Type StreamType>
 int DecoderStream<StreamType>::GetMaxDecodeRequests() const {
   // The decoder is owned by |decoder_selector_| during reinitialization, so
@@ -746,26 +757,34 @@ void DecoderStream<StreamType>::ReadFromDemuxerStream() {
   TRACE_EVENT_ASYNC_BEGIN0("media", GetDemuxerReadTraceString<StreamType>(),
                            this);
   pending_demuxer_read_ = true;
-  stream_->Read(1, base::BindOnce(&DecoderStream<StreamType>::OnBuffersRead,
-                                  weak_factory_.GetWeakPtr()));
+  uint32_t buffer_read_count = 1;
+  // Do not batch with software video decoder.
+  if (IsPlatformDecoder() &&
+      base::FeatureList::IsEnabled(kVideoDecodeBatching)) {
+    buffer_read_count = GetMaxDecodeRequests() - pending_decode_requests_;
+  }
+  {
+    TRACE_EVENT2("media", "DecodeStreamRead",
+                 "StreamType:", GetStreamTypeString(),
+                 "buffer_read_count:", buffer_read_count);
+    stream_->Read(buffer_read_count,
+                  base::BindOnce(&DecoderStream<StreamType>::OnBuffersReady,
+                                 weak_factory_.GetWeakPtr()));
+  }
 }
 
 template <DemuxerStream::Type StreamType>
-void DecoderStream<StreamType>::OnBuffersRead(
+void DecoderStream<StreamType>::OnBuffersReady(
     DemuxerStream::Status status,
     DemuxerStream::DecoderBufferVector buffers) {
-  DCHECK_LE(buffers.size(), 1u) << "DecoderStream only reads a single-buffer.";
-  OnBufferReady(status, buffers.empty() ? nullptr : std::move(buffers[0]));
-}
+  if (status == DemuxerStream::kOk && buffers.empty()) {
+    MEDIA_LOG(ERROR, media_log_) << "Empty buffer received.";
+    pending_demuxer_read_ = false;
+    return;
+  }
 
-template <DemuxerStream::Type StreamType>
-void DecoderStream<StreamType>::OnBufferReady(
-    DemuxerStream::Status status,
-    scoped_refptr<DecoderBuffer> buffer) {
   TRACE_EVENT_ASYNC_END1("media", GetDemuxerReadTraceString<StreamType>(), this,
                          "status", DemuxerStream::GetStatusName(status));
-  FUNCTION_DVLOG(3) << ": " << status << ", "
-                    << (buffer ? buffer->AsHumanReadableString() : "nullptr");
 
   DCHECK(task_runner_->RunsTasksInCurrentSequence());
   DCHECK(pending_demuxer_read_);
@@ -774,7 +793,6 @@ void DecoderStream<StreamType>::OnBufferReady(
            state_ == STATE_NORMAL)
         << state_;
   }
-  DCHECK_EQ(buffer != nullptr, status == DemuxerStream::kOk) << status;
   pending_demuxer_read_ = false;
 
   // If parallel decode requests are supported, multiple read requests might
@@ -786,7 +804,10 @@ void DecoderStream<StreamType>::OnBufferReady(
         // Save valid buffers to be consumed by the new decoder.
         // |pending_buffers_| is copied to |fallback_buffers_| in
         // OnDecoderSelected().
-        pending_buffers_.push_back(std::move(buffer));
+        for (auto buffer : buffers) {
+          pending_buffers_.push_back(std::move(buffer));
+        }
+        buffers.clear();
         break;
       case DemuxerStream::kConfigChanged:
         // TODO(tguilbert): crbug.com/603713
@@ -814,8 +835,9 @@ void DecoderStream<StreamType>::OnBufferReady(
     ClearOutputs();
     // TODO(crbug.com/c/1326324): Convert |status| into a typed status so that
     // it can be set as a cause here.
-    if (read_cb_)
+    if (read_cb_) {
       SatisfyRead(DecoderStatus::Codes::kDecoderStreamDemuxerError);
+    }
   }
 
   // Decoding has been stopped.
@@ -825,8 +847,9 @@ void DecoderStream<StreamType>::OnBufferReady(
     if (reset_cb_) {
       // If we are using DecryptingDemuxerStream, we already called DDS::Reset()
       // which will continue the resetting process in its callback.
-      if (!decrypting_demuxer_stream_)
+      if (!decrypting_demuxer_stream_) {
         Reset(std::move(reset_cb_));
+      }
     }
     return;
   }
@@ -865,15 +888,17 @@ void DecoderStream<StreamType>::OnBufferReady(
         << config.AsHumanReadableString();
 
     decoder_selector_.NotifyConfigChanged();
-    if (config_change_observer_cb_)
+    if (config_change_observer_cb_) {
       config_change_observer_cb_.Run(config);
+    }
 
     state_ = STATE_FLUSHING_DECODER;
     if (reset_cb_) {
       // If we are using DecryptingDemuxerStream, we already called DDS::Reset()
       // which will continue the resetting process in its callback.
-      if (!decrypting_demuxer_stream_)
+      if (!decrypting_demuxer_stream_) {
         Reset(std::move(reset_cb_));
+      }
       // Reinitialization will continue after Reset() is done.
     } else {
       FlushDecoder();
@@ -884,14 +909,16 @@ void DecoderStream<StreamType>::OnBufferReady(
   if (reset_cb_) {
     // If we are using DecryptingDemuxerStream, we already called DDS::Reset()
     // which will continue the resetting process in its callback.
-    if (!decrypting_demuxer_stream_)
+    if (!decrypting_demuxer_stream_) {
       Reset(std::move(reset_cb_));
+    }
     return;
   }
 
   if (status == DemuxerStream::kAborted) {
-    if (read_cb_)
+    if (read_cb_) {
       SatisfyRead(DecoderStatus::Codes::kAborted);
+    }
     return;
   }
 
@@ -902,14 +929,18 @@ void DecoderStream<StreamType>::OnBufferReady(
   // changes later, which is fine for metrics purposes.
   if (!encryption_type_reported_) {
     encryption_type_reported_ = true;
-    ReportEncryptionType(buffer);
+    ReportEncryptionType(buffers[0]);
   }
 
-  Decode(std::move(buffer));
+  for (auto buffer : buffers) {
+    Decode(std::move(buffer));
+  }
+  buffers.clear();
 
   // Read more data if the decoder supports multiple parallel decoding requests.
-  if (CanDecodeMore())
+  if (CanDecodeMore()) {
     ReadFromDemuxerStream();
+  }
 }
 
 template <DemuxerStream::Type StreamType>
index 06a47e391316f0a3018a739a8bf9066213d88314..fd8cf12085c0bb295378a2bba92b3d3a1ceadebe 100644 (file)
@@ -174,6 +174,9 @@ class MEDIA_EXPORT DecoderStream {
   // Returns maximum concurrent decode requests for the current |decoder_|.
   int GetMaxDecodeRequests() const;
 
+  // Returns if current |decoder_| is a platform decoder.
+  bool IsPlatformDecoder() const;
+
   // Returns the maximum number of outputs we should keep ready at any one time.
   int GetMaxReadyOutputs() const;
 
@@ -216,12 +219,8 @@ class MEDIA_EXPORT DecoderStream {
   // Reads a buffer from |stream_| and returns the result via OnBufferReady().
   void ReadFromDemuxerStream();
 
-  void OnBuffersRead(DemuxerStream::Status status,
-                     DemuxerStream::DecoderBufferVector buffers);
-
-  // Callback for DemuxerStream::Read().
-  void OnBufferReady(DemuxerStream::Status status,
-                     scoped_refptr<DecoderBuffer> buffer);
+  void OnBuffersReady(DemuxerStream::Status status,
+                      DemuxerStream::DecoderBufferVector buffers);
 
   void ReinitializeDecoder();
 
index 35e008663531a4dbb8daa382ff14d160371d0539..90cfa01568aba4c3c0b876d00b0ab576a1753698 100644 (file)
@@ -42,6 +42,7 @@ FakeVideoDecoder::~FakeVideoDecoder() {
     SatisfyReset();
 
   decoded_frames_.clear();
+  total_decoded_frames_ = 0;
 }
 
 void FakeVideoDecoder::EnableEncryptedConfigSupport() {
@@ -201,6 +202,7 @@ void FakeVideoDecoder::SatisfySingleDecode() {
 
   DecodeCB decode_cb = std::move(held_decode_callbacks_.front());
   held_decode_callbacks_.pop_front();
+  total_decoded_frames_++;
   RunDecodeCallback(std::move(decode_cb));
 
   if (!reset_cb_.IsNull() && held_decode_callbacks_.empty())
index 7c1def62395e3097c76d758dadf1f6147b3f99ae..4cfa93b2e0f957158357a4264ba43232a217ffbe 100644 (file)
@@ -89,6 +89,8 @@ class FakeVideoDecoder : public VideoDecoder {
 
   int total_bytes_decoded() const { return total_bytes_decoded_; }
 
+  int total_decoded_frames() const { return total_decoded_frames_; }
+
  protected:
   enum State {
     STATE_UNINITIALIZED,
@@ -142,6 +144,8 @@ class FakeVideoDecoder : public VideoDecoder {
 
   bool fail_to_initialize_;
 
+  int total_decoded_frames_ = 0;
+
   // NOTE: Weak pointers must be invalidated before all other member variables.
   base::WeakPtrFactory<FakeVideoDecoder> weak_factory_{this};
 };
index 2f27ea8ac5681ae04f30f4b6570891c575e16b5d..3edd3d7da30c7592e595c8d16bc27460cc87257a 100644 (file)
@@ -633,6 +633,12 @@ TEST_P(VideoDecoderStreamTest, Read_AfterReset) {
 // Tests that the decoder stream will switch from a software decoder to a
 // hardware decoder if the config size increases
 TEST_P(VideoDecoderStreamTest, ConfigChangeSwToHw) {
+  if (base::FeatureList::IsEnabled(kVideoDecodeBatching) &&
+      GetParam().parallel_decoding != 1) {
+    // Fake demuxer allows reading over different configs when batch decoding is
+    // enabled, so we need to skip this test.
+    return;
+  }
   EnablePlatformDecoders({1});
 
   // Create a demuxer stream with a config that increases in size
@@ -657,6 +663,12 @@ TEST_P(VideoDecoderStreamTest, ConfigChangeSwToHw) {
 // Tests that the decoder stream will switch from a hardware decoder to a
 // software decoder if the config size decreases
 TEST_P(VideoDecoderStreamTest, ConfigChangeHwToSw) {
+  if (base::FeatureList::IsEnabled(kVideoDecodeBatching) &&
+      GetParam().parallel_decoding != 1) {
+    // Fake demuxer allows reading over different configs when batch decoding is
+    // enabled, so we need to skip this test.
+    return;
+  }
   EnablePlatformDecoders({1});
 
   // Create a demuxer stream with a config that progressively decreases in size
@@ -795,6 +807,53 @@ TEST_P(VideoDecoderStreamTest, Read_BlockedDemuxerAndDecoder) {
   EXPECT_FALSE(pending_read_);
 }
 
+TEST_P(VideoDecoderStreamTest, BatchDecodingWithPlatformDecoder) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(kVideoDecodeBatching);
+  int parallel_decodings = GetParam().parallel_decoding;
+
+  Initialize();
+  decoder_->SetIsPlatformDecoder(true);
+
+  // Block the decoder so that we can check the DecoderBuffer number got
+  // from a single Read() call.
+  decoder_->HoldDecode();
+  ReadOneFrame();
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(parallel_decodings, demuxer_stream_->num_buffers_returned());
+
+  demuxer_stream_->HoldNextRead();
+  decoder_->SatisfyDecode();
+
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(decoder_->total_decoded_frames(), parallel_decodings);
+}
+
+TEST_P(VideoDecoderStreamTest, NoBatchDecodingWithNonPlatformDecoder) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(kVideoDecodeBatching);
+
+  Initialize();
+  // Set the decoder as not platform decoder, so that it prevents single
+  // demuxer read to return multiple DecoderBuffers.
+  decoder_->SetIsPlatformDecoder(false);
+
+  // Block the demuxer so that we can manually unblock the first demuxer
+  // read to check the DecoderBuffer number got from a single Read() call.
+  demuxer_stream_->HoldNextRead();
+  decoder_->HoldDecode();
+  ReadOneFrame();
+  demuxer_stream_->SatisfyReadAndHoldNext();
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(1, demuxer_stream_->num_buffers_returned());
+
+  decoder_->SatisfyDecode();
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(decoder_->total_decoded_frames(), 1);
+}
+
 TEST_P(VideoDecoderStreamTest, Read_DuringEndOfStreamDecode) {
   // Test applies only when the decoder allows multiple parallel requests, and
   // they are not satisfied in a single batch.