blob: afd0a87a4478f81ffcb99331cf281d0555efced4 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/mojo/common/mojo_decoder_buffer_converter.h"
#include <stdint.h>
#include <memory>
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/stl_util.h"
#include "base/test/mock_callback.h"
#include "media/base/decoder_buffer.h"
#include "media/base/decrypt_config.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace media {
namespace {
uint32_t kDefaultDataPipeCapacityBytes = 1024;
MATCHER_P(MatchesDecoderBuffer, buffer, "") {
DCHECK(arg);
return arg->MatchesForTesting(*buffer);
}
class MojoDecoderBufferConverter {
public:
MojoDecoderBufferConverter(
uint32_t data_pipe_capacity_bytes = kDefaultDataPipeCapacityBytes) {
mojo::DataPipe data_pipe(data_pipe_capacity_bytes);
writer = std::make_unique<MojoDecoderBufferWriter>(
std::move(data_pipe.producer_handle));
reader = std::make_unique<MojoDecoderBufferReader>(
std::move(data_pipe.consumer_handle));
}
void ConvertAndVerify(scoped_refptr<DecoderBuffer> media_buffer) {
base::RunLoop run_loop;
base::MockCallback<MojoDecoderBufferReader::ReadCB> mock_cb;
EXPECT_CALL(mock_cb, Run(MatchesDecoderBuffer(media_buffer)))
.WillOnce(testing::InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
mojom::DecoderBufferPtr mojo_buffer =
writer->WriteDecoderBuffer(media_buffer);
reader->ReadDecoderBuffer(std::move(mojo_buffer), mock_cb.Get());
run_loop.Run();
}
std::unique_ptr<MojoDecoderBufferWriter> writer;
std::unique_ptr<MojoDecoderBufferReader> reader;
};
} // namespace
TEST(MojoDecoderBufferConverterTest, ConvertDecoderBuffer_Normal) {
base::MessageLoop message_loop;
const uint8_t kData[] = "hello, world";
const uint8_t kSideData[] = "sideshow bob";
const size_t kDataSize = base::size(kData);
const size_t kSideDataSize = base::size(kSideData);
scoped_refptr<DecoderBuffer> buffer(DecoderBuffer::CopyFrom(
reinterpret_cast<const uint8_t*>(&kData), kDataSize,
reinterpret_cast<const uint8_t*>(&kSideData), kSideDataSize));
buffer->set_timestamp(base::TimeDelta::FromMilliseconds(123));
buffer->set_duration(base::TimeDelta::FromMilliseconds(456));
buffer->set_discard_padding(
DecoderBuffer::DiscardPadding(base::TimeDelta::FromMilliseconds(5),
base::TimeDelta::FromMilliseconds(6)));
MojoDecoderBufferConverter converter;
converter.ConvertAndVerify(buffer);
}
TEST(MojoDecoderBufferConverterTest, ConvertDecoderBuffer_EOS) {
base::MessageLoop message_loop;
scoped_refptr<DecoderBuffer> buffer(DecoderBuffer::CreateEOSBuffer());
MojoDecoderBufferConverter converter;
converter.ConvertAndVerify(buffer);
}
// TODO(xhwang): Investigate whether we can get rid of zero-byte-buffer.
// See http://crbug.com/663438
TEST(MojoDecoderBufferConverterTest, ConvertDecoderBuffer_ZeroByteBuffer) {
base::MessageLoop message_loop;
scoped_refptr<DecoderBuffer> buffer(new DecoderBuffer(0));
MojoDecoderBufferConverter converter;
converter.ConvertAndVerify(buffer);
}
TEST(MojoDecoderBufferConverterTest, ConvertDecoderBuffer_KeyFrame) {
base::MessageLoop message_loop;
const uint8_t kData[] = "hello, world";
const size_t kDataSize = base::size(kData);
scoped_refptr<DecoderBuffer> buffer(DecoderBuffer::CopyFrom(
reinterpret_cast<const uint8_t*>(&kData), kDataSize));
buffer->set_is_key_frame(true);
EXPECT_TRUE(buffer->is_key_frame());
MojoDecoderBufferConverter converter;
converter.ConvertAndVerify(buffer);
}
TEST(MojoDecoderBufferConverterTest, ConvertDecoderBuffer_EncryptedBuffer) {
base::MessageLoop message_loop;
const uint8_t kData[] = "hello, world";
const size_t kDataSize = base::size(kData);
const char kKeyId[] = "00112233445566778899aabbccddeeff";
const char kIv[] = "0123456789abcdef";
std::vector<SubsampleEntry> subsamples;
subsamples.push_back(SubsampleEntry(10, 20));
subsamples.push_back(SubsampleEntry(30, 40));
subsamples.push_back(SubsampleEntry(50, 60));
scoped_refptr<DecoderBuffer> buffer(DecoderBuffer::CopyFrom(
reinterpret_cast<const uint8_t*>(&kData), kDataSize));
buffer->set_decrypt_config(
DecryptConfig::CreateCencConfig(kKeyId, kIv, subsamples));
{
MojoDecoderBufferConverter converter;
converter.ConvertAndVerify(buffer);
}
// Test 'cbcs'.
buffer->set_decrypt_config(DecryptConfig::CreateCbcsConfig(
kKeyId, kIv, subsamples, EncryptionPattern(5, 6)));
{
MojoDecoderBufferConverter converter;
converter.ConvertAndVerify(buffer);
}
// Test unencrypted. This is used for clear buffer in an encrypted stream.
buffer->set_decrypt_config(nullptr);
{
MojoDecoderBufferConverter converter;
converter.ConvertAndVerify(buffer);
}
}
// This test verifies that a DecoderBuffer larger than data-pipe capacity
// can be transmitted properly.
TEST(MojoDecoderBufferConverterTest, Chunked) {
base::MessageLoop message_loop;
const uint8_t kData[] = "Lorem ipsum dolor sit amet, consectetur cras amet";
const size_t kDataSize = base::size(kData);
scoped_refptr<DecoderBuffer> buffer =
DecoderBuffer::CopyFrom(kData, kDataSize);
MojoDecoderBufferConverter converter(kDataSize / 3);
converter.ConvertAndVerify(buffer);
}
// This test verifies that MojoDecoderBufferReader::ReadCB is called with a
// NULL DecoderBuffer if data pipe is closed during transmission.
TEST(MojoDecoderBufferConverterTest, WriterSidePipeError) {
base::MessageLoop message_loop;
const uint8_t kData[] = "Lorem ipsum dolor sit amet, consectetur cras amet";
const size_t kDataSize = base::size(kData);
scoped_refptr<DecoderBuffer> media_buffer =
DecoderBuffer::CopyFrom(kData, kDataSize);
// Verify that ReadCB is called with a NULL decoder buffer.
base::RunLoop run_loop;
base::MockCallback<MojoDecoderBufferReader::ReadCB> mock_cb;
EXPECT_CALL(mock_cb, Run(testing::IsNull()))
.WillOnce(testing::InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
// Make data pipe with capacity smaller than decoder buffer so that only
// partial data is written.
MojoDecoderBufferConverter converter(kDataSize / 2);
mojom::DecoderBufferPtr mojo_buffer =
converter.writer->WriteDecoderBuffer(media_buffer);
converter.reader->ReadDecoderBuffer(std::move(mojo_buffer), mock_cb.Get());
// Before the entire data is transmitted, close the handle on writer side.
// The reader side will get notified and report the error.
converter.writer.reset();
run_loop.Run();
}
// This test verifies that MojoDecoderBuffer supports concurrent writes and
// reads.
TEST(MojoDecoderBufferConverterTest, ConcurrentDecoderBuffers) {
base::MessageLoop message_loop;
base::RunLoop run_loop;
// Prevent all of the buffers from fitting at once to exercise the chunking
// logic.
MojoDecoderBufferConverter converter(4);
// Three buffers: normal, EOS, normal.
const uint8_t kData[] = "Hello, world";
const size_t kDataSize = base::size(kData);
scoped_refptr<DecoderBuffer> media_buffer1 =
DecoderBuffer::CopyFrom(kData, kDataSize);
scoped_refptr<DecoderBuffer> media_buffer2(DecoderBuffer::CreateEOSBuffer());
scoped_refptr<DecoderBuffer> media_buffer3 =
DecoderBuffer::CopyFrom(kData, kDataSize);
// Expect the read callbacks to be issued in the same order.
::testing::InSequence scoper;
base::MockCallback<MojoDecoderBufferReader::ReadCB> mock_cb1;
base::MockCallback<MojoDecoderBufferReader::ReadCB> mock_cb2;
base::MockCallback<MojoDecoderBufferReader::ReadCB> mock_cb3;
EXPECT_CALL(mock_cb1, Run(MatchesDecoderBuffer(media_buffer1)));
EXPECT_CALL(mock_cb2, Run(MatchesDecoderBuffer(media_buffer2)));
EXPECT_CALL(mock_cb3, Run(MatchesDecoderBuffer(media_buffer3)))
.WillOnce(testing::InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
// Write all of the buffers at once.
mojom::DecoderBufferPtr mojo_buffer1 =
converter.writer->WriteDecoderBuffer(media_buffer1);
mojom::DecoderBufferPtr mojo_buffer2 =
converter.writer->WriteDecoderBuffer(media_buffer2);
mojom::DecoderBufferPtr mojo_buffer3 =
converter.writer->WriteDecoderBuffer(media_buffer3);
// Read all of the buffers at once.
// Technically could be satisfied by ReadDecoderBuffer() blocking, but that's
// actually a valid implementation. (Quitting the |run_loop| won't work
// properly with that setup though.)
converter.reader->ReadDecoderBuffer(std::move(mojo_buffer1), mock_cb1.Get());
converter.reader->ReadDecoderBuffer(std::move(mojo_buffer2), mock_cb2.Get());
converter.reader->ReadDecoderBuffer(std::move(mojo_buffer3), mock_cb3.Get());
run_loop.Run();
}
TEST(MojoDecoderBufferConverterTest, FlushWithoutRead) {
base::MessageLoop message_loop;
base::RunLoop run_loop;
base::MockCallback<base::OnceClosure> mock_flush_cb;
EXPECT_CALL(mock_flush_cb, Run());
MojoDecoderBufferConverter converter;
converter.reader->Flush(mock_flush_cb.Get());
run_loop.RunUntilIdle();
}
TEST(MojoDecoderBufferConverterTest, FlushAfterRead) {
base::MessageLoop message_loop;
base::RunLoop run_loop;
const uint8_t kData[] = "Lorem ipsum dolor sit amet, consectetur cras amet";
const size_t kDataSize = base::size(kData);
scoped_refptr<DecoderBuffer> media_buffer =
DecoderBuffer::CopyFrom(kData, kDataSize);
MojoDecoderBufferConverter converter(kDataSize / 3);
converter.ConvertAndVerify(media_buffer);
base::MockCallback<base::OnceClosure> mock_flush_cb;
EXPECT_CALL(mock_flush_cb, Run())
.WillOnce(testing::InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
converter.reader->Flush(mock_flush_cb.Get());
run_loop.Run();
}
TEST(MojoDecoderBufferConverterTest, FlushBeforeRead) {
base::MessageLoop message_loop;
base::RunLoop run_loop;
const uint8_t kData[] = "Lorem ipsum dolor sit amet, consectetur cras amet";
const size_t kDataSize = base::size(kData);
scoped_refptr<DecoderBuffer> media_buffer =
DecoderBuffer::CopyFrom(kData, kDataSize);
MojoDecoderBufferConverter converter;
base::MockCallback<MojoDecoderBufferReader::ReadCB> mock_read_cb;
base::MockCallback<base::OnceClosure> mock_flush_cb;
::testing::InSequence sequence;
EXPECT_CALL(mock_flush_cb, Run());
EXPECT_CALL(mock_read_cb, Run(MatchesDecoderBuffer(media_buffer)))
.WillOnce(testing::InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
// Write, Flush, then Read.
mojom::DecoderBufferPtr mojo_buffer =
converter.writer->WriteDecoderBuffer(media_buffer);
converter.reader->Flush(mock_flush_cb.Get());
converter.reader->ReadDecoderBuffer(std::move(mojo_buffer),
mock_read_cb.Get());
run_loop.Run();
}
TEST(MojoDecoderBufferConverterTest, FlushBeforeChunkedRead) {
base::MessageLoop message_loop;
base::RunLoop run_loop;
const uint8_t kData[] = "Lorem ipsum dolor sit amet, consectetur cras amet";
const size_t kDataSize = base::size(kData);
scoped_refptr<DecoderBuffer> media_buffer =
DecoderBuffer::CopyFrom(kData, kDataSize);
MojoDecoderBufferConverter converter(kDataSize / 3);
base::MockCallback<MojoDecoderBufferReader::ReadCB> mock_read_cb;
base::MockCallback<base::OnceClosure> mock_flush_cb;
// Read callback should be fired after reset callback.
::testing::InSequence sequence;
EXPECT_CALL(mock_flush_cb, Run());
EXPECT_CALL(mock_read_cb, Run(MatchesDecoderBuffer(media_buffer)))
.WillOnce(testing::InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
// Write, reset, then read.
mojom::DecoderBufferPtr mojo_buffer =
converter.writer->WriteDecoderBuffer(media_buffer);
converter.reader->Flush(mock_flush_cb.Get());
converter.reader->ReadDecoderBuffer(std::move(mojo_buffer),
mock_read_cb.Get());
run_loop.Run();
}
TEST(MojoDecoderBufferConverterTest, FlushDuringChunkedRead) {
base::MessageLoop message_loop;
base::RunLoop run_loop;
const uint8_t kData[] = "Lorem ipsum dolor sit amet, consectetur cras amet";
const size_t kDataSize = base::size(kData);
scoped_refptr<DecoderBuffer> media_buffer =
DecoderBuffer::CopyFrom(kData, kDataSize);
MojoDecoderBufferConverter converter(kDataSize / 3);
base::MockCallback<MojoDecoderBufferReader::ReadCB> mock_read_cb;
base::MockCallback<base::OnceClosure> mock_flush_cb;
// Flush callback should be fired after read callback.
::testing::InSequence sequence;
EXPECT_CALL(mock_read_cb, Run(MatchesDecoderBuffer(media_buffer)));
EXPECT_CALL(mock_flush_cb, Run())
.WillOnce(testing::InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
// Write, read, then reset.
mojom::DecoderBufferPtr mojo_buffer =
converter.writer->WriteDecoderBuffer(media_buffer);
converter.reader->ReadDecoderBuffer(std::move(mojo_buffer),
mock_read_cb.Get());
converter.reader->Flush(mock_flush_cb.Get());
run_loop.Run();
}
TEST(MojoDecoderBufferConverterTest, FlushDuringConcurrentReads) {
base::MessageLoop message_loop;
base::RunLoop run_loop;
// Prevent all of the buffers from fitting at once to exercise the chunking
// logic.
MojoDecoderBufferConverter converter(4);
auto& writer = converter.writer;
auto& reader = converter.reader;
// Three buffers: normal, EOS, normal.
const uint8_t kData[] = "Hello, world";
const size_t kDataSize = base::size(kData);
auto media_buffer1 = DecoderBuffer::CopyFrom(kData, kDataSize);
auto media_buffer2 = DecoderBuffer::CreateEOSBuffer();
auto media_buffer3 = DecoderBuffer::CopyFrom(kData, kDataSize);
// Expect the read callbacks to be issued in the same order.
base::MockCallback<MojoDecoderBufferReader::ReadCB> mock_read_cb1;
base::MockCallback<MojoDecoderBufferReader::ReadCB> mock_read_cb2;
base::MockCallback<MojoDecoderBufferReader::ReadCB> mock_read_cb3;
base::MockCallback<base::OnceClosure> mock_flush_cb;
::testing::InSequence scoper;
EXPECT_CALL(mock_read_cb1, Run(MatchesDecoderBuffer(media_buffer1)));
EXPECT_CALL(mock_read_cb2, Run(MatchesDecoderBuffer(media_buffer2)));
EXPECT_CALL(mock_read_cb3, Run(MatchesDecoderBuffer(media_buffer3)));
EXPECT_CALL(mock_flush_cb, Run())
.WillOnce(testing::InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
// Write all of the buffers at once.
auto mojo_buffer1 = writer->WriteDecoderBuffer(media_buffer1);
auto mojo_buffer2 = writer->WriteDecoderBuffer(media_buffer2);
auto mojo_buffer3 = writer->WriteDecoderBuffer(media_buffer3);
// Read all of the buffers at once.
reader->ReadDecoderBuffer(std::move(mojo_buffer1), mock_read_cb1.Get());
reader->ReadDecoderBuffer(std::move(mojo_buffer2), mock_read_cb2.Get());
reader->ReadDecoderBuffer(std::move(mojo_buffer3), mock_read_cb3.Get());
reader->Flush(mock_flush_cb.Get());
// No ReadDecoderBuffer() can be called during pending reset.
run_loop.Run();
}
} // namespace media