Commit 689cd428 authored by Xiaohan Wang's avatar Xiaohan Wang Committed by Commit Bot

media: Add kError to DemuxerStream::Status

Some DemuxerStream could hit non-recoverable fatal errors. For example,
DecryptingDemuxerStream cannot decrypt a buffer because the CDM was
crashed.

This CL adds a new kError status to DemuxerStream::Status to indicate
such conditions.

BUG=730766
TEST=New unittests added.

Cq-Include-Trybots: master.tryserver.chromium.android:android_optional_gpu_tests_rel;master.tryserver.chromium.linux:linux_optional_gpu_tests_rel;master.tryserver.chromium.mac:mac_optional_gpu_tests_rel;master.tryserver.chromium.win:win_optional_gpu_tests_rel
Change-Id: Ia07e14662d7da852f712076dcc94aed1900aaba3
Reviewed-on: https://chromium-review.googlesource.com/598702
Commit-Queue: Xiaohan Wang <xhwang@chromium.org>
Reviewed-by: default avatarMatthew Wolenetz <wolenetz@chromium.org>
Reviewed-by: default avatarYuri Wiitala <miu@chromium.org>
Cr-Commit-Position: refs/heads/master@{#491960}
parent 6869f052
......@@ -51,11 +51,13 @@ class MEDIA_EXPORT DemuxerStream {
// when this status is returned.
// This will only be returned if SupportsConfigChanges()
// returns 'true' for this DemuxerStream.
// kError : Unexpected fatal error happened. Playback should fail.
enum Status {
kOk,
kAborted,
kConfigChanged,
kStatusMax = kConfigChanged,
kError,
kStatusMax = kError,
};
// Request a buffer to returned via the provided callback.
......
......@@ -140,6 +140,13 @@ void FakeDemuxerStream::Reset() {
base::ResetAndReturn(&read_cb_).Run(kAborted, NULL);
}
void FakeDemuxerStream::Error() {
read_to_hold_ = -1;
if (!read_cb_.is_null())
base::ResetAndReturn(&read_cb_).Run(kError, nullptr);
}
void FakeDemuxerStream::SeekToStart() {
Reset();
Initialize();
......
......@@ -59,6 +59,10 @@ class FakeDemuxerStream : public DemuxerStream {
// always clears |hold_next_read_|.
void Reset();
// Satisfies the pending read (if any) with kError and NULL. This call
// always clears |hold_next_read_|.
void Error();
// Reset() this demuxer stream and set the reading position to the start of
// the stream.
void SeekToStart();
......
......@@ -42,13 +42,7 @@ class FakeDemuxerStreamTest : public testing::Test {
num_buffers_received_++;
}
enum ReadResult {
OK,
ABORTED,
CONFIG_CHANGED,
EOS,
PENDING
};
enum ReadResult { OK, ABORTED, CONFIG_CHANGED, READ_ERROR, EOS, PENDING };
void EnterNormalReadState() {
stream_.reset(
......@@ -87,6 +81,12 @@ class FakeDemuxerStreamTest : public testing::Test {
EXPECT_FALSE(buffer_.get());
break;
case READ_ERROR:
EXPECT_FALSE(read_pending_);
EXPECT_EQ(DemuxerStream::kError, status_);
EXPECT_FALSE(buffer_.get());
break;
case EOS:
EXPECT_FALSE(read_pending_);
EXPECT_EQ(DemuxerStream::kOk, status_);
......@@ -137,6 +137,16 @@ class FakeDemuxerStreamTest : public testing::Test {
ExpectReadResult(ABORTED);
}
void Error() {
bool had_read_pending = read_pending_;
stream_->Error();
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(read_pending_);
if (had_read_pending)
ExpectReadResult(READ_ERROR);
}
void ReadAllBuffers(int num_configs, int num_buffers_in_one_config) {
DCHECK_EQ(0, num_buffers_received_);
for (int i = 0; i < num_configs; ++i) {
......@@ -256,6 +266,35 @@ TEST_F(FakeDemuxerStreamTest, Reset_BeforeEOS) {
ReadAndExpect(EOS);
}
TEST_F(FakeDemuxerStreamTest, Error_Normal) {
EnterNormalReadState();
Error();
ReadAndExpect(OK);
}
TEST_F(FakeDemuxerStreamTest, Error_AfterHoldRead) {
EnterNormalReadState();
stream_->HoldNextRead();
Error();
ReadAndExpect(OK);
}
TEST_F(FakeDemuxerStreamTest, Error_BeforeConfigChanged) {
EnterNormalReadState();
stream_->HoldNextConfigChangeRead();
ReadUntilPending();
Error();
ReadAndExpect(CONFIG_CHANGED);
}
TEST_F(FakeDemuxerStreamTest, Error_BeforeEOS) {
EnterBeforeEOSState();
stream_->HoldNextRead();
ReadAndExpect(PENDING);
Error();
ReadAndExpect(EOS);
}
TEST_F(FakeDemuxerStreamTest, NoConfigChanges) {
stream_.reset(
new FakeDemuxerStream(1, kNumBuffersInOneConfig, false));
......
......@@ -595,14 +595,25 @@ void DecoderStream<StreamType>::OnBufferReady(
pending_buffers_.clear();
break;
case DemuxerStream::kAborted:
// |this| will read from the demuxer stream again in OnDecoderSelected()
// and receive a kAborted then.
case DemuxerStream::kError:
// Will read from the demuxer stream again in OnDecoderSelected().
pending_buffers_.clear();
break;
}
return;
}
if (status == DemuxerStream::kError) {
FUNCTION_DVLOG(1) << ": Demuxer stream read error!";
state_ = STATE_ERROR;
MEDIA_LOG(ERROR, media_log_)
<< GetStreamTypeString() << " demuxer stream read error!";
pending_buffers_.clear();
ready_outputs_.clear();
if (!read_cb_.is_null())
SatisfyRead(DECODE_ERROR, nullptr);
}
// Decoding has been stopped.
if (state_ == STATE_ERROR) {
DCHECK(read_cb_.is_null());
......
......@@ -202,6 +202,7 @@ class DecryptingAudioDecoderTest : public testing::Test {
decoder_->Decode(encrypted_buffer_,
base::Bind(&DecryptingAudioDecoderTest::DecodeDone,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
}
......
......@@ -174,7 +174,7 @@ DecryptingDemuxerStream::~DecryptingDemuxerStream() {
void DecryptingDemuxerStream::DecryptBuffer(
DemuxerStream::Status status,
const scoped_refptr<DecoderBuffer>& buffer) {
DVLOG(3) << __func__;
DVLOG(3) << __func__ << ": status = " << status;
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK_EQ(state_, kPendingDemuxerRead) << state_;
DCHECK(!read_cb_.is_null());
......@@ -204,10 +204,13 @@ void DecryptingDemuxerStream::DecryptBuffer(
return;
}
if (status == kAborted) {
DVLOG(2) << "DoDecryptBuffer() - kAborted.";
if (status == kAborted || status == kError) {
if (status == kError) {
MEDIA_LOG(ERROR, media_log_)
<< GetDisplayName() << ": demuxer stream read error.";
}
state_ = kIdle;
base::ResetAndReturn(&read_cb_).Run(kAborted, NULL);
base::ResetAndReturn(&read_cb_).Run(status, nullptr);
return;
}
......@@ -285,7 +288,7 @@ void DecryptingDemuxerStream::DeliverBuffer(
MEDIA_LOG(ERROR, media_log_) << GetDisplayName() << ": decrypt error";
pending_buffer_to_decrypt_ = NULL;
state_ = kIdle;
base::ResetAndReturn(&read_cb_).Run(kAborted, NULL);
base::ResetAndReturn(&read_cb_).Run(kError, nullptr);
return;
}
......@@ -330,8 +333,8 @@ void DecryptingDemuxerStream::OnKeyAdded() {
}
if (state_ == kWaitingForKey) {
MEDIA_LOG(INFO, media_log_) << GetDisplayName()
<< ": key added, resuming decrypt";
MEDIA_LOG(INFO, media_log_)
<< GetDisplayName() << ": key was added, resuming decrypt";
state_ = kPendingDecrypt;
DecryptPendingBuffer();
}
......
......@@ -17,12 +17,15 @@
#include "media/base/gmock_callback_support.h"
#include "media/base/media_util.h"
#include "media/base/mock_filters.h"
#include "media/base/mock_media_log.h"
#include "media/base/test_helpers.h"
#include "media/filters/decrypting_demuxer_stream.h"
#include "testing/gmock/include/gmock/gmock.h"
using ::testing::_;
using ::testing::HasSubstr;
using ::testing::IsNull;
using ::testing::InSequence;
using ::testing::Return;
using ::testing::SaveArg;
using ::testing::StrictMock;
......@@ -217,11 +220,13 @@ class DecryptingDemuxerStreamTest : public testing::Test {
}
void EnterWaitingForKeyState() {
InSequence s;
EXPECT_CALL(*input_audio_stream_, Read(_))
.WillRepeatedly(ReturnBuffer(encrypted_buffer_));
EXPECT_CALL(*decryptor_, Decrypt(_, encrypted_buffer_, _))
.WillRepeatedly(RunCallback<2>(Decryptor::kNoKey,
scoped_refptr<DecoderBuffer>()));
EXPECT_MEDIA_LOG(HasSubstr("DecryptingDemuxerStream: no key for key ID"));
EXPECT_CALL(*this, OnWaitingForDecryptionKey());
demuxer_stream_->Read(base::Bind(&DecryptingDemuxerStreamTest::BufferReady,
base::Unretained(this)));
......@@ -254,7 +259,7 @@ class DecryptingDemuxerStreamTest : public testing::Test {
MOCK_METHOD0(OnWaitingForDecryptionKey, void(void));
base::MessageLoop message_loop_;
MediaLog media_log_;
StrictMock<MockMediaLog> media_log_;
std::unique_ptr<DecryptingDemuxerStream> demuxer_stream_;
std::unique_ptr<StrictMock<MockCdmContext>> cdm_context_;
std::unique_ptr<StrictMock<MockDecryptor>> decryptor_;
......@@ -308,6 +313,7 @@ TEST_F(DecryptingDemuxerStreamTest, Initialize_CdmWithoutDecryptor) {
AudioDecoderConfig input_config(kCodecVorbis, kSampleFormatPlanarF32,
CHANNEL_LAYOUT_STEREO, 44100,
EmptyExtraData(), AesCtrEncryptionScheme());
EXPECT_MEDIA_LOG(HasSubstr("DecryptingDemuxerStream: no decryptor"));
InitializeAudioAndExpectStatus(input_config, DECODER_ERROR_NOT_SUPPORTED);
}
......@@ -340,7 +346,8 @@ TEST_F(DecryptingDemuxerStreamTest, Read_DecryptError) {
EXPECT_CALL(*decryptor_, Decrypt(_, encrypted_buffer_, _))
.WillRepeatedly(RunCallback<2>(Decryptor::kError,
scoped_refptr<DecoderBuffer>()));
ReadAndExpectBufferReadyWith(DemuxerStream::kAborted, NULL);
EXPECT_MEDIA_LOG(HasSubstr("DecryptingDemuxerStream: decrypt error"));
ReadAndExpectBufferReadyWith(DemuxerStream::kError, nullptr);
}
// Test the case where the input is an end-of-stream buffer.
......@@ -362,6 +369,8 @@ TEST_F(DecryptingDemuxerStreamTest, KeyAdded_DuringWaitingForKey) {
Initialize();
EnterWaitingForKeyState();
EXPECT_MEDIA_LOG(
HasSubstr("DecryptingDemuxerStream: key was added, resuming decrypt"));
EXPECT_CALL(*decryptor_, Decrypt(_, encrypted_buffer_, _))
.WillRepeatedly(RunCallback<2>(Decryptor::kSuccess, decrypted_buffer_));
EXPECT_CALL(*this, BufferReady(DemuxerStream::kOk, decrypted_buffer_));
......@@ -375,6 +384,9 @@ TEST_F(DecryptingDemuxerStreamTest, KeyAdded_DuringPendingDecrypt) {
Initialize();
EnterPendingDecryptState();
EXPECT_MEDIA_LOG(HasSubstr("DecryptingDemuxerStream: no key for key ID"));
EXPECT_MEDIA_LOG(
HasSubstr("DecryptingDemuxerStream: key was added, resuming decrypt"));
EXPECT_CALL(*decryptor_, Decrypt(_, encrypted_buffer_, _))
.WillRepeatedly(RunCallback<2>(Decryptor::kSuccess, decrypted_buffer_));
EXPECT_CALL(*this, BufferReady(DemuxerStream::kOk, decrypted_buffer_));
......
......@@ -15,6 +15,7 @@
#include "media/base/fake_demuxer_stream.h"
#include "media/base/gmock_callback_support.h"
#include "media/base/mock_filters.h"
#include "media/base/mock_media_log.h"
#include "media/base/test_helpers.h"
#include "media/base/timestamp_constants.h"
#include "media/filters/decoder_stream.h"
......@@ -24,6 +25,8 @@
using ::testing::_;
using ::testing::AnyNumber;
using ::testing::Assign;
using ::testing::HasSubstr;
using ::testing::InSequence;
using ::testing::Invoke;
using ::testing::InvokeWithoutArgs;
using ::testing::NiceMock;
......@@ -97,6 +100,11 @@ class VideoFrameStreamTest
EXPECT_CALL(*cdm_context_, GetDecryptor())
.WillRepeatedly(Return(decryptor_.get()));
}
// Covering most MediaLog messages for now.
// TODO(wolenetz/xhwang): Fix tests to have better MediaLog checking.
EXPECT_MEDIA_LOG(HasSubstr("video")).Times(AnyNumber());
EXPECT_MEDIA_LOG(HasSubstr("decryptor")).Times(AnyNumber());
}
~VideoFrameStreamTest() {
......@@ -322,6 +330,7 @@ class VideoFrameStreamTest
case DECRYPTOR_NO_KEY:
if (GetParam().is_encrypted && GetParam().has_decryptor) {
EXPECT_MEDIA_LOG(HasSubstr("no key for key ID"));
EXPECT_CALL(*this, OnWaitingForDecryptionKey());
has_no_key_ = true;
}
......@@ -403,7 +412,7 @@ class VideoFrameStreamTest
base::MessageLoop message_loop_;
MediaLog media_log_;
StrictMock<MockMediaLog> media_log_;
std::unique_ptr<VideoFrameStream> video_frame_stream_;
std::unique_ptr<FakeDemuxerStream> demuxer_stream_;
std::unique_ptr<StrictMock<MockCdmContext>> cdm_context_;
......@@ -617,6 +626,25 @@ TEST_P(VideoFrameStreamTest, Read_DuringEndOfStreamDecode) {
frame_read_->metadata()->IsTrue(VideoFrameMetadata::END_OF_STREAM));
}
TEST_P(VideoFrameStreamTest, Read_DemuxerStreamReadError) {
Initialize();
EnterPendingState(DEMUXER_READ_NORMAL);
InSequence s;
if (GetParam().is_encrypted && GetParam().has_decryptor) {
EXPECT_MEDIA_LOG(
HasSubstr("DecryptingDemuxerStream: demuxer stream read error"));
}
EXPECT_MEDIA_LOG(HasSubstr("video demuxer stream read error"));
demuxer_stream_->Error();
base::RunLoop().RunUntilIdle();
ASSERT_FALSE(pending_read_);
EXPECT_EQ(last_read_status_, VideoFrameStream::DECODE_ERROR);
}
// No Reset() before initialization is successfully completed.
TEST_P(VideoFrameStreamTest, Reset_AfterInitialization) {
Initialize();
......
......@@ -271,6 +271,10 @@ void DemuxerStreamAdapter::OnNewBuffer(
DCHECK(!input);
SendReadAck();
return;
case DemuxerStream::kError:
// Currently kError can only happen because of DECRYPTION_ERROR.
OnFatalError(DECRYPTION_ERROR);
return;
case DemuxerStream::kConfigChanged:
// TODO(erickung): Notify controller of new decoder config, just in case
// that will require remoting to be shutdown (due to known
......
......@@ -577,6 +577,7 @@ base::Optional<DemuxerStream::Status> ToDemuxerStreamStatus(
CASE_RETURN_OTHER(kOk);
CASE_RETURN_OTHER(kAborted);
CASE_RETURN_OTHER(kConfigChanged);
CASE_RETURN_OTHER(kError);
}
return base::nullopt; // Not a 'default' to ensure compile-time checks.
}
......@@ -589,6 +590,7 @@ ToProtoDemuxerStreamStatus(DemuxerStream::Status value) {
CASE_RETURN_OTHER(kOk);
CASE_RETURN_OTHER(kAborted);
CASE_RETURN_OTHER(kConfigChanged);
CASE_RETURN_OTHER(kError);
}
return base::nullopt; // Not a 'default' to ensure compile-time checks.
}
......
......@@ -363,6 +363,7 @@ message DemuxerStreamReadUntilCallback {
kOk = 0;
kAborted = 1;
kConfigChanged = 2;
kError = 3;
};
optional Status status = 1;
......
......@@ -336,6 +336,7 @@ void MediaStream::CompleteRead(DemuxerStream::Status status) {
base::ResetAndReturn(&read_complete_callback_).Run(status, nullptr);
return;
case DemuxerStream::kAborted:
case DemuxerStream::kError:
base::ResetAndReturn(&read_complete_callback_).Run(status, nullptr);
return;
case DemuxerStream::kOk:
......
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