Index: media/base/android/media_codec_loop_unittest.cc |
diff --git a/media/base/android/media_codec_loop_unittest.cc b/media/base/android/media_codec_loop_unittest.cc |
index 2b08fe0ef08e511da2a6b885c2ed461244631f9d..fa9044e72031c4a54149ea2c9d13794bbe09cd2c 100644 |
--- a/media/base/android/media_codec_loop_unittest.cc |
+++ b/media/base/android/media_codec_loop_unittest.cc |
@@ -6,12 +6,33 @@ |
#include "base/message_loop/message_loop.h" |
#include "media/base/android/media_codec_bridge.h" |
#include "media/base/android/media_codec_loop.h" |
+#include "media/base/android/mock_media_codec_bridge.h" |
+#include "media/base/fake_single_thread_task_runner.h" |
#include "testing/gmock/include/gmock/gmock.h" |
#include "testing/gtest/include/gtest/gtest.h" |
+using ::testing::_; |
+using ::testing::AtLeast; |
+using ::testing::Eq; |
+using ::testing::Field; |
+using ::testing::InSequence; |
+using ::testing::Mock; |
+using ::testing::Return; |
+using ::testing::SetArgPointee; |
+using ::testing::StrictMock; |
+ |
namespace media { |
-class MockMediaCodecLoopClient : public MediaCodecLoop::Client { |
+// These will come from mockable BuildInfo, once it exists. |
+enum TemporaryAndroidVersions { |
+ kJellyBeanMR1 = 17, |
+ kJellyBeanMR2 = 18, |
+ kLollipop = 21, |
+}; |
+ |
+// The client is a strict mock, since we don't want random calls into it. We |
+// want to be sure about the call sequence. |
+class MockMediaCodecLoopClient : public StrictMock<MediaCodecLoop::Client> { |
public: |
MOCK_CONST_METHOD0(IsAnyInputPending, bool()); |
MOCK_METHOD0(ProvideInputData, MediaCodecLoop::InputData()); |
@@ -24,11 +45,149 @@ class MockMediaCodecLoopClient : public MediaCodecLoop::Client { |
class MediaCodecLoopTest : public testing::Test { |
public: |
- MediaCodecLoopTest() : client_(new MockMediaCodecLoopClient) {} |
+ MediaCodecLoopTest() |
+ : client_(new StrictMock<MockMediaCodecLoopClient>()), |
+ task_runner_(new FakeSingleThreadTaskRunner(&clock_)) {} |
+ |
+ ~MediaCodecLoopTest() override {} |
+ |
+ protected: |
+ enum IdleExpectation { |
+ ShouldBeIdle, |
+ ShouldNotBeIdle, |
+ }; |
+ |
+ // Wait until |codec_loop_| is idle. |
+ // Do not call this in a sequence. |
+ void WaitUntilIdle(IdleExpectation idleExpectation = ShouldBeIdle) { |
+ switch (idleExpectation) { |
+ case ShouldBeIdle: |
+ EXPECT_CALL(*client_, IsAnyInputPending()).Times(0); |
+ EXPECT_CALL(Codec(), DequeueOutputBuffer(_, _, _, _, _, _, _)).Times(0); |
+ break; |
+ case ShouldNotBeIdle: |
+ // Expect at least one call to see if more work is ready. We will |
+ // return 'no'. |
+ EXPECT_CALL(*client_, IsAnyInputPending()) |
+ .Times(AtLeast(1)) |
+ .WillRepeatedly(Return(false)); |
+ EXPECT_CALL(Codec(), DequeueOutputBuffer(_, _, _, _, _, _, _)) |
+ .Times(AtLeast(1)) |
+ .WillRepeatedly(Return(MEDIA_CODEC_DEQUEUE_OUTPUT_AGAIN_LATER)); |
+ break; |
+ } |
+ |
+ // Either way, we expect that MCL should not attempt to dequeue input |
+ // buffers, either because it's idle or because we said that no input |
+ // is pending. |
+ EXPECT_CALL(Codec(), DequeueInputBuffer(_, _)).Times(0); |
+ |
+ // TODO(liberato): assume that MCL doesn't retry for 30 seconds. Note |
+ // that this doesn't actually wall-clock wait. |
+ task_runner_->Sleep(base::TimeDelta::FromSeconds(30)); |
+ } |
+ |
+ void ConstructCodecLoop(int sdk_int = kLollipop) { |
+ std::unique_ptr<MediaCodecBridge> codec(new MockMediaCodecBridge()); |
+ // Since we're providing a codec, we do not expect an error. |
+ EXPECT_CALL(*client_, OnCodecLoopError()).Times(0); |
+ codec_loop_.reset(new MediaCodecLoop(sdk_int, client_.get(), |
+ std::move(codec), task_runner_)); |
+ codec_loop_->SetTestTickClock(&clock_); |
+ Mock::VerifyAndClearExpectations(client_.get()); |
+ } |
+ |
+ // Set an expectation that MCL will try to get another input / output buffer, |
+ // and not get one in DoPendingWork. |
+ void ExpectEmptyIOLoop() { |
+ ExpectIsAnyInputPending(false); |
+ EXPECT_CALL(Codec(), DequeueOutputBuffer(_, _, _, _, _, _, _)) |
+ .Times(1) |
+ .WillOnce(Return(MEDIA_CODEC_DEQUEUE_OUTPUT_AGAIN_LATER)); |
+ } |
+ |
+ void ExpectIsAnyInputPending(bool pending) { |
+ EXPECT_CALL(*client_, IsAnyInputPending()).WillOnce(Return(pending)); |
+ } |
+ |
+ void ExpectDequeueInputBuffer(int input_buffer_index, |
+ MediaCodecStatus status = MEDIA_CODEC_OK) { |
+ EXPECT_CALL(Codec(), DequeueInputBuffer(_, _)) |
+ .WillOnce(DoAll(SetArgPointee<1>(input_buffer_index), Return(status))); |
+ } |
+ |
+ void ExpectInputDataQueued(bool success) { |
+ EXPECT_CALL(*client_, OnInputDataQueued(success)).Times(1); |
+ } |
+ |
+ // Expect a call to queue |data| into MC buffer |input_buffer_index|. |
+ void ExpectQueueInputBuffer(int input_buffer_index, |
+ const MediaCodecLoop::InputData& data, |
+ MediaCodecStatus status = MEDIA_CODEC_OK) { |
+ EXPECT_CALL(Codec(), QueueInputBuffer(input_buffer_index, data.memory, |
+ data.length, data.presentation_time)) |
+ .Times(1) |
+ .WillOnce(Return(status)); |
+ } |
+ |
+ void ExpectProvideInputData(const MediaCodecLoop::InputData& data) { |
+ EXPECT_CALL(*client_, ProvideInputData()).WillOnce(Return(data)); |
+ } |
+ |
+ MediaCodecLoop::InputData BigBuckBunny() { |
+ MediaCodecLoop::InputData data; |
+ data.memory = reinterpret_cast<const uint8_t*>("big buck bunny"); |
+ data.length = 14; |
+ data.presentation_time = base::TimeDelta::FromSeconds(1); |
+ return data; |
+ } |
+ |
+ struct OutputBuffer { |
+ int index = 1; |
+ size_t offset = 0; |
+ size_t size = 1024; |
+ base::TimeDelta pts = base::TimeDelta::FromSeconds(1); |
+ bool eos = false; |
+ bool key_frame = true; |
+ }; |
+ |
+ struct EosOutputBuffer : public OutputBuffer { |
+ EosOutputBuffer() { eos = true; } |
+ }; |
+ |
+ void ExpectDequeueOutputBuffer(MediaCodecStatus status) { |
+ EXPECT_CALL(Codec(), DequeueOutputBuffer(_, _, _, _, _, _, _)) |
+ .WillOnce(Return(status)); |
+ } |
+ |
+ void ExpectDequeueOutputBuffer(const OutputBuffer& buffer) { |
+ EXPECT_CALL(Codec(), DequeueOutputBuffer(_, _, _, _, _, _, _)) |
+ .WillOnce(DoAll( |
+ SetArgPointee<1>(buffer.index), SetArgPointee<2>(buffer.offset), |
+ SetArgPointee<3>(buffer.size), SetArgPointee<4>(buffer.pts), |
+ SetArgPointee<5>(buffer.eos), SetArgPointee<6>(buffer.key_frame), |
+ Return(MEDIA_CODEC_OK))); |
+ } |
+ |
+ void ExpectOnDecodedFrame(const OutputBuffer& buf) { |
+ EXPECT_CALL(*client_, |
+ OnDecodedFrame( |
+ Field(&MediaCodecLoop::OutputBuffer::index, Eq(buf.index)))) |
+ .Times(1) |
+ .WillOnce(Return(true)); |
+ } |
+ |
+ MockMediaCodecBridge& Codec() { |
+ return *static_cast<MockMediaCodecBridge*>(codec_loop_->GetCodec()); |
+ } |
public: |
std::unique_ptr<MediaCodecLoop> codec_loop_; |
std::unique_ptr<MockMediaCodecLoopClient> client_; |
+ // TODO: how is the lifecycle of |clock_| handled? |task_runner_| can outlive |
+ // us, since it's a refptr. |
+ base::SimpleTestTickClock clock_; |
+ scoped_refptr<FakeSingleThreadTaskRunner> task_runner_; |
DISALLOW_COPY_AND_ASSIGN(MediaCodecLoopTest); |
}; |
@@ -36,9 +195,272 @@ class MediaCodecLoopTest : public testing::Test { |
TEST_F(MediaCodecLoopTest, TestConstructionWithNullCodec) { |
std::unique_ptr<MediaCodecBridge> codec; |
EXPECT_CALL(*client_, OnCodecLoopError()).Times(1); |
- codec_loop_.reset(new MediaCodecLoop(client_.get(), std::move(codec))); |
+ const int sdk_int = kLollipop; |
+ codec_loop_.reset( |
+ new MediaCodecLoop(sdk_int, client_.get(), std::move(codec))); |
+ // Do not WaitUntilIdle() here, since that assumes that we have a codec. |
ASSERT_FALSE(codec_loop_->GetCodec()); |
} |
+TEST_F(MediaCodecLoopTest, TestConstructionWithCodec) { |
+ ConstructCodecLoop(); |
+ ASSERT_EQ(codec_loop_->GetCodec(), &Codec()); |
+ WaitUntilIdle(ShouldBeIdle); |
+} |
+ |
+TEST_F(MediaCodecLoopTest, TestPendingWorkWithoutInput) { |
+ ConstructCodecLoop(); |
+ // MCL should try ask if there is pending input, and try to dequeue output. |
+ ExpectIsAnyInputPending(false); |
+ EXPECT_CALL(Codec(), DequeueOutputBuffer(_, _, _, _, _, _, _)) |
+ .Times(1) |
+ .WillOnce(Return(MEDIA_CODEC_DEQUEUE_OUTPUT_AGAIN_LATER)); |
+ codec_loop_->DoPendingWork(); |
+ WaitUntilIdle(ShouldNotBeIdle); |
+} |
+ |
+TEST_F(MediaCodecLoopTest, TestPendingWorkWithInput) { |
+ ConstructCodecLoop(); |
+ // MCL should try ask if there is pending input, and try to dequeue both an |
+ // output and input buffer. |
+ ExpectIsAnyInputPending(true); |
+ EXPECT_CALL(Codec(), DequeueOutputBuffer(_, _, _, _, _, _, _)).Times(1); |
+ EXPECT_CALL(Codec(), DequeueInputBuffer(_, _)).Times(1); |
+ codec_loop_->DoPendingWork(); |
+ WaitUntilIdle(ShouldNotBeIdle); |
+} |
+ |
+TEST_F(MediaCodecLoopTest, TestPendingWorkWithOutputBuffer) { |
+ ConstructCodecLoop(); |
+ { |
+ InSequence _s; |
+ |
+ // MCL will first request input, then try to dequeue output. |
+ ExpectIsAnyInputPending(false); |
+ OutputBuffer buf; |
+ ExpectDequeueOutputBuffer(buf); |
+ ExpectOnDecodedFrame(buf); |
+ |
+ // MCL will try again for another set of buffers before DoPendingWork() |
+ // returns. This is why we don't just leave them for WaitUntilIdle(). |
+ ExpectEmptyIOLoop(); |
+ } |
+ codec_loop_->DoPendingWork(); |
+ WaitUntilIdle(ShouldNotBeIdle); |
+} |
+ |
+TEST_F(MediaCodecLoopTest, TestQueueEos) { |
+ // Test sending an EOS to MCL => MCB =dequeue output=> MCL . |
+ ConstructCodecLoop(); |
+ { |
+ InSequence _s; |
+ |
+ ExpectIsAnyInputPending(true); |
+ int input_buffer_index = 123; |
+ ExpectDequeueInputBuffer(input_buffer_index); |
+ |
+ MediaCodecLoop::InputData data; |
+ data.is_eos = true; |
+ ExpectProvideInputData(data); |
+ EXPECT_CALL(Codec(), QueueEOS(input_buffer_index)); |
+ ExpectInputDataQueued(true); |
+ |
+ // Now send the EOS back on the output queue. |
+ EosOutputBuffer eos; |
+ ExpectDequeueOutputBuffer(eos); |
+ EXPECT_CALL(Codec(), ReleaseOutputBuffer(eos.index, false)); |
+ EXPECT_CALL(*client_, OnDecodedEos(_)).Times(1); |
+ |
+ // See TestUnqueuedEos. |
+ EXPECT_CALL(Codec(), DequeueOutputBuffer(_, _, _, _, _, _, _)) |
+ .Times(1) |
+ .WillOnce(Return(MEDIA_CODEC_DEQUEUE_OUTPUT_AGAIN_LATER)); |
+ } |
+ codec_loop_->DoPendingWork(); |
+ // Don't WaitUntilIdle() here. See TestUnqueuedEos. |
+} |
+ |
+TEST_F(MediaCodecLoopTest, TestQueueInputData) { |
+ // Send a buffer full of data into MCL and make sure that it gets queued with |
+ // MediaCodecBridge correctly. |
+ ConstructCodecLoop(); |
+ { |
+ InSequence _s; |
+ |
+ ExpectIsAnyInputPending(true); |
+ int input_buffer_index = 123; |
+ ExpectDequeueInputBuffer(input_buffer_index); |
+ |
+ MediaCodecLoop::InputData data = BigBuckBunny(); |
+ ExpectProvideInputData(data); |
+ |
+ // MCL should send the buffer into MediaCodec and notify the client. |
+ ExpectQueueInputBuffer(input_buffer_index, data); |
+ ExpectInputDataQueued(true); |
+ |
+ // MCL will try to dequeue an output buffer too. |
+ EXPECT_CALL(Codec(), DequeueOutputBuffer(_, _, _, _, _, _, _)) |
+ .Times(1) |
+ .WillOnce(Return(MEDIA_CODEC_DEQUEUE_OUTPUT_AGAIN_LATER)); |
+ |
+ // DoPendingWork will try again. |
+ ExpectEmptyIOLoop(); |
+ } |
+ codec_loop_->DoPendingWork(); |
+ WaitUntilIdle(ShouldNotBeIdle); |
+} |
+ |
+TEST_F(MediaCodecLoopTest, TestQueueInputDataFails) { |
+ // Send a buffer full of data into MCL, but MediaCodecBridge fails to queue |
+ // it successfully. |
+ ConstructCodecLoop(); |
+ { |
+ InSequence _s; |
+ |
+ ExpectIsAnyInputPending(true); |
+ int input_buffer_index = 123; |
+ ExpectDequeueInputBuffer(input_buffer_index); |
+ |
+ MediaCodecLoop::InputData data = BigBuckBunny(); |
+ ExpectProvideInputData(data); |
+ |
+ // MCL should send the buffer into MediaCodec and notify the client. |
+ ExpectQueueInputBuffer(input_buffer_index, data, MEDIA_CODEC_ERROR); |
+ ExpectInputDataQueued(false); |
+ EXPECT_CALL(*client_, OnCodecLoopError()).Times(1); |
+ } |
+ codec_loop_->DoPendingWork(); |
+ // MCL is now in the error state. |
+} |
+ |
+TEST_F(MediaCodecLoopTest, TestQueueInputDataTryAgain) { |
+ // Signal that there is input pending, but don't provide an input buffer. |
+ ConstructCodecLoop(); |
+ { |
+ InSequence _s; |
+ |
+ ExpectIsAnyInputPending(true); |
+ ExpectDequeueInputBuffer(-1, MEDIA_CODEC_DEQUEUE_INPUT_AGAIN_LATER); |
+ // MCL will try for output too. |
+ ExpectDequeueOutputBuffer(MEDIA_CODEC_DEQUEUE_OUTPUT_AGAIN_LATER); |
+ } |
+ codec_loop_->DoPendingWork(); |
+ // Note that the client might not be allowed to change from "input pending" |
+ // to "no input pending" without actually being asked for input. For now, |
+ // MCL doesn't assume this. |
+ WaitUntilIdle(ShouldNotBeIdle); |
+} |
+ |
+TEST_F(MediaCodecLoopTest, TestSeveralPendingIOBuffers) { |
+ // Provide several input and output buffers to MCL. |
+ ConstructCodecLoop(); |
+ int input_buffer_index = 123; |
+ const int num_loops = 4; |
+ |
+ InSequence _s; |
+ for (int i = 0; i < num_loops; i++, input_buffer_index++) { |
+ ExpectIsAnyInputPending(true); |
+ ExpectDequeueInputBuffer(input_buffer_index); |
+ |
+ MediaCodecLoop::InputData data = BigBuckBunny(); |
+ ExpectProvideInputData(data); |
+ |
+ ExpectQueueInputBuffer(input_buffer_index, data); |
+ ExpectInputDataQueued(true); |
+ |
+ OutputBuffer buffer; |
+ buffer.index = i; |
+ buffer.size += i; |
+ buffer.pts = base::TimeDelta::FromSeconds(i + 1); |
+ ExpectDequeueOutputBuffer(buffer); |
+ ExpectOnDecodedFrame(buffer); |
+ } |
+ |
+ ExpectEmptyIOLoop(); |
+ |
+ codec_loop_->DoPendingWork(); |
+} |
+ |
+TEST_F(MediaCodecLoopTest, TestTryFlushOnJellyBeanMR2) { |
+ // On JB MR2+ MCL should be willing to use MediaCodecBridge::Flush. |
+ ConstructCodecLoop(kJellyBeanMR2); |
+ EXPECT_CALL(Codec(), Flush()).Times(1).WillOnce(Return(MEDIA_CODEC_OK)); |
+ ASSERT_TRUE(codec_loop_->TryFlush()); |
+} |
+ |
+TEST_F(MediaCodecLoopTest, TestTryFlushAfterJellyBeanMR2Fails) { |
+ // On JB MR2+, MCL should be willing to use MediaCodecBridge::Flush. Try |
+ // that, but make Flush fail. |
+ ConstructCodecLoop(kJellyBeanMR2); |
+ EXPECT_CALL(Codec(), Flush()).Times(1).WillOnce(Return(MEDIA_CODEC_ERROR)); |
+ EXPECT_CALL(*client_, OnCodecLoopError()).Times(1); |
+ ASSERT_FALSE(codec_loop_->TryFlush()); |
+} |
+ |
+TEST_F(MediaCodecLoopTest, TestTryFlushOnJellyBeanMR1) { |
+ // In JB MR1, MCL should not be willing to use MediaCodecBridge::Flush. |
+ ConstructCodecLoop(kJellyBeanMR1); |
+ ASSERT_FALSE(codec_loop_->TryFlush()); |
+} |
+ |
+TEST_F(MediaCodecLoopTest, TestOnKeyAdded) { |
+ ConstructCodecLoop(); |
+ |
+ int input_buffer_index = 123; |
+ MediaCodecLoop::InputData data = BigBuckBunny(); |
+ |
+ // First provide input, but have MediaCodecBridge require a key. |
+ { |
+ InSequence _s; |
+ |
+ // First DoPendingWork() |
+ ExpectIsAnyInputPending(true); |
+ ExpectDequeueInputBuffer(input_buffer_index); |
+ |
+ ExpectProvideInputData(data); |
+ |
+ // Notify MCL that it's missing the key. |
+ ExpectQueueInputBuffer(input_buffer_index, data, MEDIA_CODEC_NO_KEY); |
+ |
+ // MCL should now try for output buffers. |
+ ExpectDequeueOutputBuffer(MEDIA_CODEC_DEQUEUE_OUTPUT_AGAIN_LATER); |
+ |
+ // MCL will try again, since trying to queue the input buffer is considered |
+ // doing work, for some reason. It would be nice to make this optional. |
+ // Note that it should not ask us for more input, since it has not yet sent |
+ // the buffer we just provided. |
+ ExpectDequeueOutputBuffer(MEDIA_CODEC_DEQUEUE_OUTPUT_AGAIN_LATER); |
+ } |
+ codec_loop_->DoPendingWork(); |
+ |
+ // Try again, to be sure that MCL doesn't request more input. Note that this |
+ // is also done in the above loop, but that one could be made optional. This |
+ // forces MCL to try again as part of an entirely new DoPendingWork cycle. |
+ { |
+ InSequence _s; |
+ // MCL should only try for output buffers, since it's still waiting for a |
+ // key to be added. |
+ ExpectDequeueOutputBuffer(MEDIA_CODEC_DEQUEUE_OUTPUT_AGAIN_LATER); |
+ } |
+ codec_loop_->DoPendingWork(); |
+ |
+ // When we add the key, MCL will DoPending work again. This time, it should |
+ // succeed since the key has been added. |
+ { |
+ InSequence _s; |
+ // MCL should not retain the original pointer. |
+ data.memory = nullptr; |
+ ExpectQueueInputBuffer(input_buffer_index, data); |
+ ExpectInputDataQueued(true); |
+ ExpectDequeueOutputBuffer(MEDIA_CODEC_DEQUEUE_OUTPUT_AGAIN_LATER); |
+ |
+ // MCL did work, so it will try again. |
+ ExpectEmptyIOLoop(); |
+ } |
+ |
+ codec_loop_->OnKeyAdded(); |
+ WaitUntilIdle(ShouldNotBeIdle); |
+} |
+ |
} // namespace media |