[go: nahoru, domu]

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: Matthew Wolenetz <wolenetz@chromium.org>
Reviewed-by: Yuri Wiitala <miu@chromium.org>
Cr-Commit-Position: refs/heads/master@{#491960}
diff --git a/media/base/demuxer_stream.h b/media/base/demuxer_stream.h
index ad6f215..a88c1ca4 100644
--- a/media/base/demuxer_stream.h
+++ b/media/base/demuxer_stream.h
@@ -51,11 +51,13 @@
   //                  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.
diff --git a/media/base/fake_demuxer_stream.cc b/media/base/fake_demuxer_stream.cc
index a7883db5..fdbeee6 100644
--- a/media/base/fake_demuxer_stream.cc
+++ b/media/base/fake_demuxer_stream.cc
@@ -140,6 +140,13 @@
     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();
diff --git a/media/base/fake_demuxer_stream.h b/media/base/fake_demuxer_stream.h
index f081fa3..0360f29d 100644
--- a/media/base/fake_demuxer_stream.h
+++ b/media/base/fake_demuxer_stream.h
@@ -59,6 +59,10 @@
   // 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();
diff --git a/media/base/fake_demuxer_stream_unittest.cc b/media/base/fake_demuxer_stream_unittest.cc
index def5f9ca..85d09f9 100644
--- a/media/base/fake_demuxer_stream_unittest.cc
+++ b/media/base/fake_demuxer_stream_unittest.cc
@@ -42,13 +42,7 @@
       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 @@
         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 @@
       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 @@
   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));
diff --git a/media/filters/decoder_stream.cc b/media/filters/decoder_stream.cc
index 368469c..ab12038 100644
--- a/media/filters/decoder_stream.cc
+++ b/media/filters/decoder_stream.cc
@@ -595,14 +595,25 @@
         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());
diff --git a/media/filters/decrypting_audio_decoder_unittest.cc b/media/filters/decrypting_audio_decoder_unittest.cc
index fb34c76..b8bca93b1b 100644
--- a/media/filters/decrypting_audio_decoder_unittest.cc
+++ b/media/filters/decrypting_audio_decoder_unittest.cc
@@ -202,6 +202,7 @@
     decoder_->Decode(encrypted_buffer_,
                      base::Bind(&DecryptingAudioDecoderTest::DecodeDone,
                                 base::Unretained(this)));
+
     base::RunLoop().RunUntilIdle();
   }
 
diff --git a/media/filters/decrypting_demuxer_stream.cc b/media/filters/decrypting_demuxer_stream.cc
index 1afb497..f5dfcd4 100644
--- a/media/filters/decrypting_demuxer_stream.cc
+++ b/media/filters/decrypting_demuxer_stream.cc
@@ -174,7 +174,7 @@
 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 @@
     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 @@
     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 @@
   }
 
   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();
   }
diff --git a/media/filters/decrypting_demuxer_stream_unittest.cc b/media/filters/decrypting_demuxer_stream_unittest.cc
index 8e0e457fc..84e992a 100644
--- a/media/filters/decrypting_demuxer_stream_unittest.cc
+++ b/media/filters/decrypting_demuxer_stream_unittest.cc
@@ -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 @@
   }
 
   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 @@
   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 @@
   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 @@
   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 @@
   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 @@
   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_));
diff --git a/media/filters/video_frame_stream_unittest.cc b/media/filters/video_frame_stream_unittest.cc
index 8e68439..b53d42d 100644
--- a/media/filters/video_frame_stream_unittest.cc
+++ b/media/filters/video_frame_stream_unittest.cc
@@ -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 @@
       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 @@
 
       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 @@
 
   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 @@
       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();
diff --git a/media/remoting/demuxer_stream_adapter.cc b/media/remoting/demuxer_stream_adapter.cc
index bb5f525..f6d6e49 100644
--- a/media/remoting/demuxer_stream_adapter.cc
+++ b/media/remoting/demuxer_stream_adapter.cc
@@ -271,6 +271,10 @@
       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
diff --git a/media/remoting/proto_enum_utils.cc b/media/remoting/proto_enum_utils.cc
index 45e6ff7..95b3749b 100644
--- a/media/remoting/proto_enum_utils.cc
+++ b/media/remoting/proto_enum_utils.cc
@@ -577,6 +577,7 @@
     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 @@
     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.
 }
diff --git a/media/remoting/rpc.proto b/media/remoting/rpc.proto
index 6eb91af..baceb6a 100644
--- a/media/remoting/rpc.proto
+++ b/media/remoting/rpc.proto
@@ -363,6 +363,7 @@
     kOk = 0;
     kAborted = 1;
     kConfigChanged = 2;
+    kError = 3;
   };
 
   optional Status status = 1;
diff --git a/media/remoting/stream_provider.cc b/media/remoting/stream_provider.cc
index 3bd99591..35b2ba22 100644
--- a/media/remoting/stream_provider.cc
+++ b/media/remoting/stream_provider.cc
@@ -336,6 +336,7 @@
       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: