Index: media/filters/video_frame_stream_unittest.cc |
diff --git a/media/filters/video_frame_stream_unittest.cc b/media/filters/video_frame_stream_unittest.cc |
index 88b5af8ae3b4f733e3bc6c39fd7d298ff1b8673b..2baf2347be21a6a1f3fd4e873dc535cfd2c79bab 100644 |
--- a/media/filters/video_frame_stream_unittest.cc |
+++ b/media/filters/video_frame_stream_unittest.cc |
@@ -3,6 +3,7 @@ |
// found in the LICENSE file. |
#include <utility> |
+#include <vector> |
#include "base/bind.h" |
#include "base/callback_helpers.h" |
@@ -70,34 +71,11 @@ class VideoFrameStreamTest |
pending_stop_(false), |
num_decoded_bytes_unreported_(0), |
has_no_key_(false) { |
- int decoding_delay = GetParam().decoding_delay; |
- int parallel_decoding = GetParam().parallel_decoding; |
- BytesDecodedCB bytes_decoded_cb = base::Bind( |
- &VideoFrameStreamTest::OnBytesDecoded, base::Unretained(this)); |
- |
- // Provide 3 decoders to test fallback cases. |
- // TODO(xhwang): We should test the case where only certain decoder |
- // supports encrypted streams. Currently this is hard to test because we use |
- // parameterized tests which need to pass in all combinations. |
- ScopedVector<VideoDecoder> decoders; |
- for (int i = 0; i < 3; ++i) { |
- FakeVideoDecoder* decoder = |
- new FakeVideoDecoder(GetDecoderName(i), decoding_delay, |
- parallel_decoding, bytes_decoded_cb); |
- |
- if (GetParam().is_encrypted && !GetParam().has_decryptor) |
- decoder->EnableEncryptedConfigSupport(); |
- |
- decoders.push_back(decoder); |
- |
- // Keep a copy of the raw pointers so we can change the behavior of each |
- // decoder. |
- decoders_.push_back(decoder); |
- } |
- |
video_frame_stream_.reset(new VideoFrameStream( |
- message_loop_.task_runner(), std::move(decoders), &media_log_)); |
- |
+ message_loop_.task_runner(), |
+ base::Bind(&VideoFrameStreamTest::CreateVideoDecodersForTest, |
+ base::Unretained(this)), |
+ &media_log_)); |
video_frame_stream_->set_decoder_change_observer_for_testing(base::Bind( |
&VideoFrameStreamTest::OnDecoderChanged, base::Unretained(this))); |
@@ -135,19 +113,95 @@ class VideoFrameStreamTest |
DCHECK(!pending_stop_); |
} |
- MOCK_METHOD0(OnWaitingForDecryptionKey, void(void)); |
- |
- void OnStatistics(const PipelineStatistics& statistics) { |
- num_decoded_bytes_unreported_ -= statistics.video_bytes_decoded; |
- } |
- |
void OnBytesDecoded(int count) { |
num_decoded_bytes_unreported_ += count; |
} |
- void SimulateDecoderInitFailure(const std::vector<int>& decoder_indices) { |
- for (const auto& i : decoder_indices) |
+ // Callback to create a list of decoders for the DecoderSelector to select |
+ // from. Decoder selection happens |
+ // - on the initial selection in Initialize(), |
+ // - on decoder reinitialization failure, which can be simulated by calling |
+ // decoder_->SimulateFailureToInit(), and |
+ // - on decode error of the first buffer, which can be simulated by calling |
+ // decoder_->SimulateError() before reading the first frame. |
+ ScopedVector<VideoDecoder> CreateVideoDecodersForTest() { |
+ // Previously decoders could have been destroyed on decoder reselection. |
+ decoders_.clear(); |
+ |
+ // Provide 3 decoders to test fallback cases. |
+ // TODO(xhwang): We should test the case where only certain decoder |
+ // supports encrypted streams. Currently this is hard to test because we use |
+ // parameterized tests which need to pass in all combinations. |
+ ScopedVector<VideoDecoder> decoders; |
+ for (int i = 0; i < 3; ++i) { |
+ FakeVideoDecoder* decoder = |
+ new FakeVideoDecoder(GetDecoderName(i), GetParam().decoding_delay, |
+ GetParam().parallel_decoding, |
+ base::Bind(&VideoFrameStreamTest::OnBytesDecoded, |
+ base::Unretained(this))); |
+ |
+ if (GetParam().is_encrypted && !GetParam().has_decryptor) |
+ decoder->EnableEncryptedConfigSupport(); |
+ |
+ decoders.push_back(decoder); |
+ |
+ // Keep a copy of the raw pointers so we can change the behavior of each |
+ // decoder. |
+ decoders_.push_back(decoder); |
+ } |
+ |
+ for (const auto& i : decoder_indices_to_fail_init_) |
decoders_[i]->SimulateFailureToInit(); |
+ |
+ for (const auto& i : decoder_indices_to_hold_init_) |
+ decoders_[i]->HoldNextInit(); |
+ |
+ for (const auto& i : decoder_indices_to_hold_decode_) |
+ decoders_[i]->HoldDecode(); |
+ |
+ decoder_indices_to_fail_init_.clear(); |
+ decoder_indices_to_hold_init_.clear(); |
+ decoder_indices_to_hold_decode_.clear(); |
+ |
+ return decoders; |
+ } |
+ |
+ // On next decoder selection, fail initialization on decoders specified by |
+ // |decoder_indices|. |
+ void FailDecoderInitOnSelection(const std::vector<int>& decoder_indices) { |
+ decoder_indices_to_fail_init_ = decoder_indices; |
+ } |
+ |
+ // 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; |
+ } |
+ |
+ // After next decoder selection, hold decode on decoders specified by |
+ // |decoder_indices|. This is needed because after decoder selection decode |
+ // 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; |
+ } |
+ |
+ // Updates the |decoder_| currently being used by VideoFrameStream. |
+ void OnDecoderChanged(VideoDecoder* decoder) { |
+ if (!decoder) { |
+ decoder_ = nullptr; |
+ return; |
+ } |
+ |
+ std::string name = decoder->GetDisplayName(); |
+ ASSERT_TRUE(GetDecoderName(0) == name || GetDecoderName(1) == name || |
+ GetDecoderName(2) == name); |
+ decoder_ = static_cast<FakeVideoDecoder*>(decoder); |
+ } |
+ |
+ MOCK_METHOD0(OnWaitingForDecryptionKey, void(void)); |
+ |
+ void OnStatistics(const PipelineStatistics& statistics) { |
+ num_decoded_bytes_unreported_ -= statistics.video_bytes_decoded; |
} |
void OnInitialized(bool success) { |
@@ -173,18 +227,6 @@ class VideoFrameStreamTest |
base::RunLoop().RunUntilIdle(); |
} |
- void OnDecoderChanged(VideoDecoder* decoder) { |
- if (!decoder) { |
- decoder_ = nullptr; |
- return; |
- } |
- |
- std::string name = decoder->GetDisplayName(); |
- ASSERT_TRUE(GetDecoderName(0) == name || GetDecoderName(1) == name || |
- GetDecoderName(2) == name); |
- decoder_ = static_cast<FakeVideoDecoder*>(decoder); |
- } |
- |
// Fake Decrypt() function used by DecryptingDemuxerStream. It does nothing |
// but removes the DecryptConfig to make the buffer unencrypted. |
void Decrypt(Decryptor::StreamType stream_type, |
@@ -374,6 +416,10 @@ class VideoFrameStreamTest |
// once on a config change. They are owned by |video_frame_stream_|. |
std::vector<FakeVideoDecoder*> decoders_; |
+ std::vector<int> decoder_indices_to_fail_init_; |
+ std::vector<int> decoder_indices_to_hold_init_; |
+ std::vector<int> decoder_indices_to_hold_decode_; |
+ |
// The current decoder used by |video_frame_stream_|. |
FakeVideoDecoder* decoder_; |
@@ -423,13 +469,13 @@ TEST_P(VideoFrameStreamTest, Initialization) { |
} |
TEST_P(VideoFrameStreamTest, AllDecoderInitializationFails) { |
- SimulateDecoderInitFailure({0, 1, 2}); |
+ FailDecoderInitOnSelection({0, 1, 2}); |
Initialize(); |
EXPECT_FALSE(is_initialized_); |
} |
TEST_P(VideoFrameStreamTest, PartialDecoderInitializationFails) { |
- SimulateDecoderInitFailure({0, 1}); |
+ FailDecoderInitOnSelection({0, 1}); |
Initialize(); |
EXPECT_TRUE(is_initialized_); |
} |
@@ -660,7 +706,7 @@ TEST_P(VideoFrameStreamTest, Destroy_BeforeInitialization) { |
} |
TEST_P(VideoFrameStreamTest, Destroy_DuringInitialization) { |
- decoders_[0]->HoldNextInit(); |
+ HoldDecoderInitOnSelection({0}); |
Initialize(); |
} |
@@ -744,15 +790,17 @@ TEST_P(VideoFrameStreamTest, Destroy_AfterRead_AfterReset) { |
Reset(); |
} |
-// The following tests cover the fallback logic. |
+// The following tests cover the fallback logic after reinitialization error or |
+// decode error of the first buffer after initialization. |
-TEST_P(VideoFrameStreamTest, FallbackDecoder_SelectedOnInitialDecodeError) { |
+TEST_P(VideoFrameStreamTest, FallbackDecoder_DecodeError) { |
Initialize(); |
decoder_->SimulateError(); |
ReadOneFrame(); |
// |video_frame_stream_| should have fallen back to a new decoder. |
ASSERT_EQ(GetDecoderName(1), decoder_->GetDisplayName()); |
+ |
ASSERT_FALSE(pending_read_); |
ASSERT_EQ(VideoFrameStream::OK, last_read_status_); |
@@ -824,7 +872,7 @@ TEST_P(VideoFrameStreamTest, FallbackDecoder_DoesReinitializeStompPendingRead) { |
// Force an error to occur on the first decode, but ensure it isn't propagated |
// until after the next read has been started. |
decoder_->SimulateError(); |
- decoders_[1]->HoldDecode(); |
+ HoldDecodeAfterSelection({1}); |
// Complete the fallback to the second decoder with the read still pending. |
base::RunLoop().RunUntilIdle(); |
@@ -844,30 +892,55 @@ TEST_P(VideoFrameStreamTest, FallbackDecoder_DoesReinitializeStompPendingRead) { |
ASSERT_GT(decoder_->total_bytes_decoded(), first_decoded_bytes); |
} |
-TEST_P(VideoFrameStreamTest, |
- FallbackDecoder_SelectedOnInitialDecodeError_Twice) { |
+TEST_P(VideoFrameStreamTest, FallbackDecoder_DecodeErrorTwice) { |
Initialize(); |
+ |
+ // Simulate decode error to trigger the fallback path. |
decoder_->SimulateError(); |
- decoders_[1]->HoldNextInit(); |
+ // Decoder 0 should be blacklisted and never tried. Hold decode on decoder 1 |
+ // and simulate decode error again. |
+ HoldDecodeAfterSelection({1}); |
ReadOneFrame(); |
- |
- decoders_[1]->SatisfyInit(); |
- decoders_[1]->SimulateError(); |
+ ASSERT_EQ(GetDecoderName(1), decoder_->GetDisplayName()); |
+ decoder_->SimulateError(); |
+ decoder_->SatisfyDecode(); |
base::RunLoop().RunUntilIdle(); |
- ASSERT_EQ(GetDecoderName(2), decoder_->GetDisplayName()); |
+ // Only one fallback is allowed so we are not falling back to other decoders. |
+ ASSERT_EQ(GetDecoderName(1), decoder_->GetDisplayName()); |
+ EXPECT_FALSE(pending_read_); |
+ ASSERT_EQ(VideoFrameStream::DECODE_ERROR, last_read_status_); |
+} |
- // |video_frame_stream_| should have fallen back to |decoders_[2]|. |
- ASSERT_FALSE(pending_read_); |
- ASSERT_EQ(VideoFrameStream::OK, last_read_status_); |
+TEST_P(VideoFrameStreamTest, |
+ FallbackDecoder_DecodeErrorTwice_AfterReinitialization) { |
+ Initialize(); |
- // Can't check previously selected decoder(s) right now, they might have been |
- // destroyed already. |
- ASSERT_GT(decoder_->total_bytes_decoded(), 0); |
+ // Simulate decode error to trigger the fallback path. |
+ decoder_->SimulateError(); |
+ ReadOneFrame(); |
+ ASSERT_EQ(GetDecoderName(1), decoder_->GetDisplayName()); |
- // Verify no frame was dropped. |
- ReadAllFrames(); |
+ // Simulate reinitialize error of decoder 1. |
+ decoder_->SimulateFailureToInit(); |
+ |
+ // Decoder 0 should be selected again. Simulate immediate decode error after |
+ // reinitialization. |
+ HoldDecodeAfterSelection({0}); |
+ ReadUntilDecoderReinitialized(); |
+ 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()); |
+ EXPECT_FALSE(pending_read_); |
+ ASSERT_EQ(VideoFrameStream::DECODE_ERROR, last_read_status_); |
} |
TEST_P(VideoFrameStreamTest, FallbackDecoder_ConfigChangeClearsPendingBuffers) { |
@@ -925,6 +998,7 @@ TEST_P(VideoFrameStreamTest, FallbackDecoder_PendingBuffersIsFilledAndCleared) { |
GetParam().parallel_decoding > 1) { |
return; |
} |
+ |
Initialize(); |
// Block on demuxer read and decoder decode so we can step through. |
@@ -956,7 +1030,10 @@ TEST_P(VideoFrameStreamTest, FallbackDecoder_PendingBuffersIsFilledAndCleared) { |
++demuxer_reads_satisfied; |
decoder_->SimulateError(); |
- decoders_[1]->HoldNextInit(); |
+ |
+ HoldDecoderInitOnSelection({1}); |
+ HoldDecodeAfterSelection({1}); |
+ |
base::RunLoop().RunUntilIdle(); |
EXPECT_TRUE(pending_read_); |
@@ -964,7 +1041,6 @@ TEST_P(VideoFrameStreamTest, FallbackDecoder_PendingBuffersIsFilledAndCleared) { |
video_frame_stream_->get_pending_buffers_size_for_testing()); |
decoders_[1]->SatisfyInit(); |
- decoders_[1]->HoldDecode(); |
base::RunLoop().RunUntilIdle(); |
ASSERT_EQ(GetDecoderName(1), decoder_->GetDisplayName()); |
@@ -981,8 +1057,8 @@ TEST_P(VideoFrameStreamTest, FallbackDecoder_PendingBuffersIsFilledAndCleared) { |
decoder_->SatisfyDecode(); |
base::RunLoop().RunUntilIdle(); |
- // Make sure all buffers consumed by |decoders_[1]| have come from the |
- // fallback. Pending buffers should not have been cleared yet. |
+ // Make sure all buffers consumed by |decoders_| have come from the fallback. |
+ // Pending buffers should not have been cleared yet. |
EXPECT_EQ(0, video_frame_stream_->get_fallback_buffers_size_for_testing()); |
EXPECT_EQ(demuxer_reads_satisfied, |
video_frame_stream_->get_pending_buffers_size_for_testing()); |
@@ -1005,12 +1081,13 @@ TEST_P(VideoFrameStreamTest, FallbackDecoder_PendingBuffersIsFilledAndCleared) { |
TEST_P(VideoFrameStreamTest, FallbackDecoder_SelectedOnDecodeThenInitErrors) { |
Initialize(); |
decoder_->SimulateError(); |
- SimulateDecoderInitFailure({1}); |
+ FailDecoderInitOnSelection({1}); |
ReadOneFrame(); |
+ // Decoder 0 should be blacklisted, and decoder 1 fails to initialize, so |
+ // |video_frame_stream_| should have fallen back to decoder 2. |
ASSERT_EQ(GetDecoderName(2), decoder_->GetDisplayName()); |
- // |video_frame_stream_| should have fallen back to |decoders_[2]| |
ASSERT_FALSE(pending_read_); |
ASSERT_EQ(VideoFrameStream::OK, last_read_status_); |
@@ -1023,7 +1100,7 @@ TEST_P(VideoFrameStreamTest, FallbackDecoder_SelectedOnDecodeThenInitErrors) { |
} |
TEST_P(VideoFrameStreamTest, FallbackDecoder_SelectedOnInitThenDecodeErrors) { |
- SimulateDecoderInitFailure({0}); |
+ FailDecoderInitOnSelection({0}); |
Initialize(); |
ASSERT_EQ(GetDecoderName(1), decoder_->GetDisplayName()); |
@@ -1032,8 +1109,8 @@ TEST_P(VideoFrameStreamTest, FallbackDecoder_SelectedOnInitThenDecodeErrors) { |
decoder_->SimulateError(); |
base::RunLoop().RunUntilIdle(); |
- // |video_frame_stream_| should have fallen back to |decoders_[2]| |
- ASSERT_EQ(GetDecoderName(2), decoder_->GetDisplayName()); |
+ // |video_frame_stream_| should have fallen back to decoder 0. |
+ ASSERT_EQ(GetDecoderName(0), decoder_->GetDisplayName()); |
ASSERT_FALSE(pending_read_); |
ASSERT_EQ(VideoFrameStream::OK, last_read_status_); |
@@ -1094,7 +1171,7 @@ TEST_P(VideoFrameStreamTest, DecoderErrorWhenNotReading) { |
EXPECT_EQ(VideoFrameStream::DECODE_ERROR, last_read_status_); |
} |
-TEST_P(VideoFrameStreamTest, FallbackDecoderSelectedOnFailureToReinitialize) { |
+TEST_P(VideoFrameStreamTest, ReinitializeFailure_Once) { |
Initialize(); |
decoder_->SimulateFailureToInit(); |
ReadUntilDecoderReinitialized(); |
@@ -1103,24 +1180,51 @@ TEST_P(VideoFrameStreamTest, FallbackDecoderSelectedOnFailureToReinitialize) { |
ASSERT_GT(decoder_->total_bytes_decoded(), 0); |
} |
-TEST_P(VideoFrameStreamTest, |
- FallbackDecoderSelectedOnFailureToReinitialize_Twice) { |
+TEST_P(VideoFrameStreamTest, ReinitializeFailure_Twice) { |
Initialize(); |
+ |
+ // Trigger reinitialization error, and fallback to decoder 1. |
decoder_->SimulateFailureToInit(); |
ReadUntilDecoderReinitialized(); |
ASSERT_EQ(GetDecoderName(1), decoder_->GetDisplayName()); |
+ |
ReadOneFrame(); |
+ |
+ // Trigger reinitialization error again, and fallback back to decoder 0. |
+ decoder_->SimulateFailureToInit(); |
+ ReadUntilDecoderReinitialized(); |
+ ASSERT_EQ(GetDecoderName(0), decoder_->GetDisplayName()); |
+ ReadAllFrames(); |
+} |
+ |
+TEST_P(VideoFrameStreamTest, ReinitializeFailure_OneUnsupportedDecoder) { |
+ Initialize(); |
+ |
+ // The current decoder will fail to reinitialize and will be blacklisted. |
decoder_->SimulateFailureToInit(); |
+ |
+ // Decoder 1 will also fail to initialize on decoder selection. |
+ FailDecoderInitOnSelection({1}); |
+ |
ReadUntilDecoderReinitialized(); |
+ |
+ // As a result, decoder 2 will be selected. |
ASSERT_EQ(GetDecoderName(2), decoder_->GetDisplayName()); |
+ |
ReadAllFrames(); |
} |
-TEST_P(VideoFrameStreamTest, DecodeErrorAfterFallbackDecoderSelectionFails) { |
+TEST_P(VideoFrameStreamTest, ReinitializeFailure_NoSupportedDecoder) { |
Initialize(); |
+ |
+ // The current decoder will fail to reinitialize and will be blacklisted. |
decoder_->SimulateFailureToInit(); |
- SimulateDecoderInitFailure({1, 2}); |
+ |
+ // Decoder 1 and 2 will also fail to initialize on decoder selection. |
+ FailDecoderInitOnSelection({1, 2}); |
+ |
ReadUntilDecoderReinitialized(); |
+ |
// The error will surface from Read() as DECODE_ERROR. |
while (last_read_status_ == VideoFrameStream::OK) { |
ReadOneFrame(); |
@@ -1134,7 +1238,7 @@ TEST_P(VideoFrameStreamTest, Destroy_DuringFallbackDecoderSelection) { |
Initialize(); |
decoder_->SimulateFailureToInit(); |
EnterPendingState(DECODER_REINIT); |
- decoders_[1]->HoldNextInit(); |
+ HoldDecoderInitOnSelection({1}); |
SatisfyPendingCallback(DECODER_REINIT); |
} |