blob: 30aafbbb3e519a0f6802617b739b1f0c211affb2 [file] [log] [blame]
// Copyright 2017 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 <memory>
#include <vector>
#include <stdint.h>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback_forward.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/scoped_refptr.h"
#include "base/single_thread_task_runner.h"
#include "base/test/mock_callback.h"
#include "base/test/scoped_task_environment.h"
#include "base/time/time.h"
#include "gpu/command_buffer/common/mailbox_holder.h"
#include "media/base/decode_status.h"
#include "media/base/decoder_buffer.h"
#include "media/base/gmock_callback_support.h"
#include "media/base/media_log.h"
#include "media/base/mock_media_log.h"
#include "media/base/test_helpers.h"
#include "media/base/video_decoder.h"
#include "media/base/video_frame.h"
#include "media/mojo/clients/mojo_video_decoder.h"
#include "media/mojo/services/mojo_cdm_service_context.h"
#include "media/mojo/services/mojo_media_client.h"
#include "media/mojo/services/mojo_video_decoder_service.h"
#include "mojo/public/cpp/bindings/strong_binding.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/color_space.h"
using ::testing::_;
using ::testing::AtLeast;
using ::testing::HasSubstr;
using ::testing::Invoke;
using ::testing::InSequence;
using ::testing::Mock;
using ::testing::Return;
using ::testing::SaveArg;
using ::testing::StrictMock;
namespace media {
namespace {
const int kMaxDecodeRequests = 4;
// A mock VideoDecoder with helpful default functionality.
// TODO(sandersd): Determine how best to merge this with MockVideoDecoder
// declared in mock_filters.h.
class MockVideoDecoder : public VideoDecoder {
public:
MockVideoDecoder() {
// Treat const getters like a NiceMock.
EXPECT_CALL(*this, GetDisplayName())
.WillRepeatedly(Return("MockVideoDecoder"));
EXPECT_CALL(*this, NeedsBitstreamConversion())
.WillRepeatedly(Return(false));
EXPECT_CALL(*this, CanReadWithoutStalling()).WillRepeatedly(Return(true));
EXPECT_CALL(*this, GetMaxDecodeRequests())
.WillRepeatedly(Return(kMaxDecodeRequests));
// For regular methods, only configure a default action.
ON_CALL(*this, Decode(_, _))
.WillByDefault(Invoke(this, &MockVideoDecoder::DoDecode));
ON_CALL(*this, Reset(_))
.WillByDefault(Invoke(this, &MockVideoDecoder::DoReset));
}
// Re-declare as public.
~MockVideoDecoder() override {}
// media::VideoDecoder implementation
MOCK_CONST_METHOD0(GetDisplayName, std::string());
// Initialize() records values before delegating to the mock method.
void Initialize(
const VideoDecoderConfig& config,
bool /* low_delay */,
CdmContext* /* cdm_context */,
const InitCB& init_cb,
const OutputCB& output_cb,
const WaitingForDecryptionKeyCB& /* waiting_for_decryption_key_cb */)
override {
config_ = config;
output_cb_ = output_cb;
DoInitialize(init_cb);
}
MOCK_METHOD2(Decode,
void(scoped_refptr<DecoderBuffer> buffer, const DecodeCB&));
MOCK_METHOD1(Reset, void(const base::Closure&));
MOCK_CONST_METHOD0(NeedsBitstreamConversion, bool());
MOCK_CONST_METHOD0(CanReadWithoutStalling, bool());
MOCK_CONST_METHOD0(GetMaxDecodeRequests, int());
// Mock helpers.
MOCK_METHOD1(DoInitialize, void(const InitCB&));
VideoFrame::ReleaseMailboxCB GetReleaseMailboxCB() {
DidGetReleaseMailboxCB();
return std::move(release_mailbox_cb);
}
MOCK_METHOD0(DidGetReleaseMailboxCB, void());
VideoFrame::ReleaseMailboxCB release_mailbox_cb;
// Returns an output frame immediately.
// TODO(sandersd): Extend to support tests of MojoVideoFrame frames.
void DoDecode(scoped_refptr<DecoderBuffer> buffer,
const DecodeCB& decode_cb) {
if (!buffer->end_of_stream()) {
gpu::MailboxHolder mailbox_holders[VideoFrame::kMaxPlanes];
mailbox_holders[0].mailbox.name[0] = 1;
scoped_refptr<VideoFrame> frame = VideoFrame::WrapNativeTextures(
PIXEL_FORMAT_ARGB, mailbox_holders, GetReleaseMailboxCB(),
config_.coded_size(), config_.visible_rect(), config_.natural_size(),
buffer->timestamp());
frame->metadata()->SetBoolean(VideoFrameMetadata::POWER_EFFICIENT, true);
output_cb_.Run(frame);
}
// |decode_cb| must not be called from the same stack.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::Bind(decode_cb, DecodeStatus::OK));
}
void DoReset(const base::Closure& reset_cb) {
// |reset_cb| must not be called from the same stack.
base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, reset_cb);
}
private:
// Destructing a std::unique_ptr<VideoDecoder>(this) is a no-op.
// TODO(sandersd): After this, any method call is an error. Implement checks
// for that.
void Destroy() override { DVLOG(1) << __func__ << "(): Ignored"; }
VideoDecoderConfig config_;
OutputCB output_cb_;
DISALLOW_COPY_AND_ASSIGN(MockVideoDecoder);
};
// Proxies CreateVideoDecoder() to a callback.
class FakeMojoMediaClient : public MojoMediaClient {
public:
using CreateVideoDecoderCB =
base::Callback<std::unique_ptr<VideoDecoder>(MediaLog*)>;
FakeMojoMediaClient(CreateVideoDecoderCB create_video_decoder_cb)
: create_video_decoder_cb_(std::move(create_video_decoder_cb)) {}
std::unique_ptr<VideoDecoder> CreateVideoDecoder(
scoped_refptr<base::SingleThreadTaskRunner> task_runner,
MediaLog* media_log,
mojom::CommandBufferIdPtr command_buffer_id,
RequestOverlayInfoCB request_overlay_info_cb,
const gfx::ColorSpace& target_color_space) override {
return create_video_decoder_cb_.Run(media_log);
}
private:
CreateVideoDecoderCB create_video_decoder_cb_;
DISALLOW_COPY_AND_ASSIGN(FakeMojoMediaClient);
};
} // namespace
class MojoVideoDecoderIntegrationTest : public ::testing::Test {
public:
MojoVideoDecoderIntegrationTest()
: mojo_media_client_(
base::Bind(&MojoVideoDecoderIntegrationTest::CreateVideoDecoder,
base::Unretained(this))) {}
void TearDown() override {
if (client_) {
client_.reset();
RunUntilIdle();
}
}
protected:
void RunUntilIdle() { scoped_task_environment_.RunUntilIdle(); }
void SetWriterCapacity(uint32_t capacity) { writer_capacity_ = capacity; }
mojom::VideoDecoderPtr CreateRemoteVideoDecoder() {
mojom::VideoDecoderPtr remote_video_decoder;
mojo::MakeStrongBinding(
std::make_unique<MojoVideoDecoderService>(&mojo_media_client_,
&mojo_cdm_service_context_),
mojo::MakeRequest(&remote_video_decoder));
return remote_video_decoder;
}
void CreateClient() {
DCHECK(!client_);
// TODO(sandersd): Pass a GpuVideoAcceleratorFactories so that the cache can
// be tested.
client_ = std::make_unique<MojoVideoDecoder>(
base::ThreadTaskRunnerHandle::Get(), nullptr, &client_media_log_,
CreateRemoteVideoDecoder(), RequestOverlayInfoCB(), gfx::ColorSpace());
if (writer_capacity_)
client_->set_writer_capacity_for_testing(writer_capacity_);
}
bool Initialize() {
CreateClient();
EXPECT_CALL(*decoder_, DoInitialize(_)).WillOnce(RunCallback<0>(true));
bool result = false;
StrictMock<base::MockCallback<VideoDecoder::InitCB>> init_cb;
EXPECT_CALL(init_cb, Run(_)).WillOnce(SaveArg<0>(&result));
client_->Initialize(TestVideoConfig::NormalH264(), false, nullptr,
init_cb.Get(), output_cb_.Get(), base::NullCallback());
RunUntilIdle();
return result;
}
DecodeStatus Decode(scoped_refptr<DecoderBuffer> buffer,
VideoFrame::ReleaseMailboxCB release_cb =
VideoFrame::ReleaseMailboxCB()) {
DecodeStatus result = DecodeStatus::DECODE_ERROR;
if (!buffer->end_of_stream()) {
decoder_->release_mailbox_cb = std::move(release_cb);
EXPECT_CALL(*decoder_, DidGetReleaseMailboxCB());
}
EXPECT_CALL(*decoder_, Decode(_, _));
StrictMock<base::MockCallback<VideoDecoder::DecodeCB>> decode_cb;
EXPECT_CALL(decode_cb, Run(_)).WillOnce(SaveArg<0>(&result));
client_->Decode(buffer, decode_cb.Get());
RunUntilIdle();
return result;
}
scoped_refptr<DecoderBuffer> CreateKeyframe(int64_t timestamp_ms) {
// Use 32 bytes to simulated chunked write (with capacity 10; see below).
std::vector<uint8_t> data(32, 0);
scoped_refptr<DecoderBuffer> buffer =
DecoderBuffer::CopyFrom(data.data(), data.size());
buffer->set_timestamp(base::TimeDelta::FromMilliseconds(timestamp_ms));
buffer->set_duration(base::TimeDelta::FromMilliseconds(10));
buffer->set_is_key_frame(true);
return buffer;
}
// Callback that |client_| will deliver VideoFrames to.
StrictMock<base::MockCallback<VideoDecoder::OutputCB>> output_cb_;
// MojoVideoDecoder (client) under test.
std::unique_ptr<MojoVideoDecoder> client_;
// MediaLog that |client_| will deliver log events to.
StrictMock<MockMediaLog> client_media_log_;
// VideoDecoder (impl used by service) under test.
std::unique_ptr<MockVideoDecoder> decoder_ =
std::make_unique<StrictMock<MockVideoDecoder>>();
// MediaLog that the service has provided to |decoder_|. This should be
// proxied to |client_media_log_|.
MediaLog* decoder_media_log_ = nullptr;
private:
// Passes |decoder_| to the service.
std::unique_ptr<VideoDecoder> CreateVideoDecoder(MediaLog* media_log) {
DCHECK(!decoder_media_log_);
decoder_media_log_ = media_log;
// Since MockVideoDecoder::Destroy() is a no-op, this doesn't actually
// transfer ownership.
return std::unique_ptr<VideoDecoder>(decoder_.get());
}
base::test::ScopedTaskEnvironment scoped_task_environment_;
// Capacity that will be used for the MojoDecoderBufferWriter.
uint32_t writer_capacity_ = 0;
MojoCdmServiceContext mojo_cdm_service_context_;
// Provides |decoder_| to the service.
FakeMojoMediaClient mojo_media_client_;
DISALLOW_COPY_AND_ASSIGN(MojoVideoDecoderIntegrationTest);
};
TEST_F(MojoVideoDecoderIntegrationTest, CreateAndDestroy) {}
TEST_F(MojoVideoDecoderIntegrationTest, GetSupportedConfigs) {
mojom::VideoDecoderPtr remote_video_decoder = CreateRemoteVideoDecoder();
StrictMock<
base::MockCallback<mojom::VideoDecoder::GetSupportedConfigsCallback>>
callback;
// TODO(sandersd): Expect there to be an entry.
EXPECT_CALL(callback, Run(_));
remote_video_decoder->GetSupportedConfigs(callback.Get());
RunUntilIdle();
}
TEST_F(MojoVideoDecoderIntegrationTest, Initialize) {
ASSERT_TRUE(Initialize());
EXPECT_EQ(client_->GetDisplayName(), "MojoVideoDecoder");
EXPECT_EQ(client_->NeedsBitstreamConversion(), false);
EXPECT_EQ(client_->CanReadWithoutStalling(), true);
EXPECT_EQ(client_->GetMaxDecodeRequests(), kMaxDecodeRequests);
}
TEST_F(MojoVideoDecoderIntegrationTest, MediaLogIsProxied) {
ASSERT_TRUE(Initialize());
EXPECT_MEDIA_LOG_ON(client_media_log_, HasSubstr("\"test\""));
MEDIA_LOG(DEBUG, decoder_media_log_) << "test";
RunUntilIdle();
}
TEST_F(MojoVideoDecoderIntegrationTest, Decode) {
ASSERT_TRUE(Initialize());
EXPECT_CALL(output_cb_, Run(_));
ASSERT_EQ(Decode(CreateKeyframe(0)), DecodeStatus::OK);
Mock::VerifyAndClearExpectations(&output_cb_);
ASSERT_EQ(Decode(DecoderBuffer::CreateEOSBuffer()), DecodeStatus::OK);
}
TEST_F(MojoVideoDecoderIntegrationTest, Release) {
ASSERT_TRUE(Initialize());
StrictMock<base::MockCallback<VideoFrame::ReleaseMailboxCB>> release_cb;
scoped_refptr<VideoFrame> frame;
EXPECT_CALL(output_cb_, Run(_)).WillOnce(SaveArg<0>(&frame));
ASSERT_EQ(Decode(CreateKeyframe(0), release_cb.Get()), DecodeStatus::OK);
Mock::VerifyAndClearExpectations(&output_cb_);
EXPECT_CALL(release_cb, Run(_));
frame = nullptr;
RunUntilIdle();
}
TEST_F(MojoVideoDecoderIntegrationTest, ReleaseAfterShutdown) {
ASSERT_TRUE(Initialize());
StrictMock<base::MockCallback<VideoFrame::ReleaseMailboxCB>> release_cb;
scoped_refptr<VideoFrame> frame;
EXPECT_CALL(output_cb_, Run(_)).WillOnce(SaveArg<0>(&frame));
ASSERT_EQ(Decode(CreateKeyframe(0), release_cb.Get()), DecodeStatus::OK);
Mock::VerifyAndClearExpectations(&output_cb_);
client_.reset();
RunUntilIdle();
EXPECT_CALL(release_cb, Run(_));
frame = nullptr;
RunUntilIdle();
}
TEST_F(MojoVideoDecoderIntegrationTest, ResetDuringDecode) {
ASSERT_TRUE(Initialize());
StrictMock<base::MockCallback<VideoDecoder::DecodeCB>> decode_cb;
StrictMock<base::MockCallback<base::Closure>> reset_cb;
EXPECT_CALL(*decoder_, DidGetReleaseMailboxCB()).Times(AtLeast(0));
EXPECT_CALL(output_cb_, Run(_)).Times(kMaxDecodeRequests);
EXPECT_CALL(*decoder_, Decode(_, _)).Times(kMaxDecodeRequests);
EXPECT_CALL(*decoder_, Reset(_)).Times(1);
InSequence s; // Make sure all callbacks are fired in order.
EXPECT_CALL(decode_cb, Run(_)).Times(kMaxDecodeRequests);
EXPECT_CALL(reset_cb, Run());
int64_t timestamp_ms = 0;
for (int j = 0; j < kMaxDecodeRequests; ++j) {
client_->Decode(CreateKeyframe(timestamp_ms++), decode_cb.Get());
}
client_->Reset(reset_cb.Get());
RunUntilIdle();
}
TEST_F(MojoVideoDecoderIntegrationTest, ResetDuringDecode_ChunkedWrite) {
// Use a small writer capacity to force chunked write.
SetWriterCapacity(10);
ASSERT_TRUE(Initialize());
VideoFrame::ReleaseMailboxCB release_cb = VideoFrame::ReleaseMailboxCB();
StrictMock<base::MockCallback<VideoDecoder::DecodeCB>> decode_cb;
StrictMock<base::MockCallback<base::Closure>> reset_cb;
EXPECT_CALL(*decoder_, DidGetReleaseMailboxCB()).Times(AtLeast(0));
EXPECT_CALL(output_cb_, Run(_)).Times(kMaxDecodeRequests);
EXPECT_CALL(*decoder_, Decode(_, _)).Times(kMaxDecodeRequests);
EXPECT_CALL(*decoder_, Reset(_)).Times(1);
InSequence s; // Make sure all callbacks are fired in order.
EXPECT_CALL(decode_cb, Run(_)).Times(kMaxDecodeRequests);
EXPECT_CALL(reset_cb, Run());
int64_t timestamp_ms = 0;
for (int j = 0; j < kMaxDecodeRequests; ++j) {
client_->Decode(CreateKeyframe(timestamp_ms++), decode_cb.Get());
}
client_->Reset(reset_cb.Get());
RunUntilIdle();
}
} // namespace media