Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(484)

Unified Diff: media/base/android/media_codec_loop_unittest.cc

Issue 2132653002: MediaCodecLoop unit tests (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: cl feedback Created 4 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « media/base/android/media_codec_loop.cc ('k') | media/base/android/media_codec_util.h » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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
« no previous file with comments | « media/base/android/media_codec_loop.cc ('k') | media/base/android/media_codec_util.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698