Commit 1492be9e authored by xhwang's avatar xhwang Committed by Commit bot

media: Support better decoder switching

Today on reinitialization error, or decode error of the first buffer,
DecoderStream will fall back to decoders left in the DecoderSelector.
Since we will have less and less decoders in the DecoderSelector, the
ability to switch decoders is limited.

This CL updates DecoderSelector so that it takes a callback to create
a list of decoders, instead of taking the list of decoders directly.
This allows the DecoderSelector to select a decoder that has been
tried or selected before, such that upon decoder reinitialization error
(e.g. switching from clear to encrypted config), or upon decode error of
the first buffer, we always have the full list of decoders to select
from.

Two mechanisms are added to avoid trying the same failing decoder again:

1. Blacklist

When SelectDecoder() is called due to reinitialization failure, or
decoding failure of the first buffer, the existing decoder is listed as
the "blacklisted decoder" such that DecoderSelector will not even try
it.

There is one exception though. When DecryptingDemuxerStream is selected,
since the input steam is changed (encrypted -> clear), the blacklisted
decoder is ignored. For example, FFmpegVideoDecoder is slected first for
a clear stream. Then on a config change, the stream becomes encrypted so
FFmpegVideoDecoder fails to reinitialize and we need to select a new
decoder. After DecryptingDemuxerStream is selected, we should still be
able to use FFmpegVideoDecoder to decode the decrypted stream.

2. Fall back at most once

If fallback has already happened and decode of the first buffer failed
again, we don't try to fallback again. This is to avoid the infinite
loop of "select decoder 1 -> decode error -> select decoder 2 -> decode
error -> select decoder 1".

BUG=695595
TEST=Updated/Added unittests.

Review-Url: https://codereview.chromium.org/2837613004
Cr-Commit-Position: refs/heads/master@{#469579}
parent 6815aefe
......@@ -43,7 +43,11 @@ class MEDIA_EXPORT AudioDecoder {
// depends on |this|.
virtual ~AudioDecoder();
// Returns the name of the decoder for logging purpose.
// Returns the name of the decoder for logging and decoder selection purposes.
// This name should be available immediately after construction (e.g. before
// Initialize() is called). It should also be stable in the sense that the
// name does not change across multiple constructions.
// TODO(xhwang): Rename this method since the name is not only for display.
virtual std::string GetDisplayName() const = 0;
// Initializes an AudioDecoder with |config|, executing the |init_cb| upon
......
......@@ -88,9 +88,7 @@ class MEDIA_EXPORT AudioDecoderConfig {
}
// Sets the config to be encrypted or not encrypted manually. This can be
// useful for decryptors that decrypts an encrypted stream to a clear stream,
// or for decoder selectors that wants to select decrypting decoders instead
// of clear decoders.
// useful for decryptors that decrypts an encrypted stream to a clear stream.
void SetIsEncrypted(bool is_encrypted);
private:
......
......@@ -88,24 +88,26 @@ VideoRotation MockDemuxerStream::video_rotation() {
return VIDEO_ROTATION_0;
}
std::string MockVideoDecoder::GetDisplayName() const {
return "MockVideoDecoder";
}
MockVideoDecoder::MockVideoDecoder() {
MockVideoDecoder::MockVideoDecoder(const std::string& decoder_name)
: decoder_name_(decoder_name) {
ON_CALL(*this, CanReadWithoutStalling()).WillByDefault(Return(true));
}
std::string MockAudioDecoder::GetDisplayName() const {
return "MockAudioDecoder";
}
MockVideoDecoder::~MockVideoDecoder() {}
MockAudioDecoder::MockAudioDecoder() {}
std::string MockVideoDecoder::GetDisplayName() const {
return decoder_name_;
}
MockAudioDecoder::MockAudioDecoder(const std::string& decoder_name)
: decoder_name_(decoder_name) {}
MockAudioDecoder::~MockAudioDecoder() {}
std::string MockAudioDecoder::GetDisplayName() const {
return decoder_name_;
}
MockRendererClient::MockRendererClient() {}
MockRendererClient::~MockRendererClient() {}
......
......@@ -179,7 +179,8 @@ class MockDemuxerStream : public DemuxerStream {
class MockVideoDecoder : public VideoDecoder {
public:
MockVideoDecoder();
explicit MockVideoDecoder(
const std::string& decoder_name = "MockVideoDecoder");
virtual ~MockVideoDecoder();
// VideoDecoder implementation.
......@@ -197,12 +198,14 @@ class MockVideoDecoder : public VideoDecoder {
MOCK_CONST_METHOD0(CanReadWithoutStalling, bool());
private:
std::string decoder_name_;
DISALLOW_COPY_AND_ASSIGN(MockVideoDecoder);
};
class MockAudioDecoder : public AudioDecoder {
public:
MockAudioDecoder();
explicit MockAudioDecoder(
const std::string& decoder_name = "MockAudioDecoder");
virtual ~MockAudioDecoder();
// AudioDecoder implementation.
......@@ -218,6 +221,7 @@ class MockAudioDecoder : public AudioDecoder {
MOCK_METHOD1(Reset, void(const base::Closure&));
private:
std::string decoder_name_;
DISALLOW_COPY_AND_ASSIGN(MockAudioDecoder);
};
......
......@@ -44,7 +44,10 @@ class MEDIA_EXPORT VideoDecoder {
// depends on |this|.
virtual ~VideoDecoder();
// Returns the name of the decoder for logging purpose.
// Returns the name of the decoder for logging and decoder selection purposes.
// This name should be available immediately after construction (e.g. before
// Initialize() is called). It should also be stable in the sense that the
// name does not change across multiple constructions.
virtual std::string GetDisplayName() const = 0;
// Initializes a VideoDecoder with the given |config|, executing the
......
......@@ -120,9 +120,7 @@ class MEDIA_EXPORT VideoDecoderConfig {
base::Optional<HDRMetadata> hdr_metadata() const;
// Sets the config to be encrypted or not encrypted manually. This can be
// useful for decryptors that decrypts an encrypted stream to a clear stream,
// or for decoder selectors that wants to select decrypting decoders instead
// of clear decoders.
// useful for decryptors that decrypts an encrypted stream to a clear stream.
void SetIsEncrypted(bool is_encrypted);
private:
......
......@@ -42,6 +42,9 @@ MATCHER(ClearConfig, "") {
namespace media {
const char kDecoder1[] = "Decoder1";
const char kDecoder2[] = "Decoder2";
class AudioDecoderSelectorTest : public ::testing::Test {
public:
enum DecryptorCapability {
......@@ -55,8 +58,8 @@ class AudioDecoderSelectorTest : public ::testing::Test {
: traits_(&media_log_),
demuxer_stream_(
new StrictMock<MockDemuxerStream>(DemuxerStream::AUDIO)),
decoder_1_(new StrictMock<MockAudioDecoder>()),
decoder_2_(new StrictMock<MockAudioDecoder>()) {
decoder_1_(new StrictMock<MockAudioDecoder>(kDecoder1)),
decoder_2_(new StrictMock<MockAudioDecoder>(kDecoder2)) {
all_decoders_.push_back(decoder_1_);
all_decoders_.push_back(decoder_2_);
// |cdm_context_| and |decryptor_| are conditionally created in
......@@ -88,6 +91,10 @@ class AudioDecoderSelectorTest : public ::testing::Test {
demuxer_stream_->set_audio_decoder_config(encrypted_audio_config);
}
ScopedVector<AudioDecoder> CreateVideoDecodersForTest() {
return std::move(all_decoders_);
}
void InitializeDecoderSelector(DecryptorCapability decryptor_capability,
int num_decoders) {
if (decryptor_capability != kNoCdm) {
......@@ -111,12 +118,16 @@ class AudioDecoderSelectorTest : public ::testing::Test {
all_decoders_.begin() + num_decoders, all_decoders_.end());
decoder_selector_.reset(new AudioDecoderSelector(
message_loop_.task_runner(), std::move(all_decoders_), &media_log_));
message_loop_.task_runner(),
base::Bind(&AudioDecoderSelectorTest::CreateVideoDecodersForTest,
base::Unretained(this)),
&media_log_));
}
void SelectDecoder() {
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),
......@@ -124,6 +135,8 @@ class AudioDecoderSelectorTest : public ::testing::Test {
base::RunLoop().RunUntilIdle();
}
void SelectDecoder() { SelectDecoderWithBlacklist(""); }
void SelectDecoderAndDestroy() {
SelectDecoder();
......@@ -168,18 +181,22 @@ class AudioDecoderSelectorTest : public ::testing::Test {
DISALLOW_COPY_AND_ASSIGN(AudioDecoderSelectorTest);
};
// Tests for clear streams.
// Tests for clear streams. The CDM will not be used for clear streams so
// DecryptorCapability doesn't really matter.
TEST_F(AudioDecoderSelectorTest, ClearStream_NoDecryptor_NoClearDecoder) {
TEST_F(AudioDecoderSelectorTest, ClearStream_NoClearDecoder) {
UseClearStream();
InitializeDecoderSelector(kNoDecryptor, 0);
// 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_NoCdm_OneClearDecoder) {
TEST_F(AudioDecoderSelectorTest, ClearStream_OneClearDecoder) {
UseClearStream();
InitializeDecoderSelector(kNoCdm, 1);
......@@ -190,7 +207,7 @@ TEST_F(AudioDecoderSelectorTest, ClearStream_NoCdm_OneClearDecoder) {
SelectDecoder();
}
TEST_F(AudioDecoderSelectorTest, Destroy_ClearStream_NoCdm_OneClearDecoder) {
TEST_F(AudioDecoderSelectorTest, Destroy_ClearStream_OneClearDecoder) {
UseClearStream();
InitializeDecoderSelector(kNoCdm, 1);
......@@ -199,7 +216,7 @@ TEST_F(AudioDecoderSelectorTest, Destroy_ClearStream_NoCdm_OneClearDecoder) {
SelectDecoderAndDestroy();
}
TEST_F(AudioDecoderSelectorTest, ClearStream_NoCdm_MultipleClearDecoder) {
TEST_F(AudioDecoderSelectorTest, ClearStream_MultipleClearDecoder) {
UseClearStream();
InitializeDecoderSelector(kNoCdm, 2);
......@@ -212,8 +229,7 @@ TEST_F(AudioDecoderSelectorTest, ClearStream_NoCdm_MultipleClearDecoder) {
SelectDecoder();
}
TEST_F(AudioDecoderSelectorTest,
Destroy_ClearStream_NoCdm_MultipleClearDecoder) {
TEST_F(AudioDecoderSelectorTest, Destroy_ClearStream_MultipleClearDecoder) {
UseClearStream();
InitializeDecoderSelector(kNoCdm, 2);
......@@ -224,90 +240,16 @@ TEST_F(AudioDecoderSelectorTest,
SelectDecoderAndDestroy();
}
TEST_F(AudioDecoderSelectorTest, ClearStream_NoDecryptor_OneClearDecoder) {
TEST_F(AudioDecoderSelectorTest, ClearStream_BlackListedDecoder) {
UseClearStream();
InitializeDecoderSelector(kNoDecryptor, 1);
EXPECT_CALL(*decoder_1_, Initialize(EncryptedConfig(), _, _, _))
.WillOnce(RunCallback<2>(false));
EXPECT_CALL(*this, OnDecoderSelected(IsNull(), IsNull()));
SelectDecoder();
}
TEST_F(AudioDecoderSelectorTest,
Destroy_ClearStream_NoDecryptor_OneClearDecoder) {
UseClearStream();
InitializeDecoderSelector(kNoDecryptor, 1);
EXPECT_CALL(*decoder_1_, Initialize(EncryptedConfig(), _, _, _));
SelectDecoderAndDestroy();
}
TEST_F(AudioDecoderSelectorTest, ClearStream_NoDecryptor_MultipleClearDecoder) {
UseClearStream();
InitializeDecoderSelector(kNoDecryptor, 2);
InitializeDecoderSelector(kNoCdm, 2);
EXPECT_CALL(*decoder_1_, Initialize(EncryptedConfig(), _, _, _))
.WillOnce(RunCallback<2>(false));
EXPECT_CALL(*decoder_2_, Initialize(EncryptedConfig(), _, _, _))
// Decoder 1 is blacklisted and will not even be tried.
EXPECT_CALL(*decoder_2_, Initialize(ClearConfig(), _, _, _))
.WillOnce(RunCallback<2>(true));
EXPECT_CALL(*this, OnDecoderSelected(decoder_2_, IsNull()));
SelectDecoder();
}
TEST_F(AudioDecoderSelectorTest,
Destroy_ClearStream_NoDecryptor_MultipleClearDecoder) {
UseClearStream();
InitializeDecoderSelector(kNoDecryptor, 2);
EXPECT_CALL(*decoder_1_, Initialize(EncryptedConfig(), _, _, _))
.WillOnce(RunCallback<2>(false));
EXPECT_CALL(*decoder_2_, Initialize(EncryptedConfig(), _, _, _));
SelectDecoderAndDestroy();
}
TEST_F(AudioDecoderSelectorTest, ClearStream_DecryptOnly) {
UseClearStream();
InitializeDecoderSelector(kDecryptOnly, 1);
EXPECT_CALL(*decoder_1_, Initialize(ClearConfig(), _, _, _))
.WillOnce(RunCallback<2>(true));
EXPECT_CALL(*this, OnDecoderSelected(decoder_1_, NotNull()));
SelectDecoder();
}
TEST_F(AudioDecoderSelectorTest, Destroy_ClearStream_DecryptOnly) {
UseClearStream();
InitializeDecoderSelector(kDecryptOnly, 1);
EXPECT_CALL(*decoder_1_, Initialize(ClearConfig(), _, _, _));
SelectDecoderAndDestroy();
}
TEST_F(AudioDecoderSelectorTest, ClearStream_DecryptAndDecode) {
UseClearStream();
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(*decoder_1_, Initialize(ClearConfig(), _, _, _))
.WillOnce(RunCallback<2>(true));
EXPECT_CALL(*this, OnDecoderSelected(NotNull(), NotNull()));
#endif
SelectDecoder();
SelectDecoderWithBlacklist(kDecoder1);
}
// Tests for encrypted streams.
......@@ -434,4 +376,48 @@ TEST_F(AudioDecoderSelectorTest, EncryptedStream_DecryptAndDecode) {
SelectDecoder();
}
TEST_F(AudioDecoderSelectorTest,
EncryptedStream_NoDecryptor_BlackListedDecoder) {
UseEncryptedStream();
InitializeDecoderSelector(kNoDecryptor, 2);
EXPECT_CALL(*decoder_2_, Initialize(EncryptedConfig(), _, _, _))
.WillOnce(RunCallback<2>(true));
EXPECT_CALL(*this, OnDecoderSelected(decoder_2_, IsNull()));
SelectDecoderWithBlacklist(kDecoder1);
}
TEST_F(AudioDecoderSelectorTest,
EncryptedStream_DecryptOnly_BlackListedDecoder) {
UseEncryptedStream();
InitializeDecoderSelector(kDecryptOnly, 2);
// When DecryptingDemuxerStream is chosen, the blacklist is ignored.
EXPECT_CALL(*decoder_1_, Initialize(ClearConfig(), _, _, _))
.WillOnce(RunCallback<2>(false));
EXPECT_CALL(*decoder_2_, Initialize(ClearConfig(), _, _, _))
.WillOnce(RunCallback<2>(true));
EXPECT_CALL(*this, OnDecoderSelected(decoder_2_, 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(*decoder_1_, Initialize(ClearConfig(), _, _, _))
.WillOnce(RunCallback<2>(false));
EXPECT_CALL(*decoder_2_, Initialize(ClearConfig(), _, _, _))
.WillOnce(RunCallback<2>(true));
EXPECT_CALL(*this, OnDecoderSelected(decoder_2_, NotNull()));
// TODO(xhwang): Avoid the hardcoded string here.
SelectDecoderWithBlacklist("DecryptingAudioDecoder");
}
} // namespace media
......@@ -43,10 +43,10 @@ static bool HasValidStreamConfig(DemuxerStream* stream) {
template <DemuxerStream::Type StreamType>
DecoderSelector<StreamType>::DecoderSelector(
const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
ScopedVector<Decoder> decoders,
CreateDecodersCB create_decoders_cb,
MediaLog* media_log)
: task_runner_(task_runner),
decoders_(std::move(decoders)),
create_decoders_cb_(std::move(create_decoders_cb)),
media_log_(media_log),
input_stream_(nullptr),
weak_ptr_factory_(this) {}
......@@ -68,18 +68,17 @@ void DecoderSelector<StreamType>::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) {
DVLOG(2) << __func__;
DVLOG(2) << __func__ << ": cdm_context=" << cdm_context
<< ", blacklisted_decoder=" << blacklisted_decoder;
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK(traits);
DCHECK(stream);
DCHECK(select_decoder_cb_.is_null());
cdm_context_ = cdm_context;
waiting_for_decryption_key_cb_ = waiting_for_decryption_key_cb;
// Make sure |select_decoder_cb| runs on a different execution stack.
select_decoder_cb_ = BindToCurrentLoop(select_decoder_cb);
......@@ -91,11 +90,20 @@ void DecoderSelector<StreamType>::SelectDecoder(
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_ = StreamTraits::GetDecoderConfig(input_stream_);
// When there is a CDM attached, always try the decrypting decoder or
// demuxer-stream first.
if (cdm_context_) {
if (config_.is_encrypted()) {
DCHECK(cdm_context_);
// TODO(xhwang): This if-defined doesn't make a lot of sense. It should be
// replaced by some better checks.
#if !defined(OS_ANDROID)
InitializeDecryptingDecoder();
#else
......@@ -104,11 +112,6 @@ void DecoderSelector<StreamType>::SelectDecoder(
return;
}
config_ = StreamTraits::GetDecoderConfig(input_stream_);
// If the input stream is encrypted, CdmContext must be non-null.
DCHECK(!config_.is_encrypted());
InitializeDecoder();
}
......@@ -116,9 +119,16 @@ void DecoderSelector<StreamType>::SelectDecoder(
template <DemuxerStream::Type StreamType>
void DecoderSelector<StreamType>::InitializeDecryptingDecoder() {
DVLOG(2) << __func__;
decoder_.reset(new typename StreamTraits::DecryptingDecoderType(
task_runner_, media_log_, waiting_for_decryption_key_cb_));
if (decoder_->GetDisplayName() == blacklisted_decoder_) {
DVLOG(1) << __func__ << ": Decrypting decoder is blacklisted.";
DecryptingDecoderInitDone(false);
return;
}
traits_->InitializeDecoder(
decoder_.get(), StreamTraits::GetDecoderConfig(input_stream_),
input_stream_->liveness() == DemuxerStream::LIVENESS_LIVE, cdm_context_,
......@@ -134,6 +144,7 @@ void DecoderSelector<StreamType>::DecryptingDecoderInitDone(bool success) {
if (success) {
DVLOG(1) << __func__ << ": " << decoder_->GetDisplayName() << " selected.";
decoders_.clear();
base::ResetAndReturn(&select_decoder_cb_)
.Run(std::move(decoder_), std::unique_ptr<DecryptingDemuxerStream>());
return;
......@@ -177,11 +188,7 @@ void DecoderSelector<StreamType>::DecryptingDemuxerStreamInitDone(
DCHECK(!config_.is_encrypted());
} else {
decrypted_stream_.reset();
config_ = StreamTraits::GetDecoderConfig(input_stream_);
// Prefer decrypting decoder by using an encrypted config.
if (!config_.is_encrypted())
config_.SetIsEncrypted(true);
DCHECK(config_.is_encrypted());
}
InitializeDecoder();
......@@ -193,14 +200,24 @@ void DecoderSelector<StreamType>::InitializeDecoder() {
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK(!decoder_);
if (decoders_.empty()) {
// Select the next non-blacklisted decoder.
while (!decoders_.empty()) {
std::unique_ptr<Decoder> decoder(decoders_.front());
decoders_.weak_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_) {
ReturnNullDecoder();
return;
}
decoder_.reset(decoders_.front());
decoders_.weak_erase(decoders_.begin());
traits_->InitializeDecoder(
decoder_.get(), config_,
input_stream_->liveness() == DemuxerStream::LIVENESS_LIVE, cdm_context_,
......@@ -224,6 +241,7 @@ void DecoderSelector<StreamType>::DecoderInitDone(bool success) {
<< " selected. DecryptingDemuxerStream "
<< (decrypted_stream_ ? "also" : "not") << " selected.";
decoders_.clear();
base::ResetAndReturn(&select_decoder_cb_)
.Run(std::move(decoder_), std::move(decrypted_stream_));
}
......@@ -232,6 +250,7 @@ template <DemuxerStream::Type StreamType>
void DecoderSelector<StreamType>::ReturnNullDecoder() {
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>());
......
......@@ -39,6 +39,11 @@ class MEDIA_EXPORT DecoderSelector {
typedef typename StreamTraits::DecoderType Decoder;
typedef typename StreamTraits::DecoderConfigType DecoderConfig;
// Callback to create a list of decoders to select from.
// TODO(xhwang): Use a DecoderFactory to create decoders one by one as needed,
// instead of creating a list of decoders all at once.
using CreateDecodersCB = base::RepeatingCallback<ScopedVector<Decoder>()>;
// Indicates completion of Decoder selection.
// - First parameter: The initialized Decoder. If it's set to NULL, then
// Decoder initialization failed.
......@@ -48,18 +53,17 @@ class MEDIA_EXPORT DecoderSelector {
// Note: The caller owns selected Decoder and DecryptingDemuxerStream.
// The caller should call DecryptingDemuxerStream::Reset() before
// calling Decoder::Reset() to release any pending decryption or read.
typedef base::Callback<void(std::unique_ptr<Decoder>,
std::unique_ptr<DecryptingDemuxerStream>)>
SelectDecoderCB;
using SelectDecoderCB =
base::Callback<void(std::unique_ptr<Decoder>,
std::unique_ptr<DecryptingDemuxerStream>)>;
// |decoders| contains the Decoders to use when initializing.
DecoderSelector(
const scoped_refptr<base::SingleThreadTaskRunner>& message_loop,
ScopedVector<Decoder> decoders,
const 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.
// null and null immediately if it's pending.
~DecoderSelector();
// Initializes and selects the first Decoder that can decode the |stream|.
......@@ -67,13 +71,20 @@ class MEDIA_EXPORT DecoderSelector {
// the |select_decoder_cb|.
// Notes:
// 1. This must not be called again before |select_decoder_cb| is run.
// 2. Decoders that fail to initialize will be deleted. Future calls will
// select from the decoders following the decoder that was last returned.
// 3. |cdm_context| is optional. If |cdm_context| is
// null, no CDM will be available to perform decryption.
// 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);
......@@ -90,7 +101,7 @@ class MEDIA_EXPORT DecoderSelector {
void ReturnNullDecoder();
scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
ScopedVector<Decoder> decoders_;
CreateDecodersCB create_decoders_cb_;
MediaLog* media_log_;
StreamTraits* traits_;
......@@ -100,10 +111,13 @@ class MEDIA_EXPORT DecoderSelector {
DemuxerStream* input_stream_;
CdmContext* cdm_context_;
std::string blacklisted_decoder_;
SelectDecoderCB select_decoder_cb_;
typename Decoder::OutputCB output_cb_;
base::Closure waiting_for_decryption_key_cb_;
ScopedVector<Decoder> decoders_;
std::unique_ptr<Decoder> decoder_;
std::unique_ptr<DecryptingDemuxerStream> decrypted_stream_;
......
......@@ -45,18 +45,17 @@ const char* GetTraceString<DemuxerStream::AUDIO>() {
template <DemuxerStream::Type StreamType>
DecoderStream<StreamType>::DecoderStream(
const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
ScopedVector<Decoder> decoders,
CreateDecodersCB create_decoders_cb,
MediaLog* media_log)
: traits_(media_log),
task_runner_(task_runner),
create_decoders_cb_(std::move(create_decoders_cb)),
media_log_(media_log),
state_(STATE_UNINITIALIZED),
stream_(NULL),
cdm_context_(nullptr),
decoder_selector_(new DecoderSelector<StreamType>(task_runner,
std::move(decoders),
media_log)),
decoder_produced_a_frame_(false),
has_fallen_back_once_on_decode_error_(false),
decoding_eos_(false),
pending_decode_requests_(0),
duration_tracker_(8),
......@@ -256,9 +255,13 @@ void DecoderStream<StreamType>::SelectDecoder() {
// 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_.reset(new DecoderSelector<StreamType>(
task_runner_, create_decoders_cb_, media_log_));
decoder_selector_->SelectDecoder(
&traits_, stream_, cdm_context,
&traits_, stream_, cdm_context, blacklisted_decoder,
base::Bind(&DecoderStream<StreamType>::OnDecoderSelected,
weak_factory_.GetWeakPtr()),
base::Bind(&DecoderStream<StreamType>::OnDecodeOutputReady,
......@@ -276,6 +279,9 @@ void DecoderStream<StreamType>::OnDecoderSelected(
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK(state_ == STATE_INITIALIZING || state_ == STATE_REINITIALIZING_DECODER)
<< state_;
decoder_selector_.reset();
if (state_ == STATE_INITIALIZING) {
DCHECK(!init_cb_.is_null());
DCHECK(read_cb_.is_null());
......@@ -431,7 +437,11 @@ void DecoderStream<StreamType>::OnDecodeDone(int buffer_size,
switch (status) {
case DecodeStatus::DECODE_ERROR:
if (!decoder_produced_a_frame_) {
// 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;
pending_decode_requests_ = 0;
// Prevent all pending decode requests and outputs from those requests
......@@ -444,6 +454,7 @@ void DecoderStream<StreamType>::OnDecodeDone(int buffer_size,
SelectDecoder();
return;
}
FUNCTION_DVLOG(1) << ": Decode error!";
state_ = STATE_ERROR;
MEDIA_LOG(ERROR, media_log_) << GetStreamTypeString() << " decode error";
......@@ -503,7 +514,7 @@ void DecoderStream<StreamType>::OnDecodeOutputReady(
decoder_produced_a_frame_ = true;
traits_.OnDecodeDone(output);
// |decoder_| sucessfully decoded a frame. No need to keep buffers for a
// |decoder_| successfully decoded a frame. No need to keep buffers for a
// fallback decoder.
// Note: |fallback_buffers_| might still have buffers, and we will keep
// reading from there before requesting new buffers from |stream_|.
......
......@@ -50,14 +50,17 @@ class MEDIA_EXPORT DecoderStream {
DECODE_ERROR, // Decoder returned decode error.
};
// Callback to create a list of decoders.
using CreateDecodersCB = base::RepeatingCallback<ScopedVector<Decoder>()>;
// Indicates completion of a DecoderStream initialization.
typedef base::Callback<void(bool success)> InitCB;
using InitCB = base::Callback<void(bool success)>;
// Indicates completion of a DecoderStream read.
typedef base::Callback<void(Status, const scoped_refptr<Output>&)> ReadCB;
using ReadCB = base::Callback<void(Status, const scoped_refptr<Output>&)>;
DecoderStream(const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
ScopedVector<Decoder> decoders,
CreateDecodersCB create_decoders_cb,
MediaLog* media_log);
virtual ~DecoderStream();
......@@ -188,7 +191,7 @@ class MEDIA_EXPORT DecoderStream {
DecoderStreamTraits<StreamType> traits_;
scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
CreateDecodersCB create_decoders_cb_;
MediaLog* media_log_;
State state_;
......@@ -211,6 +214,16 @@ 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_;
ConfigChangeObserverCB config_change_observer_cb_;
......
......@@ -23,12 +23,12 @@ FakeVideoDecoder::FakeVideoDecoder(const std::string& decoder_name,
total_bytes_decoded_(0),
fail_to_initialize_(false),
weak_factory_(this) {
DVLOG(1) << __func__;
DVLOG(1) << decoder_name_ << ": " << __func__;
DCHECK_GE(decoding_delay, 0);
}
FakeVideoDecoder::~FakeVideoDecoder() {
DVLOG(1) << __func__;
DVLOG(1) << decoder_name_ << ": " << __func__;
DCHECK(thread_checker_.CalledOnValidThread());
if (state_ == STATE_UNINITIALIZED)
......@@ -57,7 +57,7 @@ void FakeVideoDecoder::Initialize(const VideoDecoderConfig& config,
CdmContext* cdm_context,
const InitCB& init_cb,
const OutputCB& output_cb) {
DVLOG(1) << __func__;
DVLOG(1) << decoder_name_ << ": " << __func__;
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(config.IsValidConfig());
DCHECK(held_decode_callbacks_.empty())
......@@ -82,9 +82,11 @@ void FakeVideoDecoder::Initialize(const VideoDecoderConfig& config,
}
if (fail_to_initialize_) {
DVLOG(1) << decoder_name_ << ": Initialization failed.";
state_ = STATE_ERROR;
init_cb_.RunOrHold(false);
} else {
DVLOG(1) << decoder_name_ << ": Initialization succeeded.";
state_ = STATE_NORMAL;
init_cb_.RunOrHold(true);
}
......
......@@ -40,6 +40,9 @@ MATCHER(ClearConfig, "") {
namespace media {
const char kDecoder1[] = "Decoder1";
const char kDecoder2[] = "Decoder2";
class VideoDecoderSelectorTest : public ::testing::Test {
public:
enum DecryptorCapability {
......@@ -53,8 +56,8 @@ class VideoDecoderSelectorTest : public ::testing::Test {
: traits_(&media_log_),
demuxer_stream_(
new StrictMock<MockDemuxerStream>(DemuxerStream::VIDEO)),
decoder_1_(new StrictMock<MockVideoDecoder>()),
decoder_2_(new StrictMock<MockVideoDecoder>()) {
decoder_1_(new StrictMock<MockVideoDecoder>(kDecoder1)),
decoder_2_(new StrictMock<MockVideoDecoder>(kDecoder2)) {
all_decoders_.push_back(decoder_1_);
all_decoders_.push_back(decoder_2_);
// |cdm_context_| and |decryptor_| are conditionally created in
......@@ -81,6 +84,10 @@ class VideoDecoderSelectorTest : public ::testing::Test {
TestVideoConfig::NormalEncrypted());
}
ScopedVector<VideoDecoder> CreateVideoDecodersForTest() {
return std::move(all_decoders_);
}
void InitializeDecoderSelector(DecryptorCapability decryptor_capability,
int num_decoders) {
if (decryptor_capability != kNoCdm) {
......@@ -104,12 +111,18 @@ class VideoDecoderSelectorTest : public ::testing::Test {
all_decoders_.begin() + num_decoders, all_decoders_.end());
decoder_selector_.reset(new VideoDecoderSelector(
message_loop_.task_runner(), std::move(all_decoders_), &media_log_));
message_loop_.task_runner(),
base::Bind(&VideoDecoderSelectorTest::CreateVideoDecodersForTest,
base::Unretained(this)),
&media_log_));
}
void SelectDecoder() {
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,
......@@ -163,18 +176,22 @@ class VideoDecoderSelectorTest : public ::testing::Test {
DISALLOW_COPY_AND_ASSIGN(VideoDecoderSelectorTest);
};
// Tests for clear streams.
// Tests for clear streams. CDM will not be used for clear streams so
// DecryptorCapability doesn't really matter.
TEST_F(VideoDecoderSelectorTest, ClearStream_NoDecryptor_NoClearDecoder) {
TEST_F(VideoDecoderSelectorTest, ClearStream_NoClearDecoder) {
UseClearStream();
InitializeDecoderSelector(kNoDecryptor, 0);
// 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_NoCdm_OneClearDecoder) {
TEST_F(VideoDecoderSelectorTest, ClearStream_OneClearDecoder) {
UseClearStream();
InitializeDecoderSelector(kNoCdm, 1);
......@@ -185,7 +202,7 @@ TEST_F(VideoDecoderSelectorTest, ClearStream_NoCdm_OneClearDecoder) {
SelectDecoder();
}
TEST_F(VideoDecoderSelectorTest, Destroy_ClearStream_NoCdm_OneClearDecoder) {
TEST_F(VideoDecoderSelectorTest, Destroy_ClearStream_OneClearDecoder) {
UseClearStream();
InitializeDecoderSelector(kNoCdm, 1);
......@@ -194,7 +211,7 @@ TEST_F(VideoDecoderSelectorTest, Destroy_ClearStream_NoCdm_OneClearDecoder) {
SelectDecoderAndDestroy();
}
TEST_F(VideoDecoderSelectorTest, ClearStream_NoCdm_MultipleClearDecoder) {
TEST_F(VideoDecoderSelectorTest, ClearStream_MultipleClearDecoder) {
UseClearStream();
InitializeDecoderSelector(kNoCdm, 2);
......@@ -207,8 +224,7 @@ TEST_F(VideoDecoderSelectorTest, ClearStream_NoCdm_MultipleClearDecoder) {
SelectDecoder();
}
TEST_F(VideoDecoderSelectorTest,
Destroy_ClearStream_NoCdm_MultipleClearDecoder) {
TEST_F(VideoDecoderSelectorTest, Destroy_ClearStream_MultipleClearDecoder) {
UseClearStream();
InitializeDecoderSelector(kNoCdm, 2);
......@@ -219,90 +235,16 @@ TEST_F(VideoDecoderSelectorTest,
SelectDecoderAndDestroy();
}
TEST_F(VideoDecoderSelectorTest, ClearStream_NoDecryptor_OneClearDecoder) {
TEST_F(VideoDecoderSelectorTest, ClearStream_BlackListedDecoder) {
UseClearStream();
InitializeDecoderSelector(kNoDecryptor, 1);
EXPECT_CALL(*decoder_1_, Initialize(EncryptedConfig(), _, _, _, _))
.WillOnce(RunCallback<3>(false));
EXPECT_CALL(*this, OnDecoderSelected(IsNull(), IsNull()));
SelectDecoder();
}
TEST_F(VideoDecoderSelectorTest,
Destroy_ClearStream_NoDecryptor_OneClearDecoder) {
UseClearStream();
InitializeDecoderSelector(kNoDecryptor, 1);
EXPECT_CALL(*decoder_1_, Initialize(EncryptedConfig(), _, _, _, _));
SelectDecoderAndDestroy();
}
TEST_F(VideoDecoderSelectorTest, ClearStream_NoDecryptor_MultipleClearDecoder) {
UseClearStream();
InitializeDecoderSelector(kNoDecryptor, 2);
InitializeDecoderSelector(kNoCdm, 2);
EXPECT_CALL(*decoder_1_, Initialize(EncryptedConfig(), _, _, _, _))
.WillOnce(RunCallback<3>(false));
EXPECT_CALL(*decoder_2_, Initialize(EncryptedConfig(), _, _, _, _))
// Decoder 1 is blacklisted and will not even be tried.
EXPECT_CALL(*decoder_2_, Initialize(ClearConfig(), _, _, _, _))
.WillOnce(RunCallback<3>(true));
EXPECT_CALL(*this, OnDecoderSelected(decoder_2_, IsNull()));
SelectDecoder();
}
TEST_F(VideoDecoderSelectorTest,
Destroy_ClearStream_NoDecryptor_MultipleClearDecoder) {
UseClearStream();
InitializeDecoderSelector(kNoDecryptor, 2);
EXPECT_CALL(*decoder_1_, Initialize(EncryptedConfig(), _, _, _, _))
.WillOnce(RunCallback<3>(false));
EXPECT_CALL(*decoder_2_, Initialize(EncryptedConfig(), _, _, _, _));
SelectDecoderAndDestroy();
}
TEST_F(VideoDecoderSelectorTest, ClearStream_DecryptOnly) {
UseClearStream();
InitializeDecoderSelector(kDecryptOnly, 1);
EXPECT_CALL(*decoder_1_, Initialize(ClearConfig(), _, _, _, _))
.WillOnce(RunCallback<3>(true));
EXPECT_CALL(*this, OnDecoderSelected(decoder_1_, NotNull()));
SelectDecoder();
}
TEST_F(VideoDecoderSelectorTest, Destroy_ClearStream_DecryptOnly) {
UseClearStream();
InitializeDecoderSelector(kDecryptOnly, 1);
EXPECT_CALL(*decoder_1_, Initialize(ClearConfig(), _, _, _, _));
SelectDecoderAndDestroy();
}
TEST_F(VideoDecoderSelectorTest, ClearStream_DecryptAndDecode) {
UseClearStream();
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(*decoder_1_, Initialize(ClearConfig(), _, _, _, _))
.WillOnce(RunCallback<3>(true));
EXPECT_CALL(*this, OnDecoderSelected(NotNull(), NotNull()));
#endif
SelectDecoder();
SelectDecoderWithBlacklist(kDecoder1);
}
// Tests for encrypted streams.
......@@ -431,4 +373,48 @@ TEST_F(VideoDecoderSelectorTest, EncryptedStream_DecryptAndDecode) {
SelectDecoder();
}
TEST_F(VideoDecoderSelectorTest,
EncryptedStream_NoDecryptor_BlackListedDecoder) {
UseEncryptedStream();
InitializeDecoderSelector(kNoDecryptor, 2);
EXPECT_CALL(*decoder_2_, Initialize(EncryptedConfig(), _, _, _, _))
.WillOnce(RunCallback<3>(true));
EXPECT_CALL(*this, OnDecoderSelected(decoder_2_, IsNull()));
SelectDecoderWithBlacklist(kDecoder1);
}
TEST_F(VideoDecoderSelectorTest,
EncryptedStream_DecryptOnly_BlackListedDecoder) {
UseEncryptedStream();
InitializeDecoderSelector(kDecryptOnly, 2);
// When DecryptingDemuxerStream is chosen, the blacklist is ignored.
EXPECT_CALL(*decoder_1_, Initialize(ClearConfig(), _, _, _, _))
.WillOnce(RunCallback<3>(false));
EXPECT_CALL(*decoder_2_, Initialize(ClearConfig(), _, _, _, _))
.WillOnce(RunCallback<3>(true));
EXPECT_CALL(*this, OnDecoderSelected(decoder_2_, NotNull()));
SelectDecoderWithBlacklist(kDecoder2);
}
TEST_F(VideoDecoderSelectorTest,
EncryptedStream_DecryptAndDecode_BlackListedDecoder) {
UseEncryptedStream();
InitializeDecoderSelector(kDecryptAndDecode, 2);
// DecryptingAudioDecoder is blacklisted so we'll fallback to use
// DecryptingDemuxerStream to do decrypt-only.
EXPECT_CALL(*decoder_1_, Initialize(ClearConfig(), _, _, _, _))
.WillOnce(RunCallback<3>(false));
EXPECT_CALL(*decoder_2_, Initialize(ClearConfig(), _, _, _, _))
.WillOnce(RunCallback<3>(true));
EXPECT_CALL(*this, OnDecoderSelected(decoder_2_, NotNull()));
// TODO(xhwang): Avoid the hardcoded string here.
SelectDecoderWithBlacklist("DecryptingVideoDecoder");
}
} // namespace media
......@@ -356,7 +356,7 @@ void AudioRendererImpl::Initialize(DemuxerStream* stream,
client_ = client;
audio_buffer_stream_ = base::MakeUnique<AudioBufferStream>(
task_runner_, create_audio_decoders_cb_.Run(), media_log_);
task_runner_, create_audio_decoders_cb_, media_log_);
audio_buffer_stream_->set_config_change_observer(base::Bind(
&AudioRendererImpl::OnConfigChange, weak_factory_.GetWeakPtr()));
......
......@@ -150,9 +150,8 @@ void VideoRendererImpl::Initialize(
DCHECK(!was_background_rendering_);
DCHECK(!time_progressing_);
ScopedVector<VideoDecoder> decoders = create_video_decoders_cb_.Run();
video_frame_stream_.reset(
new VideoFrameStream(task_runner_, std::move(decoders), media_log_));
video_frame_stream_.reset(new VideoFrameStream(
task_runner_, create_video_decoders_cb_, media_log_));
// Always re-initialize or reset the |gpu_memory_buffer_pool_| in case we are
// switching between video tracks with incompatible video formats (e.g. 8-bit
......
......@@ -57,9 +57,9 @@ class VideoRendererImplTest : public testing::Test {
decoder_ = new NiceMock<MockVideoDecoder>();
ScopedVector<VideoDecoder> decoders;
decoders.push_back(decoder_);
EXPECT_CALL(*decoder_, Initialize(_, _, _, _, _))
.WillOnce(DoAll(SaveArg<4>(&output_cb_),
RunCallback<3>(expect_init_success_)));
ON_CALL(*decoder_, Initialize(_, _, _, _, _))
.WillByDefault(DoAll(SaveArg<4>(&output_cb_),
RunCallback<3>(expect_init_success_)));
// Monitor decodes from the decoder.
ON_CALL(*decoder_, Decode(_, _))
.WillByDefault(Invoke(this, &VideoRendererImplTest::DecodeRequested));
......
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