blob: 244b5e048193103161976322dab73387cc71fb5d [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/callback_helpers.h"
#include "base/cxx17_backports.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/single_thread_task_runner.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/mock_callback.h"
#include "base/test/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/decrypt_config.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/base/waiting.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/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/color_space.h"
using ::base::test::RunOnceCallback;
using ::testing::_;
using ::testing::AnyNumber;
using ::testing::AtLeast;
using ::testing::HasSubstr;
using ::testing::InSequence;
using ::testing::Invoke;
using ::testing::Mock;
using ::testing::Return;
using ::testing::SaveArg;
using ::testing::StrictMock;
namespace media {
namespace {
const int kMaxDecodeRequests = 4;
const int kErrorDataSize = 7;
// 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, GetDecoderType())
.WillRepeatedly(Return(VideoDecoderType::kTesting));
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));
}
MockVideoDecoder(const MockVideoDecoder&) = delete;
MockVideoDecoder& operator=(const MockVideoDecoder&) = delete;
// Re-declare as public.
~MockVideoDecoder() override {}
// media::VideoDecoder implementation
MOCK_CONST_METHOD0(GetDecoderType, VideoDecoderType());
// Initialize() records values before delegating to the mock method.
void Initialize(const VideoDecoderConfig& config,
bool /* low_delay */,
CdmContext* /* cdm_context */,
InitCB init_cb,
const OutputCB& output_cb,
const WaitingCB& waiting_cb) override {
config_ = config;
output_cb_ = output_cb;
waiting_cb_ = waiting_cb;
DoInitialize(init_cb);
}
void Decode(scoped_refptr<DecoderBuffer> buffer, DecodeCB cb) override {
Decode_(std::move(buffer), cb);
}
MOCK_METHOD2(Decode_, void(scoped_refptr<DecoderBuffer> buffer, DecodeCB&));
void Reset(base::OnceClosure cb) override { Reset_(cb); }
MOCK_METHOD1(Reset_, void(base::OnceClosure&));
MOCK_CONST_METHOD0(NeedsBitstreamConversion, bool());
MOCK_CONST_METHOD0(CanReadWithoutStalling, bool());
MOCK_CONST_METHOD0(GetMaxDecodeRequests, int());
// Mock helpers.
MOCK_METHOD1(DoInitialize, void(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, DecodeCB& decode_cb) {
if (!buffer->end_of_stream()) {
if (buffer->data_size() == kErrorDataSize) {
// This size buffer indicates that decoder should return an error.
// |decode_cb| must not be called from the same stack.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(std::move(decode_cb), DecodeStatus::DECODE_ERROR));
return;
}
if (buffer->decrypt_config()) {
// Simulate the case where outputs are only returned when key arrives.
waiting_cb_.Run(WaitingReason::kNoDecryptionKey);
} else {
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().power_efficient = true;
output_cb_.Run(frame);
}
}
// |decode_cb| must not be called from the same stack.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(decode_cb), DecodeStatus::OK));
}
void DoReset(base::OnceClosure& reset_cb) {
// |reset_cb| must not be called from the same stack.
base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
std::move(reset_cb));
}
base::WeakPtr<MockVideoDecoder> GetWeakPtr() {
return weak_this_factory_.GetWeakPtr();
}
private:
VideoDecoderConfig config_;
OutputCB output_cb_;
WaitingCB waiting_cb_;
base::WeakPtrFactory<MockVideoDecoder> weak_this_factory_{this};
};
// Proxies CreateVideoDecoder() to a callback.
class FakeMojoMediaClient : public MojoMediaClient {
public:
using CreateVideoDecoderCB =
base::RepeatingCallback<std::unique_ptr<VideoDecoder>(MediaLog*)>;
explicit 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::BindRepeating(
&MojoVideoDecoderIntegrationTest::CreateVideoDecoder,
base::Unretained(this))) {}
void TearDown() override {
if (client_) {
client_.reset();
RunUntilIdle();
}
}
protected:
void RunUntilIdle() { task_environment_.RunUntilIdle(); }
void SetWriterCapacity(uint32_t capacity) { writer_capacity_ = capacity; }
mojo::PendingRemote<mojom::VideoDecoder> CreateRemoteVideoDecoder() {
mojo::PendingRemote<mojom::VideoDecoder> remote_video_decoder;
mojo::MakeSelfOwnedReceiver(
std::make_unique<MojoVideoDecoderService>(&mojo_media_client_,
&mojo_cdm_service_context_),
remote_video_decoder.InitWithNewPipeAndPassReceiver());
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(RunOnceCallback<0>(OkStatus()));
Status result = OkStatus();
StrictMock<base::MockCallback<VideoDecoder::InitCB>> init_cb;
EXPECT_CALL(init_cb, Run(_)).WillOnce(SaveArg<0>(&result));
EXPECT_EQ(client_->GetDecoderType(), VideoDecoderType::kUnknown);
client_->Initialize(TestVideoConfig::NormalH264(), false, nullptr,
init_cb.Get(), output_cb_.Get(), waiting_cb_.Get());
RunUntilIdle();
return result.is_ok();
}
Status Decode(scoped_refptr<DecoderBuffer> buffer,
VideoFrame::ReleaseMailboxCB release_cb =
VideoFrame::ReleaseMailboxCB()) {
Status 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::Milliseconds(timestamp_ms));
buffer->set_duration(base::Milliseconds(10));
buffer->set_is_key_frame(true);
return buffer;
}
scoped_refptr<DecoderBuffer> CreateErrorFrame(int64_t timestamp_ms) {
std::vector<uint8_t> data(kErrorDataSize, 0);
scoped_refptr<DecoderBuffer> buffer =
DecoderBuffer::CopyFrom(data.data(), data.size());
buffer->set_timestamp(base::Milliseconds(timestamp_ms));
buffer->set_duration(base::Milliseconds(10));
buffer->set_is_key_frame(true);
return buffer;
}
// TODO(xhwang): Add a function to help create encrypted video buffers in
// media/base/test_helpers.h.
scoped_refptr<DecoderBuffer> CreateEncryptedKeyframe(int64_t timestamp_ms) {
auto buffer = CreateKeyframe(timestamp_ms);
const uint8_t kFakeKeyId[] = {0x4b, 0x65, 0x79, 0x20, 0x49, 0x44};
const uint8_t kFakeIv[DecryptConfig::kDecryptionKeySize] = {0};
buffer->set_decrypt_config(DecryptConfig::CreateCencConfig(
std::string(reinterpret_cast<const char*>(kFakeKeyId),
base::size(kFakeKeyId)),
std::string(reinterpret_cast<const char*>(kFakeIv),
base::size(kFakeIv)),
{}));
return buffer;
}
// Callback that |client_| will deliver VideoFrames to.
StrictMock<base::MockCallback<VideoDecoder::OutputCB>> output_cb_;
StrictMock<base::MockCallback<WaitingCB>> waiting_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.
// |decoder_owner_| owns the decoder until ownership is transferred to the
// |MojoVideoDecoderService|. |decoder_| references it for the duration of its
// lifetime.
std::unique_ptr<MockVideoDecoder> decoder_owner_ =
std::make_unique<MockVideoDecoder>();
base::WeakPtr<MockVideoDecoder> decoder_ = decoder_owner_->GetWeakPtr();
// 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;
return std::move(decoder_owner_);
}
base::test::TaskEnvironment 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) {
mojo::Remote<mojom::VideoDecoder> 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_->GetDecoderType(), VideoDecoderType::kTesting);
EXPECT_EQ(client_->NeedsBitstreamConversion(), false);
EXPECT_EQ(client_->CanReadWithoutStalling(), true);
EXPECT_EQ(client_->GetMaxDecodeRequests(), kMaxDecodeRequests);
}
TEST_F(MojoVideoDecoderIntegrationTest, InitializeFailNoDecoder) {
CreateClient();
StrictMock<base::MockCallback<VideoDecoder::InitCB>> init_cb;
EXPECT_CALL(init_cb,
Run(HasStatusCode(StatusCode::kMojoDecoderNoWrappedDecoder)));
// Clear |decoder_| so that Initialize() should fail.
decoder_owner_.reset();
client_->Initialize(TestVideoConfig::NormalH264(), false, nullptr,
init_cb.Get(), output_cb_.Get(), waiting_cb_.Get());
RunUntilIdle();
}
TEST_F(MojoVideoDecoderIntegrationTest, InitializeFailNoCdm) {
CreateClient();
StrictMock<base::MockCallback<VideoDecoder::InitCB>> init_cb;
EXPECT_CALL(
init_cb,
Run(HasStatusCode(StatusCode::kDecoderMissingCdmForEncryptedContent)));
// CdmContext* (3rd parameter) is not provided but the VideoDecoderConfig
// specifies encrypted video, so Initialize() should fail.
client_->Initialize(TestVideoConfig::NormalEncrypted(), false, nullptr,
init_cb.Get(), output_cb_.Get(), waiting_cb_.Get());
RunUntilIdle();
}
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, WaitingForKey) {
ASSERT_TRUE(Initialize());
auto buffer = CreateEncryptedKeyframe(0);
StrictMock<base::MockCallback<VideoDecoder::DecodeCB>> decode_cb;
EXPECT_CALL(*decoder_, Decode_(_, _));
EXPECT_CALL(waiting_cb_, Run(WaitingReason::kNoDecryptionKey));
EXPECT_CALL(decode_cb, Run(IsOkStatus()));
client_->Decode(buffer, decode_cb.Get());
RunUntilIdle();
}
TEST_F(MojoVideoDecoderIntegrationTest, Decode) {
ASSERT_TRUE(Initialize());
EXPECT_CALL(output_cb_, Run(_));
ASSERT_TRUE(Decode(CreateKeyframe(0)).is_ok());
Mock::VerifyAndClearExpectations(&output_cb_);
ASSERT_TRUE(Decode(DecoderBuffer::CreateEOSBuffer()).is_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_TRUE(Decode(CreateKeyframe(0), release_cb.Get()).is_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_TRUE(Decode(CreateKeyframe(0), release_cb.Get()).is_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::OnceClosure>> 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_(_));
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::OnceClosure>> 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_(_));
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, InitialPlaybackUMASuccess) {
base::HistogramTester histogram_tester_;
const int frames_to_decode = kMojoDecoderInitialPlaybackFrameCount;
ASSERT_TRUE(Initialize());
StrictMock<base::MockCallback<VideoDecoder::DecodeCB>> decode_cb;
EXPECT_CALL(*decoder_, DidGetReleaseMailboxCB()).Times(AnyNumber());
EXPECT_CALL(output_cb_, Run(_)).Times(frames_to_decode);
EXPECT_CALL(*decoder_, Decode_(_, _)).Times(frames_to_decode);
EXPECT_CALL(decode_cb, Run(IsOkStatus())).Times(frames_to_decode);
for (int i = 0; i < frames_to_decode - 1; i++)
client_->Decode(CreateKeyframe(i * 16), decode_cb.Get());
RunUntilIdle();
histogram_tester_.ExpectBucketCount(
kMojoVideoDecoderInitialPlaybackSuccessCodecCounterUMA, 1, 0);
client_->Decode(CreateKeyframe(0), decode_cb.Get());
RunUntilIdle();
histogram_tester_.ExpectBucketCount(
kMojoVideoDecoderInitialPlaybackSuccessCodecCounterUMA, 1, 1);
}
TEST_F(MojoVideoDecoderIntegrationTest, InitialPlaybackUMAError) {
base::HistogramTester histogram_tester_;
const int frames_to_decode = kMojoDecoderInitialPlaybackFrameCount;
ASSERT_TRUE(Initialize());
StrictMock<base::MockCallback<VideoDecoder::DecodeCB>> decode_cb;
EXPECT_CALL(*decoder_, DidGetReleaseMailboxCB()).Times(AnyNumber());
EXPECT_CALL(output_cb_, Run(_)).Times(frames_to_decode - 1);
EXPECT_CALL(*decoder_, Decode_(_, _)).Times(frames_to_decode);
EXPECT_CALL(decode_cb, Run(IsOkStatus())).Times(frames_to_decode - 1);
EXPECT_CALL(decode_cb, Run(IsDecodeErrorStatus())).Times(1);
for (int i = 0; i < frames_to_decode - 1; i++)
client_->Decode(CreateKeyframe(i * 16), decode_cb.Get());
RunUntilIdle();
histogram_tester_.ExpectBucketCount(
kMojoVideoDecoderInitialPlaybackErrorCodecCounterUMA, 1, 0);
histogram_tester_.ExpectBucketCount(
kMojoVideoDecoderInitialPlaybackSuccessCodecCounterUMA, 1, 0);
client_->Decode(CreateErrorFrame(0), decode_cb.Get());
RunUntilIdle();
histogram_tester_.ExpectBucketCount(
kMojoVideoDecoderInitialPlaybackErrorCodecCounterUMA, 1, 1);
histogram_tester_.ExpectBucketCount(
kMojoVideoDecoderInitialPlaybackSuccessCodecCounterUMA, 1, 0);
}
} // namespace media