blob: 38dff412e5427fb3933f5922a51f7b293db9f12c [file] [log] [blame]
// 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 <algorithm>
#include <utility>
#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/sequenced_task_runner.h"
#include "base/strings/stringprintf.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) {
// For now, we assume that RTC decoders are able to decode non-RTC streams,
// presumably by configuring themselves based on the config's rtc bit. Since
// no decoders take any action at all based on it, this is as good as any.
return config.is_rtc() && !decoder.IsOptimizedForRTC();
}
template <typename ConfigT, typename DecoderT>
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 defined(OS_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;
}
template <typename ConfigT, typename DecoderT>
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) {
if (base::FeatureList::IsEnabled(kForceHardwareVideoDecoders)) {
*out = base::BindRepeating(
SkipNonPlatformDecoders<VideoDecoderConfig, VideoDecoder>);
} else if (base::FeatureList::IsEnabled(kResolutionBasedDecoderPriority)) {
*out = base::BindRepeating(ResolutionBasedDecoderPriority);
} else {
*out = base::BindRepeating(
NormalDecoderPriority<VideoDecoderConfig, VideoDecoder>);
}
}
void SetDefaultDecoderPriorityCB(AudioDecoderSelector::DecoderPriorityCB* out) {
if (base::FeatureList::IsEnabled(kForceHardwareAudioDecoders)) {
*out = base::BindRepeating(
SkipNonPlatformDecoders<AudioDecoderConfig, AudioDecoder>);
} else {
// Platform audio decoders are not currently prioritized or deprioritized
*out = base::BindRepeating(
NormalDecoderPriority<AudioDecoderConfig, AudioDecoder>);
}
}
} // namespace
template <DemuxerStream::Type StreamType>
DecoderSelector<StreamType>::DecoderSelector(
scoped_refptr<base::SequencedTaskRunner> 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_);
SetDefaultDecoderPriorityCB(&decoder_priority_cb_);
}
template <DemuxerStream::Type StreamType>
DecoderSelector<StreamType>::~DecoderSelector() {
DVLOG(2) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (select_decoder_cb_)
ReturnNullDecoder();
}
template <DemuxerStream::Type StreamType>
void DecoderSelector<StreamType>::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);
}
template <DemuxerStream::Type StreamType>
void DecoderSelector<StreamType>::SelectDecoder(
SelectDecoderCB select_decoder_cb,
typename Decoder::OutputCB output_cb) {
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";
ReturnNullDecoder();
return;
}
// If this is the first selection (ever or since FinalizeDecoderSelection()),
// start selection with the full list of potential decoders.
if (!is_selecting_decoders_) {
is_selecting_decoders_ = true;
decoder_selection_start_ = base::TimeTicks::Now();
CreateDecoders();
}
InitializeDecoder();
}
template <DemuxerStream::Type StreamType>
void DecoderSelector<StreamType>::FinalizeDecoderSelection() {
DVLOG(2) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
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;
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 <DemuxerStream::Type StreamType>
void DecoderSelector<StreamType>::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 <DemuxerStream::Type StreamType>
void DecoderSelector<StreamType>::PrependDecoder(
std::unique_ptr<Decoder> 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));
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_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;
}
ReturnNullDecoder();
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() == DemuxerStream::LIVENESS_LIVE;
traits_->InitializeDecoder(
decoder_.get(), config_, is_live, cdm_context_,
base::BindOnce(&DecoderSelector<StreamType>::OnDecoderInitializeDone,
weak_this_factory_.GetWeakPtr()),
output_cb_, waiting_cb_);
}
template <DemuxerStream::Type StreamType>
void DecoderSelector<StreamType>::OnDecoderInitializeDone(Status status) {
DVLOG(2) << __func__ << ": " << decoder_->GetDecoderType()
<< " success=" << std::hex << status.code();
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!status.is_ok()) {
// TODO(tmathmeyer) this was too noisy in media log. Batch all the logs
// together and then send them as an informational notice instead of
// using NotifyError.
MEDIA_LOG(INFO, media_log_)
<< "Failed to initialize " << decoder_->GetDecoderType();
// Try the next decoder on the list.
decoder_.reset();
InitializeDecoder();
return;
}
RunSelectDecoderCB();
}
template <DemuxerStream::Type StreamType>
void DecoderSelector<StreamType>::ReturnNullDecoder() {
DVLOG(1) << __func__ << ": No decoder selected";
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
decrypting_demuxer_stream_.reset();
decoder_.reset();
decoders_.clear();
RunSelectDecoderCB();
}
template <DemuxerStream::Type StreamType>
void DecoderSelector<StreamType>::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<DecryptingDemuxerStream>(
task_runner_, media_log_, waiting_cb_);
decrypting_demuxer_stream_->Initialize(
stream_, cdm_context_,
base::BindOnce(
&DecoderSelector<StreamType>::OnDecryptingDemuxerStreamInitializeDone,
weak_this_factory_.GetWeakPtr()));
}
template <DemuxerStream::Type StreamType>
void DecoderSelector<StreamType>::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.
ReturnNullDecoder();
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();
InitializeDecoder();
}
template <DemuxerStream::Type StreamType>
void DecoderSelector<StreamType>::RunSelectDecoderCB() {
DCHECK(select_decoder_cb_);
TRACE_EVENT_ASYNC_END2(
"media", kSelectDecoderTrace, this, "type",
DemuxerStream::GetTypeName(StreamType), "decoder",
base::StringPrintf(
"%s (%s)",
decoder_ ? GetDecoderName(decoder_->GetDecoderType()).c_str()
: "null",
decrypting_demuxer_stream_ ? "encrypted" : "unencrypted"));
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(std::move(select_decoder_cb_), std::move(decoder_),
std::move(decrypting_demuxer_stream_)));
}
template <DemuxerStream::Type StreamType>
void DecoderSelector<StreamType>::FilterAndSortAvailableDecoders() {
std::vector<std::unique_ptr<Decoder>> decoders = std::move(decoders_);
std::vector<std::unique_ptr<Decoder>> deprioritized_decoders;
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<DemuxerStream::AUDIO>;
template class DecoderSelector<DemuxerStream::VIDEO>;
} // namespace media