// Copyright 2014 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/filters/decoder_selector.h" #include #include #include "base/bind.h" #include "base/callback_helpers.h" #include "base/feature_list.h" #include "base/location.h" #include "base/logging.h" #include "base/metrics/histogram_functions.h" #include "base/strings/stringprintf.h" #include "base/task/sequenced_task_runner.h" #include "base/trace_event/trace_event.h" #include "build/build_config.h" #include "build/chromeos_buildflags.h" #include "media/base/audio_decoder.h" #include "media/base/cdm_context.h" #include "media/base/demuxer_stream.h" #include "media/base/media_log.h" #include "media/base/media_switches.h" #include "media/base/video_decoder.h" #include "media/filters/decoder_stream_traits.h" #include "media/filters/decrypting_demuxer_stream.h" namespace media { namespace { const char kSelectDecoderTrace[] = "DecoderSelector::SelectDecoder"; bool SkipDecoderForRTC(const AudioDecoderConfig& /*config*/, const AudioDecoder& /*decoder*/) { return false; } bool SkipDecoderForRTC(const VideoDecoderConfig& config, const VideoDecoder& decoder) { // Skip non-platform decoders for rtc based on the feature flag. return config.is_rtc() && !decoder.IsPlatformDecoder() && !base::FeatureList::IsEnabled(kExposeSwDecodersToWebRTC); } template DecoderPriority NormalDecoderPriority(const ConfigT& config, const DecoderT& decoder) { if (SkipDecoderForRTC(config, decoder)) return DecoderPriority::kSkipped; return DecoderPriority::kNormal; } DecoderPriority ResolutionBasedDecoderPriority(const VideoDecoderConfig& config, const VideoDecoder& decoder) { #if BUILDFLAG(IS_ANDROID) constexpr auto kSoftwareDecoderHeightCutoff = 360; #elif BUILDFLAG(IS_CHROMEOS_ASH) constexpr auto kSoftwareDecoderHeightCutoff = 360; #else constexpr auto kSoftwareDecoderHeightCutoff = 720; #endif if (SkipDecoderForRTC(config, decoder)) return DecoderPriority::kSkipped; // We only do a height check to err on the side of prioritizing platform // decoders. const auto at_or_above_software_cutoff = config.visible_rect().height() >= kSoftwareDecoderHeightCutoff; // Platform decoders are deprioritized below the cutoff, and non-platform // decoders are deprioritized above it. return at_or_above_software_cutoff == decoder.IsPlatformDecoder() ? DecoderPriority::kNormal : DecoderPriority::kDeprioritized; } DecoderPriority PreferNonPlatformDecoders(const VideoDecoderConfig& config, const VideoDecoder& decoder) { // Prefer software decoders over hardware decoders. This is useful to force // software fallback for WebRTC, but still use hardware if there's no software // implementation to choose. return decoder.IsPlatformDecoder() ? DecoderPriority::kDeprioritized : DecoderPriority::kNormal; } DecoderPriority UnifiedDecoderPriority(const VideoDecoderConfig& config, const VideoDecoder& decoder) { if (config.is_rtc() || base::FeatureList::IsEnabled(kResolutionBasedDecoderPriority)) { return ResolutionBasedDecoderPriority(config, decoder); } else { return NormalDecoderPriority(config, decoder); } } template DecoderPriority SkipNonPlatformDecoders(const ConfigT& config, const DecoderT& decoder) { if (SkipDecoderForRTC(config, decoder)) return DecoderPriority::kSkipped; return decoder.IsPlatformDecoder() ? DecoderPriority::kNormal : DecoderPriority::kSkipped; } void SetDefaultDecoderPriorityCB( VideoDecoderSelector::DecoderPriorityCB* out, const DecoderStreamTraits* traits) { if (base::FeatureList::IsEnabled(kForceHardwareVideoDecoders)) { *out = base::BindRepeating( SkipNonPlatformDecoders); } else if (traits->GetPreferNonPlatformDecoders()) { *out = base::BindRepeating(PreferNonPlatformDecoders); } else { *out = base::BindRepeating(UnifiedDecoderPriority); } } void SetDefaultDecoderPriorityCB( AudioDecoderSelector::DecoderPriorityCB* out, const DecoderStreamTraits*) { if (base::FeatureList::IsEnabled(kForceHardwareAudioDecoders)) { *out = base::BindRepeating( SkipNonPlatformDecoders); } else { // Platform audio decoders are not currently prioritized or deprioritized *out = base::BindRepeating( NormalDecoderPriority); } } } // namespace template DecoderSelector::DecoderSelector( scoped_refptr task_runner, CreateDecodersCB create_decoders_cb, MediaLog* media_log) : task_runner_(std::move(task_runner)), create_decoders_cb_(std::move(create_decoders_cb)), media_log_(media_log) { DETACH_FROM_SEQUENCE(sequence_checker_); } template DecoderSelector::~DecoderSelector() { DVLOG(2) << __func__; DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); if (select_decoder_cb_) ReturnSelectionError(DecoderStatus::Codes::kFailed); } template void DecoderSelector::Initialize(StreamTraits* traits, DemuxerStream* stream, CdmContext* cdm_context, WaitingCB waiting_cb) { DVLOG(2) << __func__; DCHECK(traits); DCHECK(stream); traits_ = traits; stream_ = stream; cdm_context_ = cdm_context; waiting_cb_ = std::move(waiting_cb); // Only set this here if nobody has overridden it for tests. if (!decoder_priority_cb_) SetDefaultDecoderPriorityCB(&decoder_priority_cb_, traits_); } template void DecoderSelector::SelectDecoderInternal( SelectDecoderCB select_decoder_cb, typename Decoder::OutputCB output_cb, bool needs_new_decoders) { DVLOG(2) << __func__; DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(select_decoder_cb); DCHECK(!select_decoder_cb_); select_decoder_cb_ = std::move(select_decoder_cb); output_cb_ = std::move(output_cb); config_ = traits_->GetDecoderConfig(stream_); TRACE_EVENT_ASYNC_BEGIN2("media", kSelectDecoderTrace, this, "type", DemuxerStream::GetTypeName(StreamType), "config", config_.AsHumanReadableString()); if (!config_.IsValidConfig()) { DLOG(ERROR) << "Invalid stream config"; ReturnSelectionError(DecoderStatus::Codes::kUnsupportedConfig); return; } if (needs_new_decoders) { decoder_selection_start_ = base::TimeTicks::Now(); decode_failure_reinit_cause_ = absl::nullopt; CreateDecoders(); } GetAndInitializeNextDecoder(); } template void DecoderSelector::BeginDecoderSelection( SelectDecoderCB select_decoder_cb, typename Decoder::OutputCB output_cb) { SelectDecoderInternal(std::move(select_decoder_cb), std::move(output_cb), /*needs_new_decoders = */ true); } template void DecoderSelector::ResumeDecoderSelection( SelectDecoderCB select_decoder_cb, typename Decoder::OutputCB output_cb, DecoderStatus&& reinit_cause) { DVLOG(2) << __func__; if (!decode_failure_reinit_cause_.has_value()) decode_failure_reinit_cause_ = std::move(reinit_cause); SelectDecoderInternal(std::move(select_decoder_cb), std::move(output_cb), /*needs_new_decoders = */ false); } template void DecoderSelector::FinalizeDecoderSelection() { DVLOG(2) << __func__; DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(!select_decoder_cb_); 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; base::UmaHistogramTimes( "Media.MSE.CodecChangeTime." + stream_type + "." + decoder_type, base::TimeTicks::Now() - codec_change_start_); } // Discard any remaining decoder instances, they won't be used. decoders_.clear(); } template void DecoderSelector::NotifyConfigChanged() { DVLOG(2) << __func__; DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); is_selecting_for_config_change_ = true; DecoderConfig config = traits_->GetDecoderConfig(stream_); if (config.codec() != config_.codec()) { is_codec_changing_ = true; codec_change_start_ = base::TimeTicks::Now(); } } template void DecoderSelector::PrependDecoder( std::unique_ptr decoder) { DVLOG(2) << __func__; DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); // Decoders inserted directly should be given priority over those returned by // |create_decoders_cb_|. decoders_.insert(decoders_.begin(), std::move(decoder)); FilterAndSortAvailableDecoders(); } template void DecoderSelector::OverrideDecoderPriorityCBForTesting( DecoderPriorityCB decoder_priority_cb) { decoder_priority_cb_ = std::move(decoder_priority_cb); } template void DecoderSelector::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 void DecoderSelector::GetAndInitializeNextDecoder() { DVLOG(2) << __func__; DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(!decoder_); if (decoders_.empty()) { // Decoder selection failed. If the stream is encrypted, try again using // DecryptingDemuxerStream. if (config_.is_encrypted() && cdm_context_) { InitializeDecryptingDemuxerStream(); return; } if (decode_failure_reinit_cause_.has_value()) { ReturnSelectionError(std::move(*decode_failure_reinit_cause_)); } else { ReturnSelectionError(DecoderStatus::Codes::kUnsupportedConfig); } return; } // Initialize the first decoder on the list. decoder_ = std::move(decoders_.front()); decoders_.erase(decoders_.begin()); is_platform_decoder_ = decoder_->IsPlatformDecoder(); TRACE_EVENT_ASYNC_STEP_INTO0("media", kSelectDecoderTrace, this, GetDecoderName(decoder_->GetDecoderType())); DVLOG(2) << __func__ << ": initializing " << decoder_->GetDecoderType(); const bool is_live = stream_->liveness() == StreamLiveness::kLive; traits_->InitializeDecoder( decoder_.get(), config_, is_live, cdm_context_, base::BindOnce(&DecoderSelector::OnDecoderInitializeDone, weak_this_factory_.GetWeakPtr()), output_cb_, waiting_cb_); } template void DecoderSelector::OnDecoderInitializeDone( DecoderStatus status) { DCHECK(decoder_); DVLOG(2) << __func__ << ": " << decoder_->GetDecoderType() << " success=" << static_cast(status.code()); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); if (!status.is_ok()) { // Note: Don't track this decode status, as it is the result of // initialization failure. MEDIA_LOG(INFO, media_log_) << "Failed to initialize " << decoder_->GetDecoderType(); // Try the next decoder on the list. decoder_ = nullptr; GetAndInitializeNextDecoder(); return; } RunSelectDecoderCB(std::move(decoder_)); } template void DecoderSelector::ReturnSelectionError(DecoderStatus error) { DVLOG(1) << __func__ << ": No decoder selected"; DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(!error.is_ok()); decrypting_demuxer_stream_.reset(); decoders_.clear(); RunSelectDecoderCB(std::move(error)); } template void DecoderSelector::InitializeDecryptingDemuxerStream() { DCHECK(decoders_.empty()); DCHECK(config_.is_encrypted()); DCHECK(cdm_context_); TRACE_EVENT_ASYNC_STEP_INTO0("media", kSelectDecoderTrace, this, "DecryptingDemuxerStream"); decrypting_demuxer_stream_ = std::make_unique( task_runner_, media_log_, waiting_cb_); decrypting_demuxer_stream_->Initialize( stream_, cdm_context_, base::BindOnce( &DecoderSelector::OnDecryptingDemuxerStreamInitializeDone, weak_this_factory_.GetWeakPtr())); } template void DecoderSelector::OnDecryptingDemuxerStreamInitializeDone( PipelineStatus status) { DVLOG(2) << __func__ << ": status=" << status; DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); if (status != PIPELINE_OK) { // Since we already tried every potential decoder without DDS, give up. ReturnSelectionError( {DecoderStatus::Codes::kUnsupportedEncryptionMode, std::move(status)}); return; } // Once DDS is enabled, there is no going back. // TODO(sandersd): Support transitions from encrypted to unencrypted. stream_ = decrypting_demuxer_stream_.get(); cdm_context_ = nullptr; // We'll use the decrypted config from now on. config_ = traits_->GetDecoderConfig(stream_); DCHECK(!config_.is_encrypted()); // Try decoder selection again now that DDS is being used. CreateDecoders(); GetAndInitializeNextDecoder(); } template void DecoderSelector::RunSelectDecoderCB( DecoderOrError decoder_or_error) { DCHECK(select_decoder_cb_); TRACE_EVENT_ASYNC_END2( "media", kSelectDecoderTrace, this, "type", DemuxerStream::GetTypeName(StreamType), "decoder", base::StringPrintf( "%s (%s)", decoder_or_error.has_value() ? GetDecoderName(decoder_or_error->GetDecoderType()).c_str() : "null", decrypting_demuxer_stream_ ? "encrypted" : "unencrypted")); task_runner_->PostTask( FROM_HERE, base::BindOnce(std::move(select_decoder_cb_), std::move(decoder_or_error), std::move(decrypting_demuxer_stream_))); } template void DecoderSelector::FilterAndSortAvailableDecoders() { std::vector> decoders = std::move(decoders_); std::vector> deprioritized_decoders; DCHECK(decoder_priority_cb_); for (auto& decoder : decoders) { // Skip the decoder if this decoder doesn't support encryption for a // decrypting config if (config_.is_encrypted() && !decoder->SupportsDecryption()) continue; // Run the predicate on this decoder. switch (decoder_priority_cb_.Run(config_, *decoder)) { case DecoderPriority::kSkipped: continue; case DecoderPriority::kNormal: decoders_.push_back(std::move(decoder)); break; case DecoderPriority::kDeprioritized: deprioritized_decoders.push_back(std::move(decoder)); break; } } // Post-insert deprioritized decoders std::move(deprioritized_decoders.begin(), deprioritized_decoders.end(), std::inserter(decoders_, decoders_.end())); } // 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 // instantiate a DecoderSelector with anything but these two specializations // they'll most likely get linker errors. template class DecoderSelector; template class DecoderSelector; } // namespace media