Commit 9e96a32b authored by Dan Sanders's avatar Dan Sanders Committed by Commit Bot

[media] Maintain DecoderSelector state throughout decoder selection.

This CL changes DecoderStream to maintain an instance of DecoderSelector
continually. This enabled DecoderSelector to maintain
DecryptingDemuxerStream state and the blacklist internally.

An immediate consequence is that DecoderSelector can now always try the
full list of potential decoders each time that selection is triggered.
This fixes the GPU->GPU changeType() transition, and provides a
foundation to build GPU fallforward on top of.

Another consequence is that fallback can occur more than once for a
stream. It is no longer possible to loop forever doing fallbacks, so this
is a robustness improvement in addition to being a requirement for
changeType().

Bug: 877673
Cq-Include-Trybots: luci.chromium.try:android_optional_gpu_tests_rel;luci.chromium.try:linux_optional_gpu_tests_rel;luci.chromium.try:mac_optional_gpu_tests_rel;luci.chromium.try:win_optional_gpu_tests_rel
Change-Id: I76d1bed1c914e1ec58a6653d7200fbb67f70971a
Reviewed-on: https://chromium-review.googlesource.com/1188978
Commit-Queue: Dan Sanders <sandersd@chromium.org>
Reviewed-by: default avatarDale Curtis <dalecurtis@chromium.org>
Cr-Commit-Position: refs/heads/master@{#587908}
parent 93ff7f46
......@@ -17,8 +17,6 @@ template <typename CB> class CallbackHolder {
CallbackHolder() : hold_(false) {}
~CallbackHolder() {
// Make sure all callbacks are satisfied!
DCHECK(!hold_);
DCHECK(original_cb_.is_null());
DCHECK(held_cb_.is_null());
}
......
......@@ -200,6 +200,12 @@ AudioDecoderConfig TestAudioConfig::Normal() {
Unencrypted());
}
AudioDecoderConfig TestAudioConfig::NormalEncrypted() {
return AudioDecoderConfig(kCodecVorbis, kSampleFormatPlanarF32,
CHANNEL_LAYOUT_STEREO, 44100, EmptyExtraData(),
AesCtrEncryptionScheme());
}
// static
AudioParameters TestAudioParameters::Normal() {
return AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY,
......
......@@ -110,6 +110,7 @@ class TestVideoConfig {
class TestAudioConfig {
public:
static AudioDecoderConfig Normal();
static AudioDecoderConfig NormalEncrypted();
};
// Provides pre-canned AudioParameters objects.
......
......@@ -250,10 +250,10 @@ source_set("unit_tests") {
sources = [
"audio_buffer_stream_unittest.cc",
"audio_clock_unittest.cc",
"audio_decoder_selector_unittest.cc",
"audio_renderer_algorithm_unittest.cc",
"audio_timestamp_validator_unittest.cc",
"chunk_demuxer_unittest.cc",
"decoder_selector_unittest.cc",
"decrypting_audio_decoder_unittest.cc",
"decrypting_demuxer_stream_unittest.cc",
"decrypting_video_decoder_unittest.cc",
......@@ -271,7 +271,6 @@ source_set("unit_tests") {
"source_buffer_state_unittest.cc",
"source_buffer_stream_unittest.cc",
"video_cadence_estimator_unittest.cc",
"video_decoder_selector_unittest.cc",
"video_frame_stream_unittest.cc",
"video_renderer_algorithm_unittest.cc",
"vp8_bool_decoder_unittest.cc",
......
// Copyright (c) 2012 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 <stddef.h>
#include <utility>
#include "base/bind.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "build/build_config.h"
#include "media/base/channel_layout.h"
#include "media/base/gmock_callback_support.h"
#include "media/base/media_util.h"
#include "media/base/mock_filters.h"
#include "media/base/test_helpers.h"
#include "media/filters/decoder_selector.h"
#include "media/filters/decrypting_demuxer_stream.h"
#include "testing/gtest/include/gtest/gtest.h"
#if !defined(OS_ANDROID)
#include "media/filters/decrypting_audio_decoder.h"
#endif
using ::testing::_;
using ::testing::InvokeWithoutArgs;
using ::testing::IsNull;
using ::testing::NiceMock;
using ::testing::NotNull;
using ::testing::Return;
using ::testing::StrictMock;
// Use anonymous namespace here to prevent the actions to be defined multiple
// times across multiple test files. Sadly we can't use static for them.
namespace {
MATCHER(EncryptedConfig, "") {
return arg.is_encrypted();
}
MATCHER(ClearConfig, "") {
return !arg.is_encrypted();
}
} // namespace
namespace media {
const char kDecoder1[] = "Decoder1";
const char kDecoder2[] = "Decoder2";
class AudioDecoderSelectorTest : public ::testing::Test {
public:
enum DecryptorCapability {
kNoCdm, // No CDM. Only possible for clear stream.
kNoDecryptor, // CDM is available but Decryptor is not supported.
kDecryptOnly,
kDecryptAndDecode
};
AudioDecoderSelectorTest()
: traits_(&media_log_, CHANNEL_LAYOUT_STEREO),
demuxer_stream_(
new StrictMock<MockDemuxerStream>(DemuxerStream::AUDIO)) {
// |cdm_context_| and |decryptor_| are conditionally created in
// InitializeDecoderSelector().
}
~AudioDecoderSelectorTest() override { base::RunLoop().RunUntilIdle(); }
MOCK_METHOD2(OnDecoderSelected,
void(AudioDecoder*, DecryptingDemuxerStream*));
MOCK_METHOD1(OnDecoderOneSelected, void(DecryptingDemuxerStream*));
MOCK_METHOD1(OnDecoderTwoSelected, void(DecryptingDemuxerStream*));
void MockOnDecoderSelected(std::unique_ptr<AudioDecoder> decoder,
std::unique_ptr<DecryptingDemuxerStream> stream) {
selected_decoder_ = std::move(decoder);
if (!selected_decoder_) {
OnDecoderSelected(selected_decoder_.get(), stream.get());
return;
}
if (selected_decoder_->GetDisplayName() == kDecoder1)
OnDecoderOneSelected(stream.get());
else if (selected_decoder_->GetDisplayName() == kDecoder2)
OnDecoderTwoSelected(stream.get());
else // DecryptingAudioDecoder selected...
OnDecoderSelected(selected_decoder_.get(), stream.get());
}
void UseClearStream() {
AudioDecoderConfig clear_audio_config(kCodecVorbis, kSampleFormatPlanarF32,
CHANNEL_LAYOUT_STEREO, 44100,
EmptyExtraData(), Unencrypted());
demuxer_stream_->set_audio_decoder_config(clear_audio_config);
}
void UseEncryptedStream() {
AudioDecoderConfig encrypted_audio_config(
kCodecVorbis, kSampleFormatPlanarF32, CHANNEL_LAYOUT_STEREO, 44100,
EmptyExtraData(), AesCtrEncryptionScheme());
demuxer_stream_->set_audio_decoder_config(encrypted_audio_config);
}
std::vector<std::unique_ptr<AudioDecoder>> CreateAudioDecodersForTest() {
#if !defined(OS_ANDROID)
all_decoders_.push_back(std::make_unique<DecryptingAudioDecoder>(
message_loop_.task_runner(), &media_log_));
#endif
if (decoder_1_)
testing::Mock::VerifyAndClearExpectations(decoder_1_);
if (decoder_2_)
testing::Mock::VerifyAndClearExpectations(decoder_2_);
if (num_decoders_ > 0) {
decoder_1_ = new StrictMock<MockAudioDecoder>(kDecoder1);
all_decoders_.push_back(base::WrapUnique(decoder_1_));
EXPECT_CALL(*decoder_1_, Initialize(_, _, _, _, _))
.Times(testing::AnyNumber())
.WillRepeatedly(
Invoke(this, &AudioDecoderSelectorTest::OnDecoderOneInitialized));
}
if (num_decoders_ > 1) {
decoder_2_ = new StrictMock<MockAudioDecoder>(kDecoder2);
all_decoders_.push_back(base::WrapUnique(decoder_2_));
EXPECT_CALL(*decoder_2_, Initialize(_, _, _, _, _))
.Times(testing::AnyNumber())
.WillRepeatedly(
Invoke(this, &AudioDecoderSelectorTest::OnDecoderTwoInitialized));
}
return std::move(all_decoders_);
}
void InitializeDecoderSelector(DecryptorCapability decryptor_capability,
int num_decoders) {
if (decryptor_capability != kNoCdm) {
cdm_context_.reset(new StrictMock<MockCdmContext>());
if (decryptor_capability == kNoDecryptor) {
EXPECT_CALL(*cdm_context_, GetDecryptor())
.WillRepeatedly(Return(nullptr));
} else {
decryptor_.reset(new NiceMock<MockDecryptor>());
EXPECT_CALL(*cdm_context_, GetDecryptor())
.WillRepeatedly(Return(decryptor_.get()));
EXPECT_CALL(*decryptor_, InitializeAudioDecoder(_, _))
.WillRepeatedly(
RunCallback<1>(decryptor_capability == kDecryptAndDecode));
}
}
num_decoders_ = num_decoders;
decoder_selector_.reset(new AudioDecoderSelector(
message_loop_.task_runner(),
base::Bind(&AudioDecoderSelectorTest::CreateAudioDecodersForTest,
base::Unretained(this)),
&media_log_));
}
void SelectDecoderWithBlacklist(const std::string& blacklisted_decoder) {
decoder_selector_->SelectDecoder(
&traits_, demuxer_stream_.get(), cdm_context_.get(),
blacklisted_decoder,
base::Bind(&AudioDecoderSelectorTest::MockOnDecoderSelected,
base::Unretained(this)),
base::Bind(&AudioDecoderSelectorTest::OnDecoderOutput),
base::Bind(&AudioDecoderSelectorTest::OnWaitingForDecryptionKey));
base::RunLoop().RunUntilIdle();
}
void SelectDecoder() { SelectDecoderWithBlacklist(""); }
void SelectDecoderAndDestroy() {
SelectDecoder();
EXPECT_CALL(*this, OnDecoderSelected(IsNull(), IsNull()));
decoder_selector_.reset();
base::RunLoop().RunUntilIdle();
}
static void OnDecoderOutput(const scoped_refptr<AudioBuffer>& output) {
NOTREACHED();
}
static void OnWaitingForDecryptionKey() {
NOTREACHED();
}
MOCK_METHOD5(OnDecoderOneInitialized,
void(const AudioDecoderConfig& config,
CdmContext* cdm_context,
const AudioDecoder::InitCB& init_cb,
const AudioDecoder::OutputCB& output_cb,
const AudioDecoder::WaitingForDecryptionKeyCB&
waiting_for_decryption_key_cb));
MOCK_METHOD5(OnDecoderTwoInitialized,
void(const AudioDecoderConfig& config,
CdmContext* cdm_context,
const AudioDecoder::InitCB& init_cb,
const AudioDecoder::OutputCB& output_cb,
const AudioDecoder::WaitingForDecryptionKeyCB&
waiting_for_decryption_key_cb));
MediaLog media_log_;
// Stream traits specific to audio decoding.
DecoderStreamTraits<DemuxerStream::AUDIO> traits_;
// Declare |decoder_selector_| after |demuxer_stream_| and |decryptor_| since
// |demuxer_stream_| and |decryptor_| should outlive |decoder_selector_|.
std::unique_ptr<StrictMock<MockDemuxerStream>> demuxer_stream_;
std::unique_ptr<StrictMock<MockCdmContext>> cdm_context_;
// Use NiceMock since we don't care about most of calls on the decryptor, e.g.
// RegisterNewKeyCB().
std::unique_ptr<NiceMock<MockDecryptor>> decryptor_;
std::unique_ptr<AudioDecoderSelector> decoder_selector_;
StrictMock<MockAudioDecoder>* decoder_1_ = nullptr;
StrictMock<MockAudioDecoder>* decoder_2_ = nullptr;
std::vector<std::unique_ptr<AudioDecoder>> all_decoders_;
std::unique_ptr<AudioDecoder> selected_decoder_;
int num_decoders_ = 0;
base::MessageLoop message_loop_;
private:
DISALLOW_COPY_AND_ASSIGN(AudioDecoderSelectorTest);
};
// Tests for clear streams. The CDM will not be used for clear streams so
// DecryptorCapability doesn't really matter.
TEST_F(AudioDecoderSelectorTest, ClearStream_NoClearDecoder) {
UseClearStream();
// DecoderSelector will not try decrypting decoders for clear stream, even
// if the CDM is capable of doing decrypt and decode.
InitializeDecoderSelector(kDecryptAndDecode, 0);
EXPECT_CALL(*this, OnDecoderSelected(IsNull(), IsNull()));
SelectDecoder();
}
TEST_F(AudioDecoderSelectorTest, ClearStream_OneClearDecoder) {
UseClearStream();
InitializeDecoderSelector(kNoCdm, 1);
EXPECT_CALL(*this, OnDecoderOneInitialized(ClearConfig(), _, _, _, _))
.WillOnce(RunCallback<2>(true));
EXPECT_CALL(*this, OnDecoderOneSelected(IsNull()));
SelectDecoder();
}
TEST_F(AudioDecoderSelectorTest, Destroy_ClearStream_OneClearDecoder) {
UseClearStream();
InitializeDecoderSelector(kNoCdm, 1);
EXPECT_CALL(*this, OnDecoderOneInitialized(ClearConfig(), _, _, _, _));
SelectDecoderAndDestroy();
}
TEST_F(AudioDecoderSelectorTest, ClearStream_MultipleClearDecoder) {
UseClearStream();
InitializeDecoderSelector(kNoCdm, 2);
EXPECT_CALL(*this, OnDecoderOneInitialized(ClearConfig(), _, _, _, _))
.WillOnce(RunCallback<2>(false));
EXPECT_CALL(*this, OnDecoderTwoInitialized(ClearConfig(), _, _, _, _))
.WillOnce(RunCallback<2>(true));
EXPECT_CALL(*this, OnDecoderTwoSelected(IsNull()));
SelectDecoder();
}
TEST_F(AudioDecoderSelectorTest, Destroy_ClearStream_MultipleClearDecoder) {
UseClearStream();
InitializeDecoderSelector(kNoCdm, 2);
EXPECT_CALL(*this, OnDecoderOneInitialized(ClearConfig(), _, _, _, _))
.WillOnce(RunCallback<2>(false));
EXPECT_CALL(*this, OnDecoderTwoInitialized(ClearConfig(), _, _, _, _));
SelectDecoderAndDestroy();
}
TEST_F(AudioDecoderSelectorTest, ClearStream_BlackListedDecoder) {
UseClearStream();
InitializeDecoderSelector(kNoCdm, 2);
// Decoder 1 is blacklisted and will not even be tried.
EXPECT_CALL(*this, OnDecoderTwoInitialized(ClearConfig(), _, _, _, _))
.WillOnce(RunCallback<2>(true));
EXPECT_CALL(*this, OnDecoderTwoSelected(IsNull()));
SelectDecoderWithBlacklist(kDecoder1);
}
// Tests for encrypted streams.
TEST_F(AudioDecoderSelectorTest, EncryptedStream_NoDecryptor_OneClearDecoder) {
UseEncryptedStream();
InitializeDecoderSelector(kNoDecryptor, 1);
EXPECT_CALL(*this, OnDecoderOneInitialized(EncryptedConfig(), _, _, _, _))
.WillOnce(RunCallback<2>(false));
EXPECT_CALL(*this, OnDecoderSelected(IsNull(), IsNull()));
SelectDecoder();
}
TEST_F(AudioDecoderSelectorTest,
Destroy_EncryptedStream_NoDecryptor_OneClearDecoder) {
UseEncryptedStream();
InitializeDecoderSelector(kNoDecryptor, 1);
EXPECT_CALL(*this, OnDecoderOneInitialized(EncryptedConfig(), _, _, _, _));
SelectDecoderAndDestroy();
}
TEST_F(AudioDecoderSelectorTest, EncryptedStream_NoDecryptor_MultipleDecoders) {
UseEncryptedStream();
InitializeDecoderSelector(kNoDecryptor, 2);
EXPECT_CALL(*this, OnDecoderOneInitialized(EncryptedConfig(), _, _, _, _))
.WillOnce(RunCallback<2>(false));
EXPECT_CALL(*this, OnDecoderTwoInitialized(EncryptedConfig(), _, _, _, _))
.WillOnce(RunCallback<2>(true));
EXPECT_CALL(*this, OnDecoderTwoSelected(IsNull()));
SelectDecoder();
}
TEST_F(AudioDecoderSelectorTest,
Destroy_EncryptedStream_NoDecryptor_MultipleDecoders) {
UseEncryptedStream();
InitializeDecoderSelector(kNoDecryptor, 2);
EXPECT_CALL(*this, OnDecoderOneInitialized(EncryptedConfig(), _, _, _, _))
.WillOnce(RunCallback<2>(false));
EXPECT_CALL(*this, OnDecoderTwoInitialized(EncryptedConfig(), _, _, _, _));
SelectDecoderAndDestroy();
}
TEST_F(AudioDecoderSelectorTest, EncryptedStream_DecryptOnly_NoClearDecoder) {
UseEncryptedStream();
InitializeDecoderSelector(kDecryptOnly, 0);
EXPECT_CALL(*this, OnDecoderSelected(IsNull(), IsNull()));
SelectDecoder();
}
TEST_F(AudioDecoderSelectorTest, EncryptedStream_DecryptOnly_OneClearDecoder) {
UseEncryptedStream();
InitializeDecoderSelector(kDecryptOnly, 1);
EXPECT_CALL(*this, OnDecoderOneInitialized(EncryptedConfig(), _, _, _, _))
.WillOnce(RunCallback<2>(false));
EXPECT_CALL(*this, OnDecoderOneInitialized(ClearConfig(), _, _, _, _))
.WillOnce(RunCallback<2>(true));
EXPECT_CALL(*this, OnDecoderOneSelected(NotNull()));
SelectDecoder();
}
TEST_F(AudioDecoderSelectorTest,
Destroy_EncryptedStream_DecryptOnly_OneClearDecoder) {
UseEncryptedStream();
InitializeDecoderSelector(kDecryptOnly, 1);
EXPECT_CALL(*this, OnDecoderOneInitialized(EncryptedConfig(), _, _, _, _))
.WillOnce(RunCallback<2>(false));
EXPECT_CALL(*this, OnDecoderOneInitialized(ClearConfig(), _, _, _, _));
SelectDecoderAndDestroy();
}
TEST_F(AudioDecoderSelectorTest,
EncryptedStream_DecryptOnly_MultipleClearDecoder) {
UseEncryptedStream();
InitializeDecoderSelector(kDecryptOnly, 2);
EXPECT_CALL(*this, OnDecoderOneInitialized(EncryptedConfig(), _, _, _, _))
.WillOnce(RunCallback<2>(false));
EXPECT_CALL(*this, OnDecoderTwoInitialized(EncryptedConfig(), _, _, _, _))
.WillOnce(RunCallback<2>(false));
EXPECT_CALL(*this, OnDecoderOneInitialized(ClearConfig(), _, _, _, _))
.WillOnce(RunCallback<2>(false));
EXPECT_CALL(*this, OnDecoderTwoInitialized(ClearConfig(), _, _, _, _))
.WillOnce(RunCallback<2>(true));
EXPECT_CALL(*this, OnDecoderTwoSelected(NotNull()));
SelectDecoder();
}
TEST_F(AudioDecoderSelectorTest,
Destroy_EncryptedStream_DecryptOnly_MultipleClearDecoder) {
UseEncryptedStream();
InitializeDecoderSelector(kDecryptOnly, 2);
EXPECT_CALL(*this, OnDecoderOneInitialized(EncryptedConfig(), _, _, _, _))
.WillOnce(RunCallback<2>(false));
EXPECT_CALL(*this, OnDecoderTwoInitialized(EncryptedConfig(), _, _, _, _))
.WillOnce(RunCallback<2>(false));
EXPECT_CALL(*this, OnDecoderOneInitialized(ClearConfig(), _, _, _, _))
.WillOnce(RunCallback<2>(false));
EXPECT_CALL(*this, OnDecoderTwoInitialized(ClearConfig(), _, _, _, _));
SelectDecoderAndDestroy();
}
TEST_F(AudioDecoderSelectorTest, EncryptedStream_DecryptAndDecode) {
UseEncryptedStream();
InitializeDecoderSelector(kDecryptAndDecode, 1);
#if !defined(OS_ANDROID)
// A DecryptingAudioDecoder will be created and selected. The clear decoder
// should not be touched at all. No DecryptingDemuxerStream should to be
// created.
EXPECT_CALL(*this, OnDecoderSelected(NotNull(), IsNull()));
#else
// A DecryptingDemuxerStream will be created. The clear decoder will be
// initialized and returned.
EXPECT_CALL(*this, OnDecoderOneInitialized(EncryptedConfig(), _, _, _, _))
.WillOnce(RunCallback<2>(false));
EXPECT_CALL(*this, OnDecoderOneInitialized(ClearConfig(), _, _, _, _))
.WillOnce(RunCallback<2>(true));
EXPECT_CALL(*this, OnDecoderOneSelected(NotNull()));
#endif
SelectDecoder();
}
TEST_F(AudioDecoderSelectorTest,
EncryptedStream_NoDecryptor_BlackListedDecoder) {
UseEncryptedStream();
InitializeDecoderSelector(kNoDecryptor, 2);
EXPECT_CALL(*this, OnDecoderTwoInitialized(EncryptedConfig(), _, _, _, _))
.WillOnce(RunCallback<2>(true));
EXPECT_CALL(*this, OnDecoderTwoSelected(IsNull()));
SelectDecoderWithBlacklist(kDecoder1);
}
TEST_F(AudioDecoderSelectorTest,
EncryptedStream_DecryptOnly_BlackListedDecoder) {
UseEncryptedStream();
InitializeDecoderSelector(kDecryptOnly, 2);
EXPECT_CALL(*this, OnDecoderOneInitialized(EncryptedConfig(), _, _, _, _))
.WillOnce(RunCallback<2>(false));
// Decoder2 is blacklisted so isn't tried the first time through.
// When DecryptingDemuxerStream is chosen, the blacklist is ignored.
EXPECT_CALL(*this, OnDecoderOneInitialized(ClearConfig(), _, _, _, _))
.WillOnce(RunCallback<2>(false));
EXPECT_CALL(*this, OnDecoderTwoInitialized(ClearConfig(), _, _, _, _))
.WillOnce(RunCallback<2>(true));
EXPECT_CALL(*this, OnDecoderTwoSelected(NotNull()));
SelectDecoderWithBlacklist(kDecoder2);
}
TEST_F(AudioDecoderSelectorTest,
EncryptedStream_DecryptAndDecode_BlackListedDecoder) {
UseEncryptedStream();
InitializeDecoderSelector(kDecryptAndDecode, 2);
// DecryptingAudioDecoder is blacklisted so we'll fall back to use
// DecryptingDemuxerStream to do decrypt-only.
EXPECT_CALL(*this, OnDecoderOneInitialized(EncryptedConfig(), _, _, _, _))
.WillOnce(RunCallback<2>(false));
EXPECT_CALL(*this, OnDecoderTwoInitialized(EncryptedConfig(), _, _, _, _))
.WillOnce(RunCallback<2>(false));
EXPECT_CALL(*this, OnDecoderOneInitialized(ClearConfig(), _, _, _, _))
.WillOnce(RunCallback<2>(false));
EXPECT_CALL(*this, OnDecoderTwoInitialized(ClearConfig(), _, _, _, _))
.WillOnce(RunCallback<2>(true));
EXPECT_CALL(*this, OnDecoderTwoSelected(NotNull()));
// TODO(xhwang): Avoid the hardcoded string here.
SelectDecoderWithBlacklist("DecryptingAudioDecoder");
}
} // namespace media
......@@ -8,11 +8,11 @@
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/single_thread_task_runner.h"
#include "build/build_config.h"
#include "media/base/audio_decoder.h"
#include "media/base/bind_to_current_loop.h"
#include "media/base/cdm_context.h"
#include "media/base/demuxer_stream.h"
#include "media/base/media_log.h"
......@@ -22,101 +22,88 @@
namespace media {
static bool HasValidStreamConfig(DemuxerStream* stream) {
switch (stream->type()) {
case DemuxerStream::AUDIO:
return stream->audio_decoder_config().IsValidConfig();
case DemuxerStream::VIDEO:
return stream->video_decoder_config().IsValidConfig();
case DemuxerStream::TEXT:
case DemuxerStream::UNKNOWN:
NOTREACHED();
}
return false;
}
template <DemuxerStream::Type StreamType>
DecoderSelector<StreamType>::DecoderSelector(
const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
scoped_refptr<base::SingleThreadTaskRunner> task_runner,
CreateDecodersCB create_decoders_cb,
MediaLog* media_log)
: task_runner_(task_runner),
: task_runner_(std::move(task_runner)),
create_decoders_cb_(std::move(create_decoders_cb)),
media_log_(media_log),
weak_ptr_factory_(this) {}
weak_this_factory_(this) {}
template <DemuxerStream::Type StreamType>
DecoderSelector<StreamType>::~DecoderSelector() {
DVLOG(2) << __func__;
DCHECK(task_runner_->BelongsToCurrentThread());
if (!select_decoder_cb_.is_null())
ReturnNullDecoder();
decoder_.reset();
decrypted_stream_.reset();
}
template <DemuxerStream::Type StreamType>
void DecoderSelector<StreamType>::SelectDecoder(
void DecoderSelector<StreamType>::Initialize(
StreamTraits* traits,
DemuxerStream* stream,
CdmContext* cdm_context,
const std::string& blacklisted_decoder,
const SelectDecoderCB& select_decoder_cb,
const typename Decoder::OutputCB& output_cb,
const base::Closure& waiting_for_decryption_key_cb) {
DVLOG(2) << __func__ << ": cdm_context=" << cdm_context
<< ", blacklisted_decoder=" << blacklisted_decoder;
DCHECK(task_runner_->BelongsToCurrentThread());
base::RepeatingClosure waiting_for_decryption_key_cb) {
DVLOG(2) << __func__;
DCHECK(traits);
DCHECK(stream);
traits_ = traits;
stream_ = stream;
cdm_context_ = cdm_context;
waiting_for_decryption_key_cb_ = std::move(waiting_for_decryption_key_cb);
}
template <DemuxerStream::Type StreamType>
void DecoderSelector<StreamType>::SelectDecoder(
SelectDecoderCB select_decoder_cb,
typename Decoder::OutputCB output_cb) {
DVLOG(2) << __func__;
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK(select_decoder_cb_.is_null());
// Make sure |select_decoder_cb| runs on a different execution stack.
select_decoder_cb_ = BindToCurrentLoop(select_decoder_cb);
select_decoder_cb_ = std::move(select_decoder_cb);
output_cb_ = std::move(output_cb);
config_ = traits_->GetDecoderConfig(stream_);
if (!HasValidStreamConfig(stream)) {
DLOG(ERROR) << "Invalid stream config.";
if (!config_.IsValidConfig()) {
DLOG(ERROR) << "Invalid stream config";
ReturnNullDecoder();
return;
}
traits_ = traits;
input_stream_ = stream;
cdm_context_ = cdm_context;
blacklisted_decoder_ = blacklisted_decoder;
output_cb_ = output_cb;
waiting_for_decryption_key_cb_ = waiting_for_decryption_key_cb;
decoders_ = create_decoders_cb_.Run();
config_ = traits_->GetDecoderConfig(input_stream_);
// 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;
decoders_ = create_decoders_cb_.Run();
}
InitializeDecoder();
}
template <DemuxerStream::Type StreamType>
void DecoderSelector<StreamType>::FinalizeDecoderSelection() {
DVLOG(2) << __func__;
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK(select_decoder_cb_.is_null());
is_selecting_decoders_ = false;
// Discard any remaining decoder instances, they won't be used.
decoders_.clear();
}
template <DemuxerStream::Type StreamType>
void DecoderSelector<StreamType>::InitializeDecoder() {
DVLOG(2) << __func__;
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK(!decoder_);
// Select the next non-blacklisted decoder.
while (!decoders_.empty()) {
std::unique_ptr<Decoder> decoder(std::move(decoders_.front()));
decoders_.erase(decoders_.begin());
// When |decrypted_stream_| is selected, the |config_| has changed so ignore
// the blacklist.
if (decrypted_stream_ ||
decoder->GetDisplayName() != blacklisted_decoder_) {
decoder_ = std::move(decoder);
break;
}
}
if (!decoder_) {
// No decoder could handle encrypted content, try to do decrypt-only.
if (!tried_decrypting_demuxer_stream_ && config_.is_encrypted()) {
if (decoders_.empty()) {
// Decoder selection failed. If the stream is encrypted, try again using
// DecryptingDemuxerStream.
if (config_.is_encrypted() && cdm_context_) {
InitializeDecryptingDemuxerStream();
return;
}
......@@ -125,90 +112,91 @@ void DecoderSelector<StreamType>::InitializeDecoder() {
return;
}
// Initialize the first decoder on the list.
decoder_ = std::move(decoders_.front());
decoders_.erase(decoders_.begin());
DVLOG(2) << __func__ << ": initializing " << decoder_->GetDisplayName();
const bool is_live = stream_->liveness() == DemuxerStream::LIVENESS_LIVE;
traits_->InitializeDecoder(
decoder_.get(), config_,
input_stream_->liveness() == DemuxerStream::LIVENESS_LIVE, cdm_context_,
base::Bind(&DecoderSelector<StreamType>::DecoderInitDone,
weak_ptr_factory_.GetWeakPtr()),
decoder_.get(), config_, is_live, cdm_context_,
base::BindRepeating(&DecoderSelector<StreamType>::OnDecoderInitializeDone,
weak_this_factory_.GetWeakPtr()),
output_cb_, waiting_for_decryption_key_cb_);
}
template <DemuxerStream::Type StreamType>
void DecoderSelector<StreamType>::DecoderInitDone(bool success) {
void DecoderSelector<StreamType>::OnDecoderInitializeDone(bool success) {
DVLOG(2) << __func__ << ": " << decoder_->GetDisplayName()
<< " success=" << success;
DCHECK(task_runner_->BelongsToCurrentThread());
if (!success) {
// Try the next decoder on the list.
decoder_.reset();
InitializeDecoder();
return;
}
DVLOG(1) << __func__ << ": " << decoder_->GetDisplayName()
<< " selected. DecryptingDemuxerStream "
<< (decrypted_stream_ ? "also" : "not") << " selected.";
decoders_.clear();
base::ResetAndReturn(&select_decoder_cb_)
.Run(std::move(decoder_), std::move(decrypted_stream_));
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>::ReturnNullDecoder() {
DVLOG(1) << __func__ << ": No decoder selected.";
DVLOG(1) << __func__ << ": No decoder selected";
DCHECK(task_runner_->BelongsToCurrentThread());
decoders_.clear();
base::ResetAndReturn(&select_decoder_cb_)
.Run(std::unique_ptr<Decoder>(),
std::unique_ptr<DecryptingDemuxerStream>());
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(std::move(select_decoder_cb_), nullptr, nullptr));
}
template <DemuxerStream::Type StreamType>
void DecoderSelector<StreamType>::InitializeDecryptingDemuxerStream() {
decrypted_stream_.reset(new DecryptingDemuxerStream(
task_runner_, media_log_, waiting_for_decryption_key_cb_));
DCHECK(decoders_.empty());
DCHECK(config_.is_encrypted());
DCHECK(cdm_context_);
decrypted_stream_->Initialize(
input_stream_, cdm_context_,
base::Bind(&DecoderSelector<StreamType>::DecryptingDemuxerStreamInitDone,
weak_ptr_factory_.GetWeakPtr()));
decrypting_demuxer_stream_ = std::make_unique<DecryptingDemuxerStream>(
task_runner_, media_log_, waiting_for_decryption_key_cb_);
decrypting_demuxer_stream_->Initialize(
stream_, cdm_context_,
base::BindRepeating(
&DecoderSelector<StreamType>::OnDecryptingDemuxerStreamInitializeDone,
weak_this_factory_.GetWeakPtr()));
}
template <DemuxerStream::Type StreamType>
void DecoderSelector<StreamType>::DecryptingDemuxerStreamInitDone(
void DecoderSelector<StreamType>::OnDecryptingDemuxerStreamInitializeDone(
PipelineStatus status) {
DVLOG(2) << __func__
<< ": status=" << MediaLog::PipelineStatusToString(status);
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK(cdm_context_);
// If DecryptingDemuxerStream initialization failed, we've already tried every
// possible decoder, so we can just ReturnNullDecoder() here.
if (status != PIPELINE_OK) {
DCHECK(decoders_.empty());
DCHECK(config_.is_encrypted());
decrypted_stream_.reset();
// Since we already tried every potential decoder without DDS, give up.
decrypting_demuxer_stream_.reset();
ReturnNullDecoder();
return;
}
// If DecryptingDemuxerStream initialization succeeded, we'll use it to do
// decryption and use a decoder to decode the clear stream. Otherwise, we'll
// try to see whether any decoder can decrypt-and-decode the encrypted stream
// directly. So in both cases, we'll initialize the decoders.
input_stream_ = decrypted_stream_.get();
config_ = traits_->GetDecoderConfig(input_stream_);
// 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());
// If we're here we tried all the decoders w/ is_encrypted=true, try again
// now that the stream is being decrypted by the demuxer.
DCHECK(decoders_.empty());
DCHECK(!tried_decrypting_demuxer_stream_);
// Try decoder selection again now that DDS is being used.
decoders_ = create_decoders_cb_.Run();
tried_decrypting_demuxer_stream_ = true;
InitializeDecoder();
}
......
......@@ -26,9 +26,8 @@ class CdmContext;
class DecryptingDemuxerStream;
class MediaLog;
// DecoderSelector (creates if necessary and) initializes the proper
// Decoder for a given DemuxerStream. If the given DemuxerStream is
// encrypted, a DecryptingDemuxerStream may also be created.
// DecoderSelector handles construction and initialization of Decoders for a
// DemuxerStream, and maintains the state required for decoder fallback.
// The template parameter |StreamType| is the type of stream we will be
// selecting a decoder for.
template<DemuxerStream::Type StreamType>
......@@ -44,87 +43,76 @@ class MEDIA_EXPORT DecoderSelector {
using CreateDecodersCB =
base::RepeatingCallback<std::vector<std::unique_ptr<Decoder>>()>;
// Indicates completion of Decoder selection.
// - First parameter: The initialized Decoder. If it's set to NULL, then
// Decoder initialization failed.
// - Second parameter: The initialized DecryptingDemuxerStream. If it's not
// NULL, then a DecryptingDemuxerStream is created and initialized to do
// decryption for the initialized Decoder.
// Note: The caller owns selected Decoder and DecryptingDemuxerStream.
// Emits the result of a single call to SelectDecoder(). Parameters are
// 1: The initialized Decoder. nullptr if selection failed.
// 2: The initialized DecryptingDemuxerStream, if one was created. This
// happens at most once for a DecoderSelector instance.
// The caller owns the Decoder and DecryptingDemuxerStream.
//
// The caller should call DecryptingDemuxerStream::Reset() before
// calling Decoder::Reset() to release any pending decryption or read.
using SelectDecoderCB =
base::Callback<void(std::unique_ptr<Decoder>,
std::unique_ptr<DecryptingDemuxerStream>)>;
base::OnceCallback<void(std::unique_ptr<Decoder>,
std::unique_ptr<DecryptingDemuxerStream>)>;
DecoderSelector(
const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
CreateDecodersCB create_decoders_cb,
MediaLog* media_log);
DecoderSelector(scoped_refptr<base::SingleThreadTaskRunner> task_runner,
CreateDecodersCB create_decoders_cb,
MediaLog* media_log);
// Aborts pending Decoder selection and fires |select_decoder_cb| with
// null and null immediately if it's pending.
// Aborts any pending decoder selection.
~DecoderSelector();
// Initializes and selects the first Decoder that can decode the |stream|.
// The selected Decoder (and DecryptingDemuxerStream) is returned via
// the |select_decoder_cb|.
// Notes:
// 1. This must not be called again before |select_decoder_cb| is run.
// 2. |create_decoders_cb| will be called to create a list of candidate
// decoders to select from.
// 3. The |blacklisted_decoder| will be skipped in the decoder selection
// process, unless DecryptingDemuxerStream is chosen. This is because
// DecryptingDemuxerStream updates the |config_|, and the blacklist should
// only be applied to the original |stream| config.
// 4. All decoders that are not selected will be deleted upon returning
// |select_decoder_cb|.
// 5. |cdm_context| is optional. If |cdm_context| is null, no CDM will be
// available to perform decryption.
void SelectDecoder(StreamTraits* traits,
DemuxerStream* stream,
CdmContext* cdm_context,
const std::string& blacklisted_decoder,
const SelectDecoderCB& select_decoder_cb,
const typename Decoder::OutputCB& output_cb,
const base::Closure& waiting_for_decryption_key_cb);
// Initialize with stream parameters. Should be called exactly once.
void Initialize(StreamTraits* traits,
DemuxerStream* stream,
CdmContext* cdm_context,
base::RepeatingClosure waiting_for_decryption_key_cb);
// Selects and initializes a decoder, which will be returned via
// |select_decoder_cb| posted to |task_runner|. Subsequent calls to
// SelectDecoder() will return different decoder instances, until all
// potential decoders have been exhausted.
//
// When the caller determines that decoder selection has succeeded (eg.
// because the decoder decoded a frame successfully), it should call
// FinalizeDecoderSelection().
//
// Must not be called while another selection is pending.
void SelectDecoder(SelectDecoderCB select_decoder_cb,
typename Decoder::OutputCB output_cb);
// Signals that decoder selection has been completed (successfully). Future
// calls to SelectDecoder() will select from the full list of decoders.
void FinalizeDecoderSelection();
private:
void InitializeDecoder();
void DecoderInitDone(bool success);
void OnDecoderInitializeDone(bool success);
void ReturnNullDecoder();
void InitializeDecryptingDemuxerStream();
void DecryptingDemuxerStreamInitDone(PipelineStatus status);
void OnDecryptingDemuxerStreamInitializeDone(PipelineStatus status);
scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
CreateDecodersCB create_decoders_cb_;
MediaLog* media_log_;
StreamTraits* traits_;
StreamTraits* traits_ = nullptr;
DemuxerStream* stream_ = nullptr;
CdmContext* cdm_context_ = nullptr;
base::RepeatingClosure waiting_for_decryption_key_cb_;
// Could be the |stream| passed in SelectDecoder, or |decrypted_stream_| when
// a DecryptingDemuxerStream is selected.
DemuxerStream* input_stream_ = nullptr;
// Overall decoder selection state.
DecoderConfig config_;
bool is_selecting_decoders_ = false;
std::vector<std::unique_ptr<Decoder>> decoders_;
CdmContext* cdm_context_;
std::string blacklisted_decoder_;
// State for a single SelectDecoder() invocation.
SelectDecoderCB select_decoder_cb_;
typename Decoder::OutputCB output_cb_;
base::Closure waiting_for_decryption_key_cb_;
std::vector<std::unique_ptr<Decoder>> decoders_;
std::unique_ptr<Decoder> decoder_;
std::unique_ptr<DecryptingDemuxerStream> decrypted_stream_;
// Config of the |input_stream| used to initialize decoders.
DecoderConfig config_;
// Indicates if we tried to initialize |decrypted_stream_|.
bool tried_decrypting_demuxer_stream_ = false;
std::unique_ptr<DecryptingDemuxerStream> decrypting_demuxer_stream_;
// NOTE: Weak pointers must be invalidated before all other member variables.
base::WeakPtrFactory<DecoderSelector> weak_ptr_factory_;
base::WeakPtrFactory<DecoderSelector> weak_this_factory_;
DISALLOW_IMPLICIT_CONSTRUCTORS(DecoderSelector);
};
......
// Copyright 2018 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 <memory>
#include <tuple>
#include "base/bind.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/scoped_refptr.h"
#include "base/test/scoped_task_environment.h"
#include "build/build_config.h"
#include "media/base/demuxer_stream.h"
#include "media/base/gmock_callback_support.h"
#include "media/base/mock_filters.h"
#include "media/base/test_helpers.h"
#include "media/filters/decoder_selector.h"
#include "media/filters/decrypting_demuxer_stream.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#if !defined(OS_ANDROID)
#include "media/filters/decrypting_audio_decoder.h"
#include "media/filters/decrypting_video_decoder.h"
#endif // !defined(OS_ANDROID)
using ::testing::_;
using ::testing::IsNull;
using ::testing::NiceMock;
using ::testing::NotNull;
using ::testing::Return;
using ::testing::StrictMock;
namespace media {
namespace {
enum DecryptorCapability {
kNoDecryptor,
kDecryptOnly,
kDecryptAndDecode,
};
enum DecoderCapability {
kAlwaysFail,
kClearOnly,
kEncryptedOnly,
kAlwaysSucceed,
};
bool IsConfigSupported(DecoderCapability capability, bool is_encrypted) {
switch (capability) {
case kAlwaysFail:
return false;
case kClearOnly:
return !is_encrypted;
case kEncryptedOnly:
return is_encrypted;
case kAlwaysSucceed:
return true;
}
}
const char kNoDecoder[] = "";
const char kDecoder1[] = "Decoder1";
const char kDecoder2[] = "Decoder2";
// Specializations for the AUDIO version of the test.
class AudioDecoderSelectorTestParam {
public:
static constexpr DemuxerStream::Type kStreamType = DemuxerStream::AUDIO;
using StreamTraits = DecoderStreamTraits<DemuxerStream::AUDIO>;
using MockDecoder = MockAudioDecoder;
using Output = AudioBuffer;
#if !defined(OS_ANDROID)
static constexpr char kDecryptingDecoder[] = "DecryptingAudioDecoder";
using DecryptingDecoder = DecryptingAudioDecoder;
#endif // !defined(OS_ANDROID)
// StreamTraits() takes different parameters depending on the type.
static std::unique_ptr<StreamTraits> CreateStreamTraits(MediaLog* media_log) {
return std::make_unique<StreamTraits>(media_log, CHANNEL_LAYOUT_STEREO);
}
// Decoder::Initialize() takes different parameters depending on the type.
static void ExpectInitialize(MockDecoder* decoder,
DecoderCapability capability) {
EXPECT_CALL(*decoder, Initialize(_, _, _, _, _))
.WillRepeatedly(
[capability](const AudioDecoderConfig& config, CdmContext*,
const AudioDecoder::InitCB& init_cb,
const AudioDecoder::OutputCB&,
const AudioDecoder::WaitingForDecryptionKeyCB&) {
init_cb.Run(IsConfigSupported(capability, config.is_encrypted()));
});
}
};
// Allocate storage for the member variables.
constexpr DemuxerStream::Type AudioDecoderSelectorTestParam::kStreamType;
#if !defined(OS_ANDROID)
constexpr char AudioDecoderSelectorTestParam::kDecryptingDecoder[];
#endif // !defined(OS_ANDROID)
// Specializations for the VIDEO version of the test.
class VideoDecoderSelectorTestParam {
public:
static constexpr DemuxerStream::Type kStreamType = DemuxerStream::VIDEO;
using StreamTraits = DecoderStreamTraits<DemuxerStream::VIDEO>;
using MockDecoder = MockVideoDecoder;
using Output = VideoFrame;
#if !defined(OS_ANDROID)
static constexpr char kDecryptingDecoder[] = "DecryptingVideoDecoder";
using DecryptingDecoder = DecryptingVideoDecoder;
#endif // !defined(OS_ANDROID)
static std::unique_ptr<StreamTraits> CreateStreamTraits(MediaLog* media_log) {
return std::make_unique<StreamTraits>(media_log);
}
static void ExpectInitialize(MockDecoder* decoder,
DecoderCapability capability) {
EXPECT_CALL(*decoder, Initialize(_, _, _, _, _, _))
.WillRepeatedly(
[capability](const VideoDecoderConfig& config, bool low_delay,
CdmContext*, const VideoDecoder::InitCB& init_cb,
const VideoDecoder::OutputCB&,
const VideoDecoder::WaitingForDecryptionKeyCB&) {
init_cb.Run(IsConfigSupported(capability, config.is_encrypted()));
});
}
};
// Allocate storate for the member variables.
constexpr DemuxerStream::Type VideoDecoderSelectorTestParam::kStreamType;
#if !defined(OS_ANDROID)
constexpr char VideoDecoderSelectorTestParam::kDecryptingDecoder[];
#endif // !defined(OS_ANDROID)
} // namespace
// Note: The parameter is called TypeParam in the test cases regardless of what
// we call it here. It's been named the same for convenience.
// Note: The test fixtures inherit from this class. Inside the test cases the
// test fixture class is called TestFixture.
template <typename TypeParam>
class DecoderSelectorTest : public ::testing::Test {
public:
// Convenience aliases.
using Self = DecoderSelectorTest<TypeParam>;
using StreamTraits = typename TypeParam::StreamTraits;
using Decoder = typename StreamTraits::DecoderType;
using MockDecoder = typename TypeParam::MockDecoder;
using Output = typename TypeParam::Output;
DecoderSelectorTest()
: traits_(TypeParam::CreateStreamTraits(&media_log_)),
demuxer_stream_(TypeParam::kStreamType) {}
void OnWaitingForDecryptionKey() { NOTREACHED(); }
void OnOutput(const scoped_refptr<Output>& output) { NOTREACHED(); }
MOCK_METHOD2_T(OnDecoderSelected,
void(std::string, std::unique_ptr<DecryptingDemuxerStream>));
void OnDecoderSelectedThunk(
std::unique_ptr<Decoder> decoder,
std::unique_ptr<DecryptingDemuxerStream> decrypting_demuxer_stream) {
// Report only the name of the decoder, since that's what the tests care
// about. The decoder will be destructed immediately.
OnDecoderSelected(decoder ? decoder->GetDisplayName() : kNoDecoder,
std::move(decrypting_demuxer_stream));
}
void AddDecryptingDecoder() {
// Require the DecryptingDecoder to be first, because that's easier to
// implement.
DCHECK(mock_decoders_to_create_.empty());
DCHECK(!use_decrypting_decoder_);
use_decrypting_decoder_ = true;
}
void AddMockDecoder(const std::string& decoder_name,
DecoderCapability capability) {
// Actual decoders are created in CreateDecoders(), which may be called
// multiple times by the DecoderSelector.
mock_decoders_to_create_.emplace_back(decoder_name, capability);
}
std::vector<std::unique_ptr<Decoder>> CreateDecoders() {
std::vector<std::unique_ptr<Decoder>> decoders;
#if !defined(OS_ANDROID)
if (use_decrypting_decoder_) {
decoders.push_back(
std::make_unique<typename TypeParam::DecryptingDecoder>(
scoped_task_environment_.GetMainThreadTaskRunner(), &media_log_));
}
#endif // !defined(OS_ANDROID)
for (const auto& info : mock_decoders_to_create_) {
std::unique_ptr<StrictMock<MockDecoder>> decoder =
std::make_unique<StrictMock<MockDecoder>>(info.first);
TypeParam::ExpectInitialize(decoder.get(), info.second);
decoders.push_back(std::move(decoder));
}
return decoders;
}
void CreateCdmContext(DecryptorCapability capability) {
DCHECK(!decoder_selector_);
cdm_context_ = std::make_unique<StrictMock<MockCdmContext>>();
if (capability == kNoDecryptor) {
EXPECT_CALL(*cdm_context_, GetDecryptor())
.WillRepeatedly(Return(nullptr));
return;
}
decryptor_ = std::make_unique<NiceMock<MockDecryptor>>();
EXPECT_CALL(*cdm_context_, GetDecryptor())
.WillRepeatedly(Return(decryptor_.get()));
switch (TypeParam::kStreamType) {
case DemuxerStream::AUDIO:
EXPECT_CALL(*decryptor_, InitializeAudioDecoder(_, _))
.WillRepeatedly(RunCallback<1>(capability == kDecryptAndDecode));
break;
case DemuxerStream::VIDEO:
EXPECT_CALL(*decryptor_, InitializeVideoDecoder(_, _))
.WillRepeatedly(RunCallback<1>(capability == kDecryptAndDecode));
break;
default:
NOTREACHED();
}
}
void CreateDecoderSelector() {
decoder_selector_ =
std::make_unique<DecoderSelector<TypeParam::kStreamType>>(
scoped_task_environment_.GetMainThreadTaskRunner(),
base::BindRepeating(&Self::CreateDecoders, base::Unretained(this)),
&media_log_);
decoder_selector_->Initialize(
traits_.get(), &demuxer_stream_, cdm_context_.get(),
base::BindRepeating(&Self::OnWaitingForDecryptionKey,
base::Unretained(this)));
}
void UseClearDecoderConfig() {
switch (TypeParam::kStreamType) {
case DemuxerStream::AUDIO:
demuxer_stream_.set_audio_decoder_config(TestAudioConfig::Normal());
break;
case DemuxerStream::VIDEO:
demuxer_stream_.set_video_decoder_config(TestVideoConfig::Normal());
break;
default:
NOTREACHED();
}
}
void UseEncryptedDecoderConfig() {
switch (TypeParam::kStreamType) {
case DemuxerStream::AUDIO:
demuxer_stream_.set_audio_decoder_config(
TestAudioConfig::NormalEncrypted());
break;
case DemuxerStream::VIDEO:
demuxer_stream_.set_video_decoder_config(
TestVideoConfig::NormalEncrypted());
break;
default:
NOTREACHED();
}
}
void SelectDecoder() {
decoder_selector_->SelectDecoder(
base::BindOnce(&Self::OnDecoderSelectedThunk, base::Unretained(this)),
base::BindRepeating(&Self::OnOutput, base::Unretained(this)));
RunUntilIdle();
}
void RunUntilIdle() { scoped_task_environment_.RunUntilIdle(); }
base::test::ScopedTaskEnvironment scoped_task_environment_;
MediaLog media_log_;
std::unique_ptr<StreamTraits> traits_;
StrictMock<MockDemuxerStream> demuxer_stream_;
std::unique_ptr<StrictMock<MockCdmContext>> cdm_context_;
std::unique_ptr<NiceMock<MockDecryptor>> decryptor_;
std::unique_ptr<DecoderSelector<TypeParam::kStreamType>> decoder_selector_;
bool use_decrypting_decoder_ = false;
std::vector<std::pair<std::string, DecoderCapability>>
mock_decoders_to_create_;
private:
DISALLOW_COPY_AND_ASSIGN(DecoderSelectorTest);
};
using DecoderSelectorTestParams =
::testing::Types<AudioDecoderSelectorTestParam,
VideoDecoderSelectorTestParam>;
TYPED_TEST_CASE(DecoderSelectorTest, DecoderSelectorTestParams);
// Tests for clear streams. CDM will not be used for clear streams so
// DecryptorCapability doesn't really matter.
TYPED_TEST(DecoderSelectorTest, ClearStream_NoDecoders) {
this->UseClearDecoderConfig();
this->CreateDecoderSelector();
EXPECT_CALL(*this, OnDecoderSelected(kNoDecoder, IsNull()));
this->SelectDecoder();
}
TYPED_TEST(DecoderSelectorTest, ClearStream_NoClearDecoder) {
this->AddDecryptingDecoder();
this->UseClearDecoderConfig();
this->CreateDecoderSelector();
EXPECT_CALL(*this, OnDecoderSelected(kNoDecoder, IsNull()));
this->SelectDecoder();
}
TYPED_TEST(DecoderSelectorTest, ClearStream_OneClearDecoder) {
this->AddMockDecoder(kDecoder1, kClearOnly);
this->UseClearDecoderConfig();
this->CreateDecoderSelector();
EXPECT_CALL(*this, OnDecoderSelected(kDecoder1, IsNull()));
this->SelectDecoder();
}
TYPED_TEST(DecoderSelectorTest, ClearStream_InternalFallback) {
this->AddMockDecoder(kDecoder1, kAlwaysFail);
this->AddMockDecoder(kDecoder2, kClearOnly);
this->UseClearDecoderConfig();
this->CreateDecoderSelector();
EXPECT_CALL(*this, OnDecoderSelected(kDecoder2, IsNull()));
this->SelectDecoder();
}
TYPED_TEST(DecoderSelectorTest, ClearStream_ExternalFallback) {
this->AddMockDecoder(kDecoder1, kClearOnly);
this->AddMockDecoder(kDecoder2, kClearOnly);
this->UseClearDecoderConfig();
this->CreateDecoderSelector();
EXPECT_CALL(*this, OnDecoderSelected(kDecoder1, IsNull()));
this->SelectDecoder();
EXPECT_CALL(*this, OnDecoderSelected(kDecoder2, IsNull()));
this->SelectDecoder();
EXPECT_CALL(*this, OnDecoderSelected(kNoDecoder, IsNull()));
this->SelectDecoder();
}
TYPED_TEST(DecoderSelectorTest, ClearStream_FinalizeDecoderSelection) {
this->AddMockDecoder(kDecoder1, kClearOnly);
this->AddMockDecoder(kDecoder2, kClearOnly);
this->UseClearDecoderConfig();
this->CreateDecoderSelector();
EXPECT_CALL(*this, OnDecoderSelected(kDecoder1, IsNull()));
this->SelectDecoder();
this->decoder_selector_->FinalizeDecoderSelection();
EXPECT_CALL(*this, OnDecoderSelected(kDecoder1, IsNull()));
this->SelectDecoder();
}
// Tests for encrypted streams.
TYPED_TEST(DecoderSelectorTest, EncryptedStream_NoDecryptor_OneClearDecoder) {
this->AddMockDecoder(kDecoder1, kClearOnly);
this->CreateCdmContext(kNoDecryptor);
this->UseEncryptedDecoderConfig();
this->CreateDecoderSelector();
EXPECT_CALL(*this, OnDecoderSelected(kNoDecoder, IsNull()));
this->SelectDecoder();
}
TYPED_TEST(DecoderSelectorTest, EncryptedStream_NoDecryptor_InternalFallback) {
this->AddMockDecoder(kDecoder1, kClearOnly);
this->AddMockDecoder(kDecoder2, kEncryptedOnly);
this->CreateCdmContext(kNoDecryptor);
this->UseEncryptedDecoderConfig();
this->CreateDecoderSelector();
EXPECT_CALL(*this, OnDecoderSelected(kDecoder2, IsNull()));
this->SelectDecoder();
}
TYPED_TEST(DecoderSelectorTest, EncryptedStream_NoDecryptor_ExternalFallback) {
this->AddMockDecoder(kDecoder1, kEncryptedOnly);
this->AddMockDecoder(kDecoder2, kEncryptedOnly);
this->CreateCdmContext(kNoDecryptor);
this->UseEncryptedDecoderConfig();
this->CreateDecoderSelector();
EXPECT_CALL(*this, OnDecoderSelected(kDecoder1, IsNull()));
this->SelectDecoder();
EXPECT_CALL(*this, OnDecoderSelected(kDecoder2, IsNull()));
this->SelectDecoder();
}
TYPED_TEST(DecoderSelectorTest,
EncryptedStream_NoDecryptor_FinalizeDecoderSelection) {
this->AddMockDecoder(kDecoder1, kEncryptedOnly);
this->AddMockDecoder(kDecoder2, kEncryptedOnly);
this->CreateCdmContext(kNoDecryptor);
this->UseEncryptedDecoderConfig();
this->CreateDecoderSelector();
EXPECT_CALL(*this, OnDecoderSelected(kDecoder1, IsNull()));
this->SelectDecoder();
this->decoder_selector_->FinalizeDecoderSelection();
EXPECT_CALL(*this, OnDecoderSelected(kDecoder1, IsNull()));
this->SelectDecoder();
}
TYPED_TEST(DecoderSelectorTest, EncryptedStream_DecryptOnly_NoDecoder) {
this->CreateCdmContext(kDecryptOnly);
this->UseEncryptedDecoderConfig();
this->CreateDecoderSelector();
EXPECT_CALL(*this, OnDecoderSelected(kNoDecoder, IsNull()));
this->SelectDecoder();
}
TYPED_TEST(DecoderSelectorTest, EncryptedStream_DecryptOnly_OneClearDecoder) {
this->AddMockDecoder(kDecoder1, kClearOnly);
this->CreateCdmContext(kDecryptOnly);
this->UseEncryptedDecoderConfig();
this->CreateDecoderSelector();
EXPECT_CALL(*this, OnDecoderSelected(kDecoder1, NotNull()));
this->SelectDecoder();
}
TYPED_TEST(DecoderSelectorTest, EncryptedStream_DecryptOnly_InternalFallback) {
this->AddMockDecoder(kDecoder1, kAlwaysFail);
this->AddMockDecoder(kDecoder2, kClearOnly);
this->CreateCdmContext(kDecryptOnly);
this->UseEncryptedDecoderConfig();
this->CreateDecoderSelector();
EXPECT_CALL(*this, OnDecoderSelected(kDecoder2, NotNull()));
this->SelectDecoder();
}
TYPED_TEST(DecoderSelectorTest,
EncryptedStream_DecryptOnly_FinalizeDecoderSelection) {
this->AddMockDecoder(kDecoder1, kClearOnly);
this->AddMockDecoder(kDecoder2, kClearOnly);
this->CreateCdmContext(kDecryptOnly);
this->UseEncryptedDecoderConfig();
this->CreateDecoderSelector();
std::unique_ptr<DecryptingDemuxerStream> saved_dds;
EXPECT_CALL(*this, OnDecoderSelected(kDecoder1, NotNull()))
.WillOnce([&](std::string decoder_name,
std::unique_ptr<DecryptingDemuxerStream> dds) {
saved_dds = std::move(dds);
});
this->SelectDecoder();
this->decoder_selector_->FinalizeDecoderSelection();
// DDS is reused.
EXPECT_CALL(*this, OnDecoderSelected(kDecoder1, IsNull()));
this->SelectDecoder();
}
TYPED_TEST(DecoderSelectorTest, EncryptedStream_DecryptAndDecode) {
this->AddDecryptingDecoder();
this->AddMockDecoder(kDecoder1, kClearOnly);
this->CreateCdmContext(kDecryptAndDecode);
this->UseEncryptedDecoderConfig();
this->CreateDecoderSelector();
#if !defined(OS_ANDROID)
// A DecryptingVideoDecoder will be created and selected. The clear decoder
// should not be touched at all. No DecryptingDemuxerStream should be
// created.
EXPECT_CALL(*this,
OnDecoderSelected(TypeParam::kDecryptingDecoder, IsNull()));
#else
// A DecryptingDemuxerStream will be created. The clear decoder will be
// initialized and returned.
EXPECT_CALL(*this, OnDecoderSelected(kDecoder1, NotNull()));
#endif // !defined(OS_ANDROID)
this->SelectDecoder();
}
TYPED_TEST(DecoderSelectorTest,
EncryptedStream_DecryptAndDecode_ExternalFallback) {
this->AddDecryptingDecoder();
this->AddMockDecoder(kDecoder1, kClearOnly);
this->AddMockDecoder(kDecoder2, kClearOnly);
this->CreateCdmContext(kDecryptAndDecode);
this->UseEncryptedDecoderConfig();
this->CreateDecoderSelector();
#if !defined(OS_ANDROID)
// DecryptingDecoder is selected immediately.
EXPECT_CALL(*this,
OnDecoderSelected(TypeParam::kDecryptingDecoder, IsNull()));
this->SelectDecoder();
#endif // !defined(OS_ANDROID)
// On fallback, a DecryptingDemuxerStream will be created.
std::unique_ptr<DecryptingDemuxerStream> saved_dds;
EXPECT_CALL(*this, OnDecoderSelected(kDecoder1, NotNull()))
.WillOnce([&](std::string decoder_name,
std::unique_ptr<DecryptingDemuxerStream> dds) {
saved_dds = std::move(dds);
});
this->SelectDecoder();
// The DecryptingDemuxerStream should be reused.
EXPECT_CALL(*this, OnDecoderSelected(kDecoder2, IsNull()));
this->SelectDecoder();
}
TYPED_TEST(DecoderSelectorTest, ClearToEncryptedStream_DecryptOnly) {
this->AddMockDecoder(kDecoder1, kClearOnly);
this->CreateCdmContext(kDecryptOnly);
this->UseClearDecoderConfig();
this->CreateDecoderSelector();
EXPECT_CALL(*this, OnDecoderSelected(kDecoder1, IsNull()));
this->SelectDecoder();
this->decoder_selector_->FinalizeDecoderSelection();
this->UseEncryptedDecoderConfig();
EXPECT_CALL(*this, OnDecoderSelected(kDecoder1, NotNull()));
this->SelectDecoder();
}
} // namespace media
......@@ -48,13 +48,12 @@ DecoderStream<StreamType>::DecoderStream(
MediaLog* media_log)
: traits_(std::move(traits)),
task_runner_(task_runner),
create_decoders_cb_(std::move(create_decoders_cb)),
media_log_(media_log),
state_(STATE_UNINITIALIZED),
stream_(nullptr),
cdm_context_(nullptr),
decoder_produced_a_frame_(false),
has_fallen_back_once_on_decode_error_(false),
decoder_selector_(task_runner, std::move(create_decoders_cb), media_log),
decoding_eos_(false),
preparing_output_(false),
pending_decode_requests_(0),
......@@ -110,9 +109,11 @@ void DecoderStream<StreamType>::Initialize(
init_cb_ = std::move(init_cb);
cdm_context_ = cdm_context;
statistics_cb_ = std::move(statistics_cb);
waiting_for_decryption_key_cb_ = std::move(waiting_for_decryption_key_cb);
waiting_for_decryption_key_cb_ = waiting_for_decryption_key_cb;
traits_->OnStreamReset(stream_);
decoder_selector_.Initialize(traits_.get(), stream, cdm_context,
std::move(waiting_for_decryption_key_cb));
state_ = STATE_INITIALIZING;
SelectDecoder();
......@@ -266,23 +267,11 @@ void DecoderStream<StreamType>::SkipPrepareUntil(
template <DemuxerStream::Type StreamType>
void DecoderStream<StreamType>::SelectDecoder() {
// If we are already using DecryptingDemuxerStream (DDS), e.g. during
// fallback, the |stream_| will always be clear. In this case, no need pass in
// the |cdm_context_|. This will also help prevent creating a new DDS on top
// of the current DDS.
CdmContext* cdm_context = decrypting_demuxer_stream_ ? nullptr : cdm_context_;
std::string blacklisted_decoder = decoder_ ? decoder_->GetDisplayName() : "";
decoder_selector_ = std::make_unique<DecoderSelector<StreamType>>(
task_runner_, create_decoders_cb_, media_log_);
decoder_selector_->SelectDecoder(
traits_.get(), stream_, cdm_context, blacklisted_decoder,
decoder_selector_.SelectDecoder(
base::BindRepeating(&DecoderStream<StreamType>::OnDecoderSelected,
weak_factory_.GetWeakPtr()),
base::BindRepeating(&DecoderStream<StreamType>::OnDecodeOutputReady,
fallback_weak_factory_.GetWeakPtr()),
waiting_for_decryption_key_cb_);
fallback_weak_factory_.GetWeakPtr()));
}
template <DemuxerStream::Type StreamType>
......@@ -296,8 +285,6 @@ void DecoderStream<StreamType>::OnDecoderSelected(
DCHECK(state_ == STATE_INITIALIZING || state_ == STATE_REINITIALIZING_DECODER)
<< state_;
decoder_selector_.reset();
if (state_ == STATE_INITIALIZING) {
DCHECK(init_cb_);
DCHECK(!read_cb_);
......@@ -310,6 +297,9 @@ void DecoderStream<StreamType>::OnDecoderSelected(
if (decrypting_demuxer_stream) {
decrypting_demuxer_stream_ = std::move(decrypting_demuxer_stream);
stream_ = decrypting_demuxer_stream_.get();
// Also clear |cdm_context_|, it shouldn't be passed during reinitialize for
// a sream that isn't encrypted.
cdm_context_ = nullptr;
}
if (decoder_change_observer_cb_)
decoder_change_observer_cb_.Run(decoder_.get());
......@@ -463,11 +453,7 @@ void DecoderStream<StreamType>::OnDecodeDone(int buffer_size,
switch (status) {
case DecodeStatus::DECODE_ERROR:
// Only fall back to a new decoder after failing to decode the first
// buffer, and if we have not fallen back before.
if (!decoder_produced_a_frame_ &&
!has_fallen_back_once_on_decode_error_) {
has_fallen_back_once_on_decode_error_ = true;
if (!decoder_produced_a_frame_) {
pending_decode_requests_ = 0;
// Prevent all pending decode requests and outputs from those requests
......@@ -541,10 +527,13 @@ void DecoderStream<StreamType>::OnDecodeOutputReady(
// fallback decoder.
// Note: |fallback_buffers_| might still have buffers, and we will keep
// reading from there before requesting new buffers from |stream_|.
pending_buffers_.clear();
if (!decoder_produced_a_frame_) {
decoder_produced_a_frame_ = true;
decoder_selector_.FinalizeDecoderSelection();
pending_buffers_.clear();
}
// If the frame should be dropped, exit early and decode another frame.
decoder_produced_a_frame_ = true;
if (traits_->OnDecodeDone(output) == PostDecodeAction::DROP)
return;
......@@ -740,7 +729,9 @@ void DecoderStream<StreamType>::ReinitializeDecoder() {
DCHECK_EQ(pending_decode_requests_, 0);
state_ = STATE_REINITIALIZING_DECODER;
// Decoders should not need a new CDM during reinitialization.
// TODO(sandersd): Detect whether a new decoder is required before
// attempting reinitialization.
traits_->InitializeDecoder(
decoder_.get(), traits_->GetDecoderConfig(stream_),
stream_->liveness() == DemuxerStream::LIVENESS_LIVE, cdm_context_,
......
......@@ -216,7 +216,6 @@ class MEDIA_EXPORT DecoderStream {
std::unique_ptr<DecoderStreamTraits<StreamType>> traits_;
scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
CreateDecodersCB create_decoders_cb_;
MediaLog* media_log_;
State state_;
......@@ -237,20 +236,11 @@ class MEDIA_EXPORT DecoderStream {
// Whether |decoder_| has produced a frame yet. Reset on fallback.
bool decoder_produced_a_frame_;
// Whether we have already fallen back once on decode error, used to prevent
// issues like infinite fallback like:
// 1. select decoder 1
// 2. decode error on decoder 1
// 3. black list decoder 1 and select decoder 2
// 4. decode error again on decoder 2
// 5. black list decoder 2 and select decoder 1
// 6. go to (2)
bool has_fallen_back_once_on_decode_error_;
std::unique_ptr<DecryptingDemuxerStream> decrypting_demuxer_stream_;
// Destruct before |decrypting_demuxer_stream_| or |decoder_|.
std::unique_ptr<DecoderSelector<StreamType>> decoder_selector_;
// Note: Holds pointers to |traits_|, |stream_|, |decrypting_demuxer_stream_|,
// and |cdm_context_|.
DecoderSelector<StreamType> decoder_selector_;
ConfigChangeObserverCB config_change_observer_cb_;
DecoderChangeObserverCB decoder_change_observer_cb_;
......
......@@ -48,6 +48,10 @@ void FakeVideoDecoder::EnableEncryptedConfigSupport() {
supports_encrypted_config_ = true;
}
base::WeakPtr<FakeVideoDecoder> FakeVideoDecoder::GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
std::string FakeVideoDecoder::GetDisplayName() const {
return decoder_name_;
}
......
......@@ -57,6 +57,8 @@ class FakeVideoDecoder : public VideoDecoder {
void Reset(const base::Closure& closure) override;
int GetMaxDecodeRequests() const override;
base::WeakPtr<FakeVideoDecoder> GetWeakPtr();
// Holds the next init/decode/reset callback from firing.
void HoldNextInit();
void HoldDecode();
......
// Copyright (c) 2012 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 <stddef.h>
#include <utility>
#include "base/bind.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "build/build_config.h"
#include "media/base/gmock_callback_support.h"
#include "media/base/mock_filters.h"
#include "media/base/test_helpers.h"
#include "media/filters/decoder_selector.h"
#include "media/filters/decrypting_demuxer_stream.h"
#include "testing/gtest/include/gtest/gtest.h"
#if !defined(OS_ANDROID)
#include "media/filters/decrypting_video_decoder.h"
#endif
using ::testing::_;
using ::testing::IsNull;
using ::testing::NiceMock;
using ::testing::NotNull;
using ::testing::Return;
using ::testing::StrictMock;
// Use anonymous namespace here to prevent the actions to be defined multiple
// times across multiple test files. Sadly we can't use static for them.
namespace {
MATCHER(EncryptedConfig, "") {
return arg.is_encrypted();
}
MATCHER(ClearConfig, "") {
return !arg.is_encrypted();
}
} // namespace
namespace media {
const char kDecoder1[] = "Decoder1";
const char kDecoder2[] = "Decoder2";
class VideoDecoderSelectorTest : public ::testing::Test {
public:
enum DecryptorCapability {
kNoCdm, // No CDM. Only possible for clear stream.
kNoDecryptor, // CDM is available but Decryptor is not supported.
kDecryptOnly,
kDecryptAndDecode
};
VideoDecoderSelectorTest()
: traits_(&media_log_),
demuxer_stream_(
new StrictMock<MockDemuxerStream>(DemuxerStream::VIDEO)) {
// |cdm_context_| and |decryptor_| are conditionally created in
// InitializeDecoderSelector().
}
~VideoDecoderSelectorTest() override { base::RunLoop().RunUntilIdle(); }
MOCK_METHOD2(OnDecoderSelected,
void(VideoDecoder*, DecryptingDemuxerStream*));
MOCK_METHOD1(OnDecoderOneSelected, void(DecryptingDemuxerStream*));
MOCK_METHOD1(OnDecoderTwoSelected, void(DecryptingDemuxerStream*));
void MockOnDecoderSelected(std::unique_ptr<VideoDecoder> decoder,
std::unique_ptr<DecryptingDemuxerStream> stream) {
selected_decoder_ = std::move(decoder);
if (!selected_decoder_) {
OnDecoderSelected(selected_decoder_.get(), stream.get());
return;
}
if (selected_decoder_->GetDisplayName() == kDecoder1)
OnDecoderOneSelected(stream.get());
else if (selected_decoder_->GetDisplayName() == kDecoder2)
OnDecoderTwoSelected(stream.get());
else // DecryptingVideoDecoder selected...
OnDecoderSelected(selected_decoder_.get(), stream.get());
}
void UseClearStream() {
demuxer_stream_->set_video_decoder_config(TestVideoConfig::Normal());
}
void UseEncryptedStream() {
demuxer_stream_->set_video_decoder_config(
TestVideoConfig::NormalEncrypted());
}
std::vector<std::unique_ptr<VideoDecoder>> CreateVideoDecodersForTest() {
#if !defined(OS_ANDROID)
all_decoders_.push_back(std::make_unique<DecryptingVideoDecoder>(
message_loop_.task_runner(), &media_log_));
#endif
if (decoder_1_)
testing::Mock::VerifyAndClearExpectations(decoder_1_);
if (decoder_2_)
testing::Mock::VerifyAndClearExpectations(decoder_2_);
if (num_decoders_ > 0) {
decoder_1_ = new StrictMock<MockVideoDecoder>(kDecoder1);
all_decoders_.push_back(base::WrapUnique(decoder_1_));
EXPECT_CALL(*decoder_1_, Initialize(_, _, _, _, _, _))
.Times(testing::AnyNumber())
.WillRepeatedly(
Invoke(this, &VideoDecoderSelectorTest::OnDecoderOneInitialized));
}
if (num_decoders_ > 1) {
decoder_2_ = new StrictMock<MockVideoDecoder>(kDecoder2);
all_decoders_.push_back(base::WrapUnique(decoder_2_));
EXPECT_CALL(*decoder_2_, Initialize(_, _, _, _, _, _))
.Times(testing::AnyNumber())
.WillRepeatedly(
Invoke(this, &VideoDecoderSelectorTest::OnDecoderTwoInitialized));
}
return std::move(all_decoders_);
}
void InitializeDecoderSelector(DecryptorCapability decryptor_capability,
int num_decoders) {
if (decryptor_capability != kNoCdm) {
cdm_context_.reset(new StrictMock<MockCdmContext>());
if (decryptor_capability == kNoDecryptor) {
EXPECT_CALL(*cdm_context_, GetDecryptor())
.WillRepeatedly(Return(nullptr));
} else {
decryptor_.reset(new NiceMock<MockDecryptor>());
EXPECT_CALL(*cdm_context_, GetDecryptor())
.WillRepeatedly(Return(decryptor_.get()));
EXPECT_CALL(*decryptor_, InitializeVideoDecoder(_, _))
.WillRepeatedly(
RunCallback<1>(decryptor_capability == kDecryptAndDecode));
}
}
num_decoders_ = num_decoders;
decoder_selector_.reset(new VideoDecoderSelector(
message_loop_.task_runner(),
base::Bind(&VideoDecoderSelectorTest::CreateVideoDecodersForTest,
base::Unretained(this)),
&media_log_));
}
void SelectDecoder() { SelectDecoderWithBlacklist(""); }
void SelectDecoderWithBlacklist(const std::string& blacklisted_decoder) {
decoder_selector_->SelectDecoder(
&traits_, demuxer_stream_.get(), cdm_context_.get(),
blacklisted_decoder,
base::Bind(&VideoDecoderSelectorTest::MockOnDecoderSelected,
base::Unretained(this)),
base::Bind(&VideoDecoderSelectorTest::FrameReady,
base::Unretained(this)),
base::Bind(&VideoDecoderSelectorTest::OnWaitingForDecryptionKey,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
}
void SelectDecoderAndDestroy() {
SelectDecoder();
EXPECT_CALL(*this, OnDecoderSelected(IsNull(), IsNull()));
decoder_selector_.reset();
base::RunLoop().RunUntilIdle();
}
void FrameReady(const scoped_refptr<VideoFrame>& frame) { NOTREACHED(); }
void OnWaitingForDecryptionKey() { NOTREACHED(); }
MOCK_METHOD6(OnDecoderOneInitialized,
void(const VideoDecoderConfig& config,
bool low_delay,
CdmContext* cdm_context,
const VideoDecoder::InitCB& init_cb,
const VideoDecoder::OutputCB& output_cb,
const VideoDecoder::WaitingForDecryptionKeyCB&
waiting_for_decryption_key_cb));
MOCK_METHOD6(OnDecoderTwoInitialized,
void(const VideoDecoderConfig& config,
bool low_delay,
CdmContext* cdm_context,
const VideoDecoder::InitCB& init_cb,
const VideoDecoder::OutputCB& output_cb,
const VideoDecoder::WaitingForDecryptionKeyCB&
waiting_for_decryption_key_cb));
MediaLog media_log_;
// Stream traits specific to video decoding.
DecoderStreamTraits<DemuxerStream::VIDEO> traits_;
// Declare |decoder_selector_| after |demuxer_stream_| and |decryptor_| since
// |demuxer_stream_| and |decryptor_| should outlive |decoder_selector_|.
std::unique_ptr<StrictMock<MockDemuxerStream>> demuxer_stream_;
std::unique_ptr<StrictMock<MockCdmContext>> cdm_context_;
// Use NiceMock since we don't care about most of calls on the decryptor, e.g.
// RegisterNewKeyCB().
std::unique_ptr<NiceMock<MockDecryptor>> decryptor_;
std::unique_ptr<VideoDecoderSelector> decoder_selector_;
StrictMock<MockVideoDecoder>* decoder_1_ = nullptr;
StrictMock<MockVideoDecoder>* decoder_2_ = nullptr;
std::vector<std::unique_ptr<VideoDecoder>> all_decoders_;
std::unique_ptr<VideoDecoder> selected_decoder_;
int num_decoders_ = 0;
base::MessageLoop message_loop_;
private:
DISALLOW_COPY_AND_ASSIGN(VideoDecoderSelectorTest);
};
// Tests for clear streams. CDM will not be used for clear streams so
// DecryptorCapability doesn't really matter.
TEST_F(VideoDecoderSelectorTest, ClearStream_NoClearDecoder) {
UseClearStream();
// DecoderSelector will not try decrypting decoders for clear stream, even
// if the CDM is capable of doing decrypt and decode.
InitializeDecoderSelector(kDecryptAndDecode, 0);
EXPECT_CALL(*this, OnDecoderSelected(IsNull(), IsNull()));
SelectDecoder();
}
TEST_F(VideoDecoderSelectorTest, ClearStream_OneClearDecoder) {
UseClearStream();
InitializeDecoderSelector(kNoCdm, 1);
EXPECT_CALL(*this, OnDecoderOneInitialized(ClearConfig(), _, _, _, _, _))
.WillOnce(RunCallback<3>(true));
EXPECT_CALL(*this, OnDecoderOneSelected(IsNull()));
SelectDecoder();
}
TEST_F(VideoDecoderSelectorTest, Destroy_ClearStream_OneClearDecoder) {
UseClearStream();
InitializeDecoderSelector(kNoCdm, 1);
EXPECT_CALL(*this, OnDecoderOneInitialized(ClearConfig(), _, _, _, _, _));
SelectDecoderAndDestroy();
}
TEST_F(VideoDecoderSelectorTest, ClearStream_MultipleClearDecoder) {
UseClearStream();
InitializeDecoderSelector(kNoCdm, 2);
EXPECT_CALL(*this, OnDecoderOneInitialized(ClearConfig(), _, _, _, _, _))
.WillOnce(RunCallback<3>(false));
EXPECT_CALL(*this, OnDecoderTwoInitialized(ClearConfig(), _, _, _, _, _))
.WillOnce(RunCallback<3>(true));
EXPECT_CALL(*this, OnDecoderTwoSelected(IsNull()));
SelectDecoder();
}
TEST_F(VideoDecoderSelectorTest, Destroy_ClearStream_MultipleClearDecoder) {
UseClearStream();
InitializeDecoderSelector(kNoCdm, 2);
EXPECT_CALL(*this, OnDecoderOneInitialized(ClearConfig(), _, _, _, _, _))
.WillOnce(RunCallback<3>(false));
EXPECT_CALL(*this, OnDecoderTwoInitialized(ClearConfig(), _, _, _, _, _));
SelectDecoderAndDestroy();
}
TEST_F(VideoDecoderSelectorTest, ClearStream_BlackListedDecoder) {
UseClearStream();
InitializeDecoderSelector(kNoCdm, 2);
// Decoder 1 is blacklisted and will not even be tried.
EXPECT_CALL(*this, OnDecoderTwoInitialized(ClearConfig(), _, _, _, _, _))
.WillOnce(RunCallback<3>(true));
EXPECT_CALL(*this, OnDecoderTwoSelected(IsNull()));
SelectDecoderWithBlacklist(kDecoder1);
}
// Tests for encrypted streams.
TEST_F(VideoDecoderSelectorTest, EncryptedStream_NoDecryptor_OneClearDecoder) {
UseEncryptedStream();
InitializeDecoderSelector(kNoDecryptor, 1);
EXPECT_CALL(*this, OnDecoderOneInitialized(EncryptedConfig(), _, _, _, _, _))
.WillOnce(RunCallback<3>(false));
EXPECT_CALL(*this, OnDecoderSelected(IsNull(), IsNull()));
SelectDecoder();
}
TEST_F(VideoDecoderSelectorTest,
Destroy_EncryptedStream_NoDecryptor_OneClearDecoder) {
UseEncryptedStream();
InitializeDecoderSelector(kNoDecryptor, 1);
EXPECT_CALL(*this, OnDecoderOneInitialized(EncryptedConfig(), _, _, _, _, _));
SelectDecoderAndDestroy();
}
TEST_F(VideoDecoderSelectorTest, EncryptedStream_NoDecryptor_MultipleDecoders) {
UseEncryptedStream();
InitializeDecoderSelector(kNoDecryptor, 2);
EXPECT_CALL(*this, OnDecoderOneInitialized(EncryptedConfig(), _, _, _, _, _))
.WillOnce(RunCallback<3>(false));
EXPECT_CALL(*this, OnDecoderTwoInitialized(EncryptedConfig(), _, _, _, _, _))
.WillOnce(RunCallback<3>(true));
EXPECT_CALL(*this, OnDecoderTwoSelected(IsNull()));
SelectDecoder();
}
TEST_F(VideoDecoderSelectorTest,
Destroy_EncryptedStream_NoDecryptor_MultipleDecoders) {
UseEncryptedStream();
InitializeDecoderSelector(kNoDecryptor, 2);
EXPECT_CALL(*this, OnDecoderOneInitialized(EncryptedConfig(), _, _, _, _, _))
.WillOnce(RunCallback<3>(false));
EXPECT_CALL(*this, OnDecoderTwoInitialized(EncryptedConfig(), _, _, _, _, _));
SelectDecoderAndDestroy();
}
TEST_F(VideoDecoderSelectorTest, EncryptedStream_DecryptOnly_NoDecoder) {
UseEncryptedStream();
InitializeDecoderSelector(kDecryptOnly, 0);
EXPECT_CALL(*this, OnDecoderSelected(IsNull(), IsNull()));
SelectDecoder();
}
TEST_F(VideoDecoderSelectorTest, EncryptedStream_DecryptOnly_OneClearDecoder) {
UseEncryptedStream();
InitializeDecoderSelector(kDecryptOnly, 1);
// Since we use DecryptingDemuxerStream, the decoder will be initialized with
// a clear config.
EXPECT_CALL(*this, OnDecoderOneInitialized(EncryptedConfig(), _, _, _, _, _))
.WillOnce(RunCallback<3>(false));
EXPECT_CALL(*this, OnDecoderOneInitialized(ClearConfig(), _, _, _, _, _))
.WillOnce(RunCallback<3>(true));
EXPECT_CALL(*this, OnDecoderOneSelected(NotNull()));
SelectDecoder();
}
TEST_F(VideoDecoderSelectorTest,
Destroy_EncryptedStream_DecryptOnly_OneClearDecoder) {
UseEncryptedStream();
InitializeDecoderSelector(kDecryptOnly, 1);
EXPECT_CALL(*this, OnDecoderOneInitialized(EncryptedConfig(), _, _, _, _, _))
.WillOnce(RunCallback<3>(false));
EXPECT_CALL(*this, OnDecoderOneInitialized(ClearConfig(), _, _, _, _, _));
SelectDecoderAndDestroy();
}
TEST_F(VideoDecoderSelectorTest,
EncryptedStream_DecryptOnly_MultipleClearDecoder) {
UseEncryptedStream();
InitializeDecoderSelector(kDecryptOnly, 2);
EXPECT_CALL(*this, OnDecoderOneInitialized(EncryptedConfig(), _, _, _, _, _))
.WillOnce(RunCallback<3>(false));
EXPECT_CALL(*this, OnDecoderTwoInitialized(EncryptedConfig(), _, _, _, _, _))
.WillOnce(RunCallback<3>(false));
EXPECT_CALL(*this, OnDecoderOneInitialized(ClearConfig(), _, _, _, _, _))
.WillOnce(RunCallback<3>(false));
EXPECT_CALL(*this, OnDecoderTwoInitialized(ClearConfig(), _, _, _, _, _))
.WillOnce(RunCallback<3>(true));
EXPECT_CALL(*this, OnDecoderTwoSelected(NotNull()));
SelectDecoder();
}
TEST_F(VideoDecoderSelectorTest,
Destroy_EncryptedStream_DecryptOnly_MultipleClearDecoder) {
UseEncryptedStream();
InitializeDecoderSelector(kDecryptOnly, 2);
EXPECT_CALL(*this, OnDecoderOneInitialized(EncryptedConfig(), _, _, _, _, _))
.WillOnce(RunCallback<3>(false));
EXPECT_CALL(*this, OnDecoderTwoInitialized(EncryptedConfig(), _, _, _, _, _))
.WillOnce(RunCallback<3>(false));
EXPECT_CALL(*this, OnDecoderOneInitialized(ClearConfig(), _, _, _, _, _))
.WillOnce(RunCallback<3>(false));
EXPECT_CALL(*this, OnDecoderTwoInitialized(ClearConfig(), _, _, _, _, _));
SelectDecoderAndDestroy();
}
TEST_F(VideoDecoderSelectorTest, EncryptedStream_DecryptAndDecode) {
UseEncryptedStream();
InitializeDecoderSelector(kDecryptAndDecode, 1);
#if !defined(OS_ANDROID)
// A DecryptingVideoDecoder will be created and selected. The clear decoder
// should not be touched at all. No DecryptingDemuxerStream should be
// created.
EXPECT_CALL(*this, OnDecoderSelected(NotNull(), IsNull()));
#else
// A DecryptingDemuxerStream will be created. The clear decoder will be
// initialized and returned.
EXPECT_CALL(*this, OnDecoderOneInitialized(EncryptedConfig(), _, _, _, _, _))
.WillOnce(RunCallback<3>(false));
EXPECT_CALL(*this, OnDecoderOneInitialized(ClearConfig(), _, _, _, _, _))
.WillOnce(RunCallback<3>(true));
EXPECT_CALL(*this, OnDecoderOneSelected(NotNull()));
#endif
SelectDecoder();
}
TEST_F(VideoDecoderSelectorTest,
EncryptedStream_NoDecryptor_BlackListedDecoder) {
UseEncryptedStream();
InitializeDecoderSelector(kNoDecryptor, 2);
EXPECT_CALL(*this, OnDecoderTwoInitialized(EncryptedConfig(), _, _, _, _, _))
.WillOnce(RunCallback<3>(true));
EXPECT_CALL(*this, OnDecoderTwoSelected(IsNull()));
SelectDecoderWithBlacklist(kDecoder1);
}
TEST_F(VideoDecoderSelectorTest,
EncryptedStream_DecryptOnly_BlackListedDecoder) {
UseEncryptedStream();
InitializeDecoderSelector(kDecryptOnly, 2);
// When DecryptingDemuxerStream is chosen, the blacklist is ignored.
EXPECT_CALL(*this, OnDecoderOneInitialized(EncryptedConfig(), _, _, _, _, _))
.WillOnce(RunCallback<3>(false));
// DecoderTwo is blacklisted from the first attempt.
EXPECT_CALL(*this, OnDecoderOneInitialized(ClearConfig(), _, _, _, _, _))
.WillOnce(RunCallback<3>(false));
EXPECT_CALL(*this, OnDecoderTwoInitialized(ClearConfig(), _, _, _, _, _))
.WillOnce(RunCallback<3>(true));
EXPECT_CALL(*this, OnDecoderTwoSelected(NotNull()));
SelectDecoderWithBlacklist(kDecoder2);
}
TEST_F(VideoDecoderSelectorTest,
EncryptedStream_DecryptAndDecode_BlackListedDecoder) {
UseEncryptedStream();
InitializeDecoderSelector(kDecryptAndDecode, 2);
// DecryptingVideoDecoder is blacklisted so we'll fallback to use
// DecryptingDemuxerStream to do decrypt-only.
EXPECT_CALL(*this, OnDecoderOneInitialized(EncryptedConfig(), _, _, _, _, _))
.WillOnce(RunCallback<3>(false));
EXPECT_CALL(*this, OnDecoderTwoInitialized(EncryptedConfig(), _, _, _, _, _))
.WillOnce(RunCallback<3>(false));
EXPECT_CALL(*this, OnDecoderOneInitialized(ClearConfig(), _, _, _, _, _))
.WillOnce(RunCallback<3>(false));
EXPECT_CALL(*this, OnDecoderTwoInitialized(ClearConfig(), _, _, _, _, _))
.WillOnce(RunCallback<3>(true));
EXPECT_CALL(*this, OnDecoderTwoSelected(NotNull()));
// TODO(xhwang): Avoid the hardcoded string here.
SelectDecoderWithBlacklist("DecryptingVideoDecoder");
}
} // namespace media
......@@ -179,9 +179,8 @@ class VideoFrameStreamTest
if (GetParam().is_encrypted && !GetParam().has_decryptor)
decoder->EnableEncryptedConfigSupport();
// Keep a copy of the raw pointers so we can change the behavior of each
// decoder.
decoders_.push_back(decoder.get());
// Keep a reference so we can change the behavior of each decoder.
decoders_.push_back(decoder->GetWeakPtr());
decoders.push_back(std::move(decoder));
}
......@@ -208,12 +207,20 @@ class VideoFrameStreamTest
// |decoder_indices|.
void FailDecoderInitOnSelection(const std::vector<int>& decoder_indices) {
decoder_indices_to_fail_init_ = decoder_indices;
for (int i : decoder_indices) {
if (!decoders_.empty() && decoders_[i] && decoders_[i].get() != decoder_)
decoders_[i]->SimulateFailureToInit();
}
}
// On next decoder selection, hold initialization on decoders specified by
// |decoder_indices|.
void HoldDecoderInitOnSelection(const std::vector<int>& decoder_indices) {
decoder_indices_to_hold_init_ = decoder_indices;
for (int i : decoder_indices) {
if (!decoders_.empty() && decoders_[i] && decoders_[i].get() != decoder_)
decoders_[i]->HoldNextInit();
}
}
// After next decoder selection, hold decode on decoders specified by
......@@ -221,6 +228,10 @@ class VideoFrameStreamTest
// may be resumed immediately and it'll be too late to hold decode then.
void HoldDecodeAfterSelection(const std::vector<int>& decoder_indices) {
decoder_indices_to_hold_decode_ = decoder_indices;
for (int i : decoder_indices) {
if (!decoders_.empty() && decoders_[i] && decoders_[i].get() != decoder_)
decoders_[i]->HoldDecode();
}
}
// Updates the |decoder_| currently being used by VideoFrameStream.
......@@ -458,10 +469,10 @@ class VideoFrameStreamTest
// e.g. RegisterNewKeyCB().
std::unique_ptr<NiceMock<MockDecryptor>> decryptor_;
// Raw pointers to the list of decoders to be select from by DecoderSelector.
// References to the list of decoders to be select from by DecoderSelector.
// Three decoders are needed to test that decoder fallback can occur more than
// once on a config change. They are owned by |video_frame_stream_|.
std::vector<FakeVideoDecoder*> decoders_;
std::vector<base::WeakPtr<FakeVideoDecoder>> decoders_;
std::vector<int> decoder_indices_to_fail_init_;
std::vector<int> decoder_indices_to_hold_init_;
......@@ -968,25 +979,31 @@ TEST_P(VideoFrameStreamTest, FallbackDecoder_DoesReinitializeStompPendingRead) {
ASSERT_GT(decoder_->total_bytes_decoded(), first_decoded_bytes);
}
TEST_P(VideoFrameStreamTest, FallbackDecoder_DecodeErrorTwice) {
TEST_P(VideoFrameStreamTest, FallbackDecoder_DecodeErrorRepeated) {
Initialize();
// Hold other decoders to simuate errors.
HoldDecodeAfterSelection({1, 2});
// Simulate decode error to trigger the fallback path.
decoder_->SimulateError();
// Decoder 0 should be blacklisted and never tried. Hold decode on decoder 1
// and simulate decode error again.
HoldDecodeAfterSelection({1});
ReadOneFrame();
base::RunLoop().RunUntilIdle();
// Expect decoder 1 to be tried.
ASSERT_EQ(GetDecoderName(1), decoder_->GetDisplayName());
decoder_->SimulateError();
decoder_->SatisfyDecode();
base::RunLoop().RunUntilIdle();
// Only one fallback is allowed so we are not falling back to other decoders.
ASSERT_EQ(GetDecoderName(1), decoder_->GetDisplayName());
// Then decoder 2.
ASSERT_EQ(GetDecoderName(2), decoder_->GetDisplayName());
decoder_->SimulateError();
base::RunLoop().RunUntilIdle();
// No decoders left, expect failure.
EXPECT_EQ(decoder_, nullptr);
EXPECT_FALSE(pending_read_);
ASSERT_EQ(VideoFrameStream::DECODE_ERROR, last_read_status_);
EXPECT_EQ(VideoFrameStream::DECODE_ERROR, last_read_status_);
}
// This tests verifies that we properly fallback to a new decoder if the first
......@@ -1029,40 +1046,46 @@ TEST_P(VideoFrameStreamTest,
ReadOneFrame();
// Verify that fallback happened.
EXPECT_EQ(GetDecoderName(1), decoder_->GetDisplayName());
EXPECT_EQ(GetDecoderName(0), decoder_->GetDisplayName());
EXPECT_FALSE(pending_read_);
EXPECT_EQ(VideoFrameStream::OK, last_read_status_);
EXPECT_GT(decoder_->total_bytes_decoded(), 0);
}
TEST_P(VideoFrameStreamTest,
FallbackDecoder_DecodeErrorTwice_AfterReinitialization) {
FallbackDecoder_DecodeErrorRepeated_AfterReinitialization) {
Initialize();
// Simulate decode error to trigger the fallback path.
// Simulate decode error to trigger fallback.
decoder_->SimulateError();
ReadOneFrame();
ASSERT_EQ(GetDecoderName(1), decoder_->GetDisplayName());
base::RunLoop().RunUntilIdle();
// Simulate reinitialize error of decoder 1.
ASSERT_EQ(GetDecoderName(1), decoder_->GetDisplayName());
decoder_->SimulateFailureToInit();
// Decoder 0 should be selected again. Simulate immediate decode error after
// reinitialization.
HoldDecodeAfterSelection({0});
HoldDecodeAfterSelection({0, 1, 2});
ReadUntilDecoderReinitialized();
// Decoder 0 should be selected again.
ASSERT_EQ(GetDecoderName(0), decoder_->GetDisplayName());
decoder_->SimulateError();
decoder_->SatisfyDecode();
base::RunLoop().RunUntilIdle();
// VideoDecoderStream has produced video frames, so we are not trying fallback
// again.
// TODO(xhwang): Revisit this behavior, e.g. always try to fallback if a newly
// selected decoder has not produced any video frames before.
ASSERT_EQ(GetDecoderName(0), decoder_->GetDisplayName());
// Decoder 1.
ASSERT_EQ(GetDecoderName(1), decoder_->GetDisplayName());
decoder_->SimulateError();
base::RunLoop().RunUntilIdle();
// Decoder 2.
ASSERT_EQ(GetDecoderName(2), decoder_->GetDisplayName());
decoder_->SimulateError();
base::RunLoop().RunUntilIdle();
// No decoders left.
EXPECT_EQ(decoder_, nullptr);
EXPECT_FALSE(pending_read_);
ASSERT_EQ(VideoFrameStream::DECODE_ERROR, last_read_status_);
EXPECT_EQ(VideoFrameStream::DECODE_ERROR, last_read_status_);
}
TEST_P(VideoFrameStreamTest, FallbackDecoder_ConfigChangeClearsPendingBuffers) {
......@@ -1232,8 +1255,8 @@ TEST_P(VideoFrameStreamTest, FallbackDecoder_SelectedOnInitThenDecodeErrors) {
decoder_->SimulateError();
base::RunLoop().RunUntilIdle();
// |video_frame_stream_| should have fallen back to decoder 0.
ASSERT_EQ(GetDecoderName(0), decoder_->GetDisplayName());
// |video_frame_stream_| should have fallen back to decoder 2.
ASSERT_EQ(GetDecoderName(2), decoder_->GetDisplayName());
ASSERT_FALSE(pending_read_);
ASSERT_EQ(VideoFrameStream::OK, last_read_status_);
......@@ -1298,7 +1321,8 @@ TEST_P(VideoFrameStreamTest, ReinitializeFailure_Once) {
Initialize();
decoder_->SimulateFailureToInit();
ReadUntilDecoderReinitialized();
ASSERT_EQ(GetDecoderName(1), decoder_->GetDisplayName());
// Should have fallen back to a new instance of decoder 0.
ASSERT_EQ(GetDecoderName(0), decoder_->GetDisplayName());
ReadAllFrames();
ASSERT_GT(decoder_->total_bytes_decoded(), 0);
}
......@@ -1306,14 +1330,15 @@ TEST_P(VideoFrameStreamTest, ReinitializeFailure_Once) {
TEST_P(VideoFrameStreamTest, ReinitializeFailure_Twice) {
Initialize();
// Trigger reinitialization error, and fallback to decoder 1.
// Trigger reinitialization error, and fallback to a new instance.
decoder_->SimulateFailureToInit();
ReadUntilDecoderReinitialized();
ASSERT_EQ(GetDecoderName(1), decoder_->GetDisplayName());
ASSERT_EQ(GetDecoderName(0), decoder_->GetDisplayName());
ReadOneFrame();
// Trigger reinitialization error again, and fallback back to decoder 0.
// Trigger reinitialization error again. Since a frame was output, this will
// be a new instance of decoder 0 again.
decoder_->SimulateFailureToInit();
ReadUntilDecoderReinitialized();
ASSERT_EQ(GetDecoderName(0), decoder_->GetDisplayName());
......@@ -1323,11 +1348,11 @@ TEST_P(VideoFrameStreamTest, ReinitializeFailure_Twice) {
TEST_P(VideoFrameStreamTest, ReinitializeFailure_OneUnsupportedDecoder) {
Initialize();
// The current decoder will fail to reinitialize and will be blacklisted.
// The current decoder will fail to reinitialize.
decoder_->SimulateFailureToInit();
// Decoder 1 will also fail to initialize on decoder selection.
FailDecoderInitOnSelection({1});
FailDecoderInitOnSelection({0, 1});
ReadUntilDecoderReinitialized();
......@@ -1340,11 +1365,12 @@ TEST_P(VideoFrameStreamTest, ReinitializeFailure_OneUnsupportedDecoder) {
TEST_P(VideoFrameStreamTest, ReinitializeFailure_NoSupportedDecoder) {
Initialize();
// The current decoder will fail to reinitialize and will be blacklisted.
// The current decoder will fail to reinitialize, triggering decoder
// selection.
decoder_->SimulateFailureToInit();
// Decoder 1 and 2 will also fail to initialize on decoder selection.
FailDecoderInitOnSelection({1, 2});
// All of the decoders will fail in decoder selection.
FailDecoderInitOnSelection({0, 1, 2});
ReadUntilDecoderReinitialized();
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment