}
// 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_);
return;
DCHECK(read_to_hold_ == -1 || read_to_hold_ > next_read_num_);
- DoRead();
+ DoRead(count);
}
AudioDecoderConfig FakeDemuxerStream::audio_decoder_config() {
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_++;
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,
private:
void UpdateVideoDecoderConfig();
- void DoRead();
+ void DoRead(int read_count = 1);
scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
"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);
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.
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
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_);
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
// 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
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.
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;
}
<< 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();
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;
}
// 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>
// 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;
// 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();
SatisfyReset();
decoded_frames_.clear();
+ total_decoded_frames_ = 0;
}
void FakeVideoDecoder::EnableEncryptedConfigSupport() {
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())
int total_bytes_decoded() const { return total_bytes_decoded_; }
+ int total_decoded_frames() const { return total_decoded_frames_; }
+
protected:
enum State {
STATE_UNINITIALIZED,
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};
};
// 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
// 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
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.