| 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
|
|
|