summaryrefslogtreecommitdiff
path: root/chromium/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_stream_adapter.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_stream_adapter.cc')
-rw-r--r--chromium/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_stream_adapter.cc805
1 files changed, 805 insertions, 0 deletions
diff --git a/chromium/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_stream_adapter.cc b/chromium/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_stream_adapter.cc
new file mode 100644
index 00000000000..7881d263db9
--- /dev/null
+++ b/chromium/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_stream_adapter.cc
@@ -0,0 +1,805 @@
+// 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 "third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_stream_adapter.h"
+
+#include <algorithm>
+#include <functional>
+#include <utility>
+
+#include "base/containers/circular_deque.h"
+#include "base/feature_list.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/memory/ptr_util.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/sequenced_task_runner.h"
+#include "base/stl_util.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
+#include "media/base/media_log.h"
+#include "media/base/media_switches.h"
+#include "media/base/media_util.h"
+#include "media/base/overlay_info.h"
+#include "media/base/video_types.h"
+#include "media/renderers/default_decoder_factory.h"
+#include "media/video/gpu_video_accelerator_factories.h"
+#include "media/video/video_decode_accelerator.h"
+#include "third_party/blink/public/platform/platform.h"
+#include "third_party/blink/renderer/platform/scheduler/public/post_cross_thread_task.h"
+#include "third_party/blink/renderer/platform/webrtc/webrtc_video_frame_adapter.h"
+#include "third_party/blink/renderer/platform/webrtc/webrtc_video_utils.h"
+#include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h"
+#include "third_party/webrtc/api/video/video_frame.h"
+#include "third_party/webrtc/media/base/vp9_profile.h"
+#include "third_party/webrtc/modules/video_coding/codecs/h264/include/h264.h"
+#include "third_party/webrtc/rtc_base/ref_count.h"
+#include "third_party/webrtc/rtc_base/ref_counted_object.h"
+#include "ui/gfx/color_space.h"
+
+namespace WTF {
+
+template <>
+struct CrossThreadCopier<media::VideoDecoderConfig>
+ : public CrossThreadCopierPassThrough<media::VideoDecoderConfig> {
+ STATIC_ONLY(CrossThreadCopier);
+};
+
+} // namespace WTF
+
+namespace blink {
+
+namespace {
+
+// Any reasonable size, will be overridden by the decoder anyway.
+constexpr gfx::Size kDefaultSize(640, 480);
+
+// Maximum number of buffers that we will queue in the decoder stream during
+// normal operation. It includes all buffers that we have not gotten an output
+// for. "Normal operation" means that we believe that the decoder is trying to
+// drain the queue. During init and reset, for example, we don't expect it.
+constexpr int32_t kMaxPendingBuffers = 8;
+
+// Absolute maximum number of pending buffers, whether we think the decoder is
+// draining them or not. If, at any time, we believe that there are this many
+// decodes in-flight when a new decode request arrives, we will fall back to
+// software decoding. It indicates that (a) reset never completed, (b) init
+// never completed, or (c) we're hopelessly behind.
+constexpr int32_t kAbsoluteMaxPendingBuffers = 32;
+
+// Map webrtc::VideoCodecType to media::VideoCodec.
+media::VideoCodec ToVideoCodec(webrtc::VideoCodecType video_codec_type) {
+ switch (video_codec_type) {
+ case webrtc::kVideoCodecVP8:
+ return media::kCodecVP8;
+ case webrtc::kVideoCodecVP9:
+ return media::kCodecVP9;
+ case webrtc::kVideoCodecH264:
+ return media::kCodecH264;
+ default:
+ return media::kUnknownVideoCodec;
+ }
+}
+
+// Map webrtc::SdpVideoFormat to a guess for media::VideoCodecProfile.
+media::VideoCodecProfile GuessVideoCodecProfile(
+ const webrtc::SdpVideoFormat& format) {
+ const webrtc::VideoCodecType video_codec_type =
+ webrtc::PayloadStringToCodecType(format.name);
+ switch (video_codec_type) {
+ case webrtc::kVideoCodecVP8:
+ return media::VP8PROFILE_ANY;
+ case webrtc::kVideoCodecVP9: {
+ const webrtc::VP9Profile vp9_profile =
+ webrtc::ParseSdpForVP9Profile(format.parameters)
+ .value_or(webrtc::VP9Profile::kProfile0);
+ switch (vp9_profile) {
+ case webrtc::VP9Profile::kProfile2:
+ return media::VP9PROFILE_PROFILE2;
+ case webrtc::VP9Profile::kProfile1:
+ return media::VP9PROFILE_PROFILE1;
+ case webrtc::VP9Profile::kProfile0:
+ default:
+ return media::VP9PROFILE_PROFILE0;
+ }
+ return media::VP9PROFILE_PROFILE0;
+ }
+ case webrtc::kVideoCodecH264:
+ return media::H264PROFILE_BASELINE;
+ default:
+ return media::VIDEO_CODEC_PROFILE_UNKNOWN;
+ }
+}
+
+void RecordInitializationLatency(base::TimeDelta latency) {
+ base::UmaHistogramTimes("Media.RTCVideoDecoderInitializationLatencyMs",
+ latency);
+}
+
+} // namespace
+
+// DemuxerStream implementation that forwards DecoderBuffer from some other
+// source (i.e., VideoDecoder::Decode).
+class RTCVideoDecoderStreamAdapter::InternalDemuxerStream
+ : public media::DemuxerStream {
+ public:
+ explicit InternalDemuxerStream(const media::VideoDecoderConfig& config)
+ : config_(config) {}
+
+ ~InternalDemuxerStream() override = default;
+
+ // DemuxerStream
+ void Read(ReadCB read_cb) override {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!pending_read_);
+ pending_read_ = std::move(read_cb);
+ MaybeSatisfyPendingRead();
+ }
+
+ media::AudioDecoderConfig audio_decoder_config() override {
+ NOTREACHED();
+ return media::AudioDecoderConfig();
+ }
+
+ media::VideoDecoderConfig video_decoder_config() override {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return config_;
+ }
+
+ Type type() const override {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return DemuxerStream::VIDEO;
+ }
+
+ Liveness liveness() const override {
+ // Select low-delay mode.
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return Liveness::LIVENESS_LIVE;
+ }
+
+ void EnableBitstreamConverter() override {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ }
+
+ bool SupportsConfigChanges() override {
+ // The decoder can signal a config change to us, and we'll relay it to the
+ // DecoderStream that's reading from us.
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ return true;
+ }
+
+ // We've been given a new DecoderBuffer for the DecoderStream to consume.
+ // Queue it, and maybe send it along immediately if there's a read pending.
+ void EnqueueBuffer(std::unique_ptr<PendingBuffer> pending_buffer) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ buffers_.emplace_back(std::move(pending_buffer));
+ MaybeSatisfyPendingRead();
+ }
+
+ // Start a reset -- drop all buffers and abort any pending read request.
+ void Reset() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ buffers_.clear();
+ if (pending_read_)
+ std::move(pending_read_).Run(DemuxerStream::Status::kAborted, nullptr);
+ }
+
+ private:
+ // Send more DecoderBuffers to the reader, if we can.
+ void MaybeSatisfyPendingRead() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ // If there aren't any queued decoder buffers, then nothing to do.
+ if (buffers_.empty())
+ return;
+
+ // If the decoder stream isn't trying to read, then also nothing to do.
+ if (!pending_read_)
+ return;
+
+ // See if this buffer should cause a config change. If so, send the config
+ // change first, and keep the buffer for the next call.
+ if (buffers_.front()->new_config) {
+ config_ = std::move(*(buffers_.front()->new_config));
+ std::move(pending_read_)
+ .Run(DemuxerStream::Status::kConfigChanged, nullptr);
+ return;
+ }
+
+ auto pending_buffer = std::move(buffers_.front());
+ buffers_.pop_front();
+
+ std::move(pending_read_)
+ .Run(DemuxerStream::Status::kOk, std::move(pending_buffer->buffer));
+ }
+
+ media::VideoDecoderConfig config_;
+
+ // Buffers that have been sent to us, but we haven't forwarded yet.
+ // These are only ptrs because CrossThread* binding seems to work that way.
+ base::circular_deque<std::unique_ptr<PendingBuffer>> buffers_;
+
+ // Read request from the stream that we haven't been able to fulfill, if any.
+ ReadCB pending_read_;
+
+ SEQUENCE_CHECKER(sequence_checker_);
+};
+
+// static
+std::unique_ptr<RTCVideoDecoderStreamAdapter>
+RTCVideoDecoderStreamAdapter::Create(
+ media::GpuVideoAcceleratorFactories* gpu_factories,
+ media::DecoderFactory* decoder_factory,
+ const webrtc::SdpVideoFormat& format) {
+ DVLOG(1) << __func__ << "(" << format.name << ")";
+
+ const webrtc::VideoCodecType video_codec_type =
+ webrtc::PayloadStringToCodecType(format.name);
+
+ if (!Platform::Current()->IsWebRtcHWH264DecodingEnabled(video_codec_type))
+ return nullptr;
+
+ // Bail early for unknown codecs.
+ if (ToVideoCodec(video_codec_type) == media::kUnknownVideoCodec)
+ return nullptr;
+
+ // Avoid the thread hop if the decoder is known not to support the config.
+ // TODO(sandersd): Predict size from level.
+ media::VideoDecoderConfig config(
+ ToVideoCodec(webrtc::PayloadStringToCodecType(format.name)),
+ GuessVideoCodecProfile(format),
+ media::VideoDecoderConfig::AlphaMode::kIsOpaque, media::VideoColorSpace(),
+ media::kNoTransformation, kDefaultSize, gfx::Rect(kDefaultSize),
+ kDefaultSize, media::EmptyExtraData(),
+ media::EncryptionScheme::kUnencrypted);
+
+ config.set_is_rtc(true);
+
+ // InitializeSync doesn't really initialize anything; it just posts the work
+ // to the media thread. If init fails, then we'll fall back on the first
+ // decode after we notice.
+ auto rtc_video_decoder_adapter =
+ base::WrapUnique(new RTCVideoDecoderStreamAdapter(
+ gpu_factories, decoder_factory, config, format));
+ rtc_video_decoder_adapter->InitializeSync(config);
+ return rtc_video_decoder_adapter;
+}
+
+RTCVideoDecoderStreamAdapter::RTCVideoDecoderStreamAdapter(
+ media::GpuVideoAcceleratorFactories* gpu_factories,
+ media::DecoderFactory* decoder_factory,
+ const media::VideoDecoderConfig& config,
+ const webrtc::SdpVideoFormat& format)
+ : media_task_runner_(gpu_factories->GetTaskRunner()),
+ gpu_factories_(gpu_factories),
+ decoder_factory_(decoder_factory),
+ format_(format),
+ config_(config),
+ max_pending_buffer_count_(kAbsoluteMaxPendingBuffers) {
+ DVLOG(1) << __func__;
+ decoder_info_.implementation_name = "unknown";
+ decoder_info_.is_hardware_accelerated = false;
+ DETACH_FROM_SEQUENCE(decoding_sequence_checker_);
+ weak_this_ = weak_this_factory_.GetWeakPtr();
+}
+
+RTCVideoDecoderStreamAdapter::~RTCVideoDecoderStreamAdapter() {
+ DVLOG(1) << __func__;
+ DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
+}
+
+void RTCVideoDecoderStreamAdapter::InitializeSync(
+ const media::VideoDecoderConfig& config) {
+ DVLOG(3) << __func__;
+
+ // Can be called on |worker_thread_| or |decoding_thread_|.
+ DCHECK(!media_task_runner_->RunsTasksInCurrentSequence());
+ const auto start_time = base::TimeTicks::Now();
+
+ // Allow init to complete asynchronously, since we'll probably succeed.
+ // Trying to do it synchronously can block the mojo pipe, and deadlock.
+ auto init_cb = CrossThreadBindOnce(
+ &RTCVideoDecoderStreamAdapter::OnInitializeDone, weak_this_, start_time);
+
+ PostCrossThreadTask(
+ *media_task_runner_.get(), FROM_HERE,
+ CrossThreadBindOnce(
+ &RTCVideoDecoderStreamAdapter::InitializeOnMediaThread,
+ CrossThreadUnretained(this), config, std::move(init_cb)));
+}
+
+int32_t RTCVideoDecoderStreamAdapter::InitDecode(
+ const webrtc::VideoCodec* codec_settings,
+ int32_t number_of_cores) {
+ DVLOG(1) << __func__;
+ DCHECK_CALLED_ON_VALID_SEQUENCE(decoding_sequence_checker_);
+
+ video_codec_type_ = codec_settings->codecType;
+ DCHECK_EQ(webrtc::PayloadStringToCodecType(format_.name), video_codec_type_);
+
+ base::AutoLock auto_lock(lock_);
+ init_decode_complete_ = true;
+ AttemptLogInitializationState_Locked();
+ return has_error_ ? WEBRTC_VIDEO_CODEC_UNINITIALIZED : WEBRTC_VIDEO_CODEC_OK;
+}
+
+void RTCVideoDecoderStreamAdapter::AttemptLogInitializationState_Locked() {
+ lock_.AssertAcquired();
+
+ // Don't log more than once.
+ if (logged_init_status_)
+ return;
+
+ // Don't log anything until both InitDecode and Initialize have completed,
+ // unless we failed. Log failures immediately, since both steps might not
+ // ever complete.
+ if (!has_error_ && (!init_complete_ || !init_decode_complete_))
+ return;
+
+ logged_init_status_ = true;
+
+ UMA_HISTOGRAM_BOOLEAN("Media.RTCVideoDecoderInitDecodeSuccess", !has_error_);
+ if (!has_error_) {
+ UMA_HISTOGRAM_ENUMERATION("Media.RTCVideoDecoderProfile",
+ GuessVideoCodecProfile(format_),
+ media::VIDEO_CODEC_PROFILE_MAX + 1);
+ }
+}
+
+int32_t RTCVideoDecoderStreamAdapter::Decode(
+ const webrtc::EncodedImage& input_image,
+ bool missing_frames,
+ int64_t render_time_ms) {
+ DVLOG(2) << __func__;
+ DCHECK_CALLED_ON_VALID_SEQUENCE(decoding_sequence_checker_);
+
+ // Hardware VP9 decoders don't handle more than one spatial layer. Fall back
+ // to software decoding. See https://crbug.com/webrtc/9304.
+ if (video_codec_type_ == webrtc::kVideoCodecVP9 &&
+ input_image.SpatialIndex().value_or(0) > 0) {
+#if defined(ARCH_CPU_X86_FAMILY) && BUILDFLAG(IS_CHROMEOS_ASH)
+ if (!base::FeatureList::IsEnabled(media::kVp9kSVCHWDecoding)) {
+ DLOG(ERROR) << __func__ << " multiple spatial layers.";
+ return WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE;
+ }
+#else
+ DLOG(ERROR) << __func__ << " multiple spatial layers.";
+ return WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE;
+#endif // defined(ARCH_CPU_X86_FAMILY) && BUILDFLAG(IS_CHROMEOS_ASH)
+ }
+
+ if (missing_frames) {
+ DVLOG(2) << "Missing frames";
+ // We probably can't handle broken frames. Request a key frame.
+ return WEBRTC_VIDEO_CODEC_ERROR;
+ }
+
+ if (key_frame_required_) {
+ // We discarded previous frame because we have too many pending buffers (see
+ // logic) below. Now we need to wait for the key frame and discard
+ // everything else.
+ if (input_image._frameType != webrtc::VideoFrameType::kVideoFrameKey) {
+ DVLOG(2) << "Discard non-key frame";
+ return WEBRTC_VIDEO_CODEC_ERROR;
+ }
+ DVLOG(2) << "Key frame received, resume decoding";
+ // ok, we got key frame and can continue decoding.
+ key_frame_required_ = false;
+ }
+
+ std::vector<uint32_t> spatial_layer_frame_size;
+ size_t max_sl_index = input_image.SpatialIndex().value_or(0);
+ for (size_t i = 0; i <= max_sl_index; i++) {
+ auto frame_size = input_image.SpatialLayerFrameSize(i);
+ if (!frame_size)
+ continue;
+ spatial_layer_frame_size.push_back(*frame_size);
+ }
+
+ // Convert to media::DecoderBuffer.
+ // TODO(sandersd): What is |render_time_ms|?
+ auto pending_buffer = std::make_unique<PendingBuffer>();
+ if (spatial_layer_frame_size.size() > 1) {
+ const uint8_t* side_data =
+ reinterpret_cast<const uint8_t*>(spatial_layer_frame_size.data());
+ size_t side_data_size =
+ spatial_layer_frame_size.size() * sizeof(uint32_t) / sizeof(uint8_t);
+ pending_buffer->buffer = media::DecoderBuffer::CopyFrom(
+ input_image.data(), input_image.size(), side_data, side_data_size);
+ } else {
+ pending_buffer->buffer =
+ media::DecoderBuffer::CopyFrom(input_image.data(), input_image.size());
+ }
+ pending_buffer->buffer->set_timestamp(
+ base::TimeDelta::FromMicroseconds(input_image.Timestamp()));
+ pending_buffer->buffer->set_is_key_frame(
+ input_image._frameType == webrtc::VideoFrameType::kVideoFrameKey);
+
+ // Detect config changes, and include the new config if needed.
+ if (ShouldReinitializeForSettingHDRColorSpace(input_image)) {
+ pending_buffer->new_config = config_;
+ pending_buffer->new_config->set_color_space_info(
+ blink::WebRtcToMediaVideoColorSpace(*input_image.ColorSpace()));
+ }
+
+ // Queue for decoding.
+ {
+ base::AutoLock auto_lock(lock_);
+ // TODO(crbug.com/1150098): We could destroy and re-create `decoder_stream_`
+ // here, to reset the decoder state. For now, just fail.
+ if (has_error_) {
+ DLOG(ERROR) << __func__ << " decoding failed.";
+ return WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE;
+ }
+
+ if (pending_buffer_count_ >= max_pending_buffer_count_) {
+ // We are severely behind. Drop pending buffers and request a keyframe to
+ // catch up as quickly as possible.
+ DVLOG(2) << "Pending buffers overflow";
+ // Actually we just discarded a frame. We must wait for the key frame and
+ // drop any other non-key frame.
+ key_frame_required_ = true;
+
+ // If we hit the absolute limit, then give up.
+ if (pending_buffer_count_ >= kAbsoluteMaxPendingBuffers) {
+ has_error_ = true;
+ PostCrossThreadTask(
+ *media_task_runner_.get(), FROM_HERE,
+ CrossThreadBindOnce(
+ &RTCVideoDecoderStreamAdapter::ShutdownOnMediaThread,
+ weak_this_));
+ DLOG(ERROR) << __func__ << " too many errors / pending buffers.";
+ return WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE;
+ }
+
+ // Note that this is approximate, since there might be decodes in flight.
+ // If they complete, then this might get decremented (clamped to 0), so
+ // we'll underestimate the queue length a bit until it stabilizes.
+ pending_buffer_count_ = 0;
+ // Increase to the absolute max while decoding is paused. It'll be
+ // lowered as we drain the queue.
+ max_pending_buffer_count_ = kAbsoluteMaxPendingBuffers;
+
+ PostCrossThreadTask(
+ *media_task_runner_.get(), FROM_HERE,
+ CrossThreadBindOnce(&RTCVideoDecoderStreamAdapter::ResetOnMediaThread,
+ weak_this_));
+
+ return WEBRTC_VIDEO_CODEC_ERROR;
+ }
+
+ pending_buffer_count_++;
+ }
+
+ // It would be nice to do this on the current thread, but we'd have to hop to
+ // the media thread anyway if we needed to do any work. So just hop to keep
+ // it simpler.
+ PostCrossThreadTask(
+ *media_task_runner_.get(), FROM_HERE,
+ CrossThreadBindOnce(&RTCVideoDecoderStreamAdapter::DecodeOnMediaThread,
+ weak_this_, std::move(pending_buffer)));
+
+ return WEBRTC_VIDEO_CODEC_OK;
+}
+
+int32_t RTCVideoDecoderStreamAdapter::RegisterDecodeCompleteCallback(
+ webrtc::DecodedImageCallback* callback) {
+ DVLOG(2) << __func__;
+ DCHECK_CALLED_ON_VALID_SEQUENCE(decoding_sequence_checker_);
+ DCHECK(callback);
+
+ base::AutoLock auto_lock(lock_);
+ decode_complete_callback_ = callback;
+ return has_error_ ? WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE
+ : WEBRTC_VIDEO_CODEC_OK;
+}
+
+int32_t RTCVideoDecoderStreamAdapter::Release() {
+ DVLOG(1) << __func__;
+
+ base::AutoLock auto_lock(lock_);
+
+ PostCrossThreadTask(
+ *media_task_runner_.get(), FROM_HERE,
+ CrossThreadBindOnce(&RTCVideoDecoderStreamAdapter::ShutdownOnMediaThread,
+ weak_this_));
+
+ return has_error_ ? WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE
+ : WEBRTC_VIDEO_CODEC_OK;
+}
+
+webrtc::VideoDecoder::DecoderInfo RTCVideoDecoderStreamAdapter::GetDecoderInfo()
+ const {
+ base::AutoLock auto_lock(lock_);
+ return decoder_info_;
+}
+
+void RTCVideoDecoderStreamAdapter::InitializeOnMediaThread(
+ const media::VideoDecoderConfig& config,
+ InitCB init_cb) {
+ DVLOG(3) << __func__;
+ DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
+
+ // There's no re-init these days. If we ever need to re-init, such as to
+ // clear an error, then `decoder_stream_` and `demuxer_stream_` should be
+ // recreated rather than re-used.
+ DCHECK(!decoder_stream_);
+
+ // TODO(sandersd): Plumb a real log sink here so that we can contribute to
+ // the media-internals UI. The current log just discards all messages.
+ media_log_ = std::make_unique<media::NullMediaLog>();
+
+ // Encryption is not supported.
+ media::CdmContext* cdm_context = nullptr;
+
+ // First init. Set everything up.
+ demuxer_stream_ = std::make_unique<InternalDemuxerStream>(config);
+
+ auto traits =
+ std::make_unique<media::DecoderStreamTraits<media::DemuxerStream::VIDEO>>(
+ media_log_.get());
+
+ media::RequestOverlayInfoCB request_overlay_cb = base::DoNothing();
+ auto create_decoders_cb = base::BindRepeating(
+ [](scoped_refptr<base::SequencedTaskRunner> task_runner,
+ media::DecoderFactory* decoder_factory,
+ media::GpuVideoAcceleratorFactories* gpu_factories,
+ media::MediaLog* media_log,
+ const media::RequestOverlayInfoCB& request_overlay_cb) {
+ std::vector<std::unique_ptr<media::VideoDecoder>> video_decoders;
+ decoder_factory->CreateVideoDecoders(
+ std::move(task_runner), gpu_factories, media_log,
+ request_overlay_cb, gpu_factories->GetRenderingColorSpace(),
+ &video_decoders);
+ return video_decoders;
+ },
+ media_task_runner_, base::Unretained(decoder_factory_),
+ base::Unretained(gpu_factories_), media_log_.get(),
+ std::move(request_overlay_cb));
+
+ decoder_stream_ = std::make_unique<media::VideoDecoderStream>(
+ std::move(traits), media_task_runner_, std::move(create_decoders_cb),
+ media_log_.get());
+ decoder_stream_->set_decoder_change_observer(base::BindRepeating(
+ &RTCVideoDecoderStreamAdapter::OnDecoderChanged, weak_this_));
+ decoder_stream_->Initialize(
+ demuxer_stream_.get(), ConvertToBaseOnceCallback(std::move(init_cb)),
+ cdm_context, base::DoNothing() /* statistics_cb */,
+ base::DoNothing() /* waiting_cb */);
+}
+
+void RTCVideoDecoderStreamAdapter::OnInitializeDone(base::TimeTicks start_time,
+ bool success) {
+ RecordInitializationLatency(base::TimeTicks::Now() - start_time);
+ base::AutoLock auto_lock(lock_);
+ init_complete_ = true;
+
+ if (!success) {
+ has_error_ = true;
+ // TODO(crbug.com/1150103): Is it guaranteed that there will be a next
+ // decode call to signal the error? If not, then we should use the decode
+ // callback if we have one yet.
+ } else {
+ AdjustQueueLength_Locked();
+ AttemptRead_Locked();
+ }
+
+ AttemptLogInitializationState_Locked();
+}
+
+void RTCVideoDecoderStreamAdapter::DecodeOnMediaThread(
+ std::unique_ptr<PendingBuffer> pending_buffer) {
+ DVLOG(4) << __func__;
+ DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
+
+ base::AutoLock auto_lock(lock_);
+
+ // If we're in the error state, then do nothing. `Decode()` will notify about
+ // the error.
+ if (has_error_)
+ return;
+
+ // Remember that this timestamp has already been added to the list.
+ demuxer_stream_->EnqueueBuffer(std::move(pending_buffer));
+
+ // Kickstart reading output, if we're not already.
+ AttemptRead_Locked();
+}
+
+void RTCVideoDecoderStreamAdapter::OnFrameReady(
+ media::VideoDecoderStream::ReadResult result) {
+ DVLOG(3) << __func__;
+ DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
+
+ pending_read_ = false;
+
+ switch (result.code()) {
+ case media::StatusCode::kOk:
+ break;
+ case media::StatusCode::kAborted:
+ // We're doing a Reset(), so just ignore it and keep going.
+ return;
+ default:
+ DVLOG(2) << "Entering permanent error state";
+ UMA_HISTOGRAM_ENUMERATION("Media.RTCVideoDecoderError",
+ media::VideoDecodeAccelerator::PLATFORM_FAILURE,
+ media::VideoDecodeAccelerator::ERROR_MAX + 1);
+ {
+ base::AutoLock auto_lock(lock_);
+ has_error_ = true;
+ pending_buffer_count_ = 0;
+ }
+ return;
+ }
+
+ scoped_refptr<media::VideoFrame> frame = std::move(result).value();
+ DCHECK(frame);
+
+ const base::TimeDelta timestamp = frame->timestamp();
+ webrtc::VideoFrame rtc_frame =
+ webrtc::VideoFrame::Builder()
+ .set_video_frame_buffer(
+ new rtc::RefCountedObject<blink::WebRtcVideoFrameAdapter>(
+ std::move(frame)))
+ .set_timestamp_rtp(static_cast<uint32_t>(timestamp.InMicroseconds()))
+ .set_timestamp_us(0)
+ .set_rotation(webrtc::kVideoRotation_0)
+ .build();
+
+ base::AutoLock auto_lock(lock_);
+
+ // Try to read the next output, if any, regardless if this succeeded.
+ AttemptRead_Locked();
+
+ // Assumes that Decoded() can be safely called with the lock held, which
+ // apparently it can be because RTCVideoDecoder does the same.
+ DCHECK(decode_complete_callback_);
+ // Since we can reset the queue length while things are in flight, just clamp
+ // to zero. We could choose to discard this frame, too, since it was before a
+ // reset was issued.
+ if (pending_buffer_count_ > 0)
+ pending_buffer_count_--;
+ decode_complete_callback_->Decoded(rtc_frame);
+ AdjustQueueLength_Locked();
+}
+
+void RTCVideoDecoderStreamAdapter::AttemptRead_Locked() {
+ DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
+ lock_.AssertAcquired();
+
+ // Only one read may be in-flight at once. We'll try again once the previous
+ // read completes. If a reset is in progress, a read is not allowed to start.
+ // We also may not read until DecoderStream init completes.
+ if (pending_read_ || pending_reset_ || !init_complete_ || has_error_)
+ return;
+
+ // We don't care if there are any pending decodes; keep a read running even if
+ // there aren't any. This way, we don't have to count correctly.
+
+ decoder_stream_->Read(
+ base::BindOnce(&RTCVideoDecoderStreamAdapter::OnFrameReady, weak_this_));
+ pending_read_ = true;
+}
+
+bool RTCVideoDecoderStreamAdapter::ShouldReinitializeForSettingHDRColorSpace(
+ const webrtc::EncodedImage& input_image) const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(decoding_sequence_checker_);
+
+ if (config_.profile() == media::VP9PROFILE_PROFILE2 &&
+ input_image.ColorSpace()) {
+ const media::VideoColorSpace& new_color_space =
+ blink::WebRtcToMediaVideoColorSpace(*input_image.ColorSpace());
+ if (!config_.color_space_info().IsSpecified() ||
+ new_color_space != config_.color_space_info()) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void RTCVideoDecoderStreamAdapter::ResetOnMediaThread() {
+ DVLOG(3) << __func__;
+ DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(!pending_reset_);
+ // A pending read is okay. We may decide to reset at any time, even if a read
+ // is in progress. It'll be aborted when we reset `decoder_stream_`, and no
+ // new read will be issued until the reset completes.
+
+ pending_reset_ = true;
+ demuxer_stream_->Reset();
+ decoder_stream_->Reset(base::BindOnce(
+ &RTCVideoDecoderStreamAdapter::OnResetCompleteOnMediaThread, weak_this_));
+}
+
+void RTCVideoDecoderStreamAdapter::OnResetCompleteOnMediaThread() {
+ DVLOG(3) << __func__;
+ DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(pending_reset_);
+ DCHECK(!pending_read_);
+
+ base::AutoLock auto_lock(lock_);
+
+ pending_reset_ = false;
+
+ AdjustQueueLength_Locked();
+ AttemptRead_Locked();
+}
+
+void RTCVideoDecoderStreamAdapter::AdjustQueueLength_Locked() {
+ // After an init or reset, we can have a larger backlog of queued
+ // DecoderBuffers than we'd normally allow. Since decoding is effectively
+ // paused, we let the backlog grow since it doesn't indicate that we're
+ // running behind in any meaningful sense; hopefully we'll catch up once we
+ // turn the decoder on. Once decoding un-pauses, we need to get back to a
+ // sane upper limit, without spuriously tripping the queue length check in the
+ // process. New decodes will still arrive, and decoding only has to be fast
+ // enough on average. So, the queue might get longer as we (on average) work
+ // through the backlog. `kMaxPendingBuffers` is what we believe is the
+ // maximum backlog we will see, if decoding is "fast enough".
+
+ // So, we lower the max allowable limit as we drain buffers, but allow that
+ // the queue can grow by up to `kMaxPendingBuffers` at any time from the
+ // lowest limit we've observed. This has the side-effect of resetting the
+ // limit to `kMaxPendingBuffers` if we ever do work through the backlog.
+ lock_.AssertAcquired();
+ if (pending_buffer_count_ + kMaxPendingBuffers < max_pending_buffer_count_)
+ max_pending_buffer_count_ = pending_buffer_count_ + kMaxPendingBuffers;
+}
+
+void RTCVideoDecoderStreamAdapter::ShutdownOnMediaThread() {
+ DVLOG(3) << __func__;
+ DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
+ weak_this_factory_.InvalidateWeakPtrs();
+ decoder_stream_.reset();
+ demuxer_stream_.reset();
+
+ base::AutoLock auto_lock(lock_);
+ pending_reset_ = false;
+ pending_read_ = false;
+ init_complete_ = false;
+ init_decode_complete_ = false;
+ logged_init_status_ = false;
+ pending_buffer_count_ = 0;
+ // `has_error_` might or might not be set.
+}
+
+void RTCVideoDecoderStreamAdapter::OnDecoderChanged(
+ media::VideoDecoder* decoder) {
+ DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
+
+ if (!decoder)
+ return;
+
+ base::AutoLock auto_lock(lock_);
+
+ if (decoder->IsPlatformDecoder()) {
+ decoder_info_.implementation_name = "ExternalDecoder";
+ decoder_info_.is_hardware_accelerated = true;
+ return;
+ }
+
+ // Translate software decoders to look like rtc-provided ones, to make it
+ // easier for clients to detect.
+ switch (demuxer_stream_->video_decoder_config().codec()) {
+ case media::VideoCodec::kCodecVP8:
+ case media::VideoCodec::kCodecVP9:
+ decoder_info_.implementation_name = "libvpx (DecoderStream)";
+ break;
+ case media::VideoCodec::kCodecAV1:
+ decoder_info_.implementation_name = "libaom (DecoderStream)";
+ break;
+ case media::VideoCodec::kCodecH264:
+ decoder_info_.implementation_name = "FFmpeg (DecoderStream)";
+ break;
+ default:
+ decoder_info_.implementation_name = "unknown";
+ }
+ decoder_info_.is_hardware_accelerated = false;
+}
+
+} // namespace blink