blob: 958bd3ef780dfaf8d96ece5ed4427d81cae1498d [file] [log] [blame]
// Copyright 2016 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/gpu/android/media_codec_video_decoder.h"
#include "base/android/jni_android.h"
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/run_loop.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/mock_callback.h"
#include "base/test/task_environment.h"
#include "base/threading/thread_task_runner_handle.h"
#include "gpu/command_buffer/service/mock_texture_owner.h"
#include "gpu/config/gpu_preferences.h"
#include "media/base/android/media_codec_util.h"
#include "media/base/android/mock_android_overlay.h"
#include "media/base/android/mock_media_crypto_context.h"
#include "media/base/async_destroy_video_decoder.h"
#include "media/base/decoder_buffer.h"
#include "media/base/media_util.h"
#include "media/base/supported_video_decoder_config.h"
#include "media/base/test_helpers.h"
#include "media/base/video_codecs.h"
#include "media/base/video_frame.h"
#include "media/gpu/android/android_video_surface_chooser_impl.h"
#include "media/gpu/android/fake_codec_allocator.h"
#include "media/gpu/android/mock_android_video_surface_chooser.h"
#include "media/gpu/android/mock_device_info.h"
#include "media/gpu/android/video_frame_factory.h"
#include "testing/gtest/include/gtest/gtest.h"
using base::test::RunCallback;
using testing::_;
using testing::InvokeWithoutArgs;
using testing::NiceMock;
using testing::NotNull;
using testing::Return;
using testing::ReturnRef;
using testing::SaveArg;
namespace media {
namespace {
void OutputCb(scoped_refptr<VideoFrame>* output,
scoped_refptr<VideoFrame> frame) {
*output = std::move(frame);
}
std::unique_ptr<AndroidOverlay> CreateAndroidOverlayCb(
const base::UnguessableToken&,
AndroidOverlayConfig) {
return nullptr;
}
} // namespace
class MockVideoFrameFactory : public VideoFrameFactory {
public:
MOCK_METHOD2(Initialize, void(OverlayMode overlay_mode, InitCB init_cb));
MOCK_METHOD1(MockSetSurfaceBundle, void(scoped_refptr<CodecSurfaceBundle>));
MOCK_METHOD5(
MockCreateVideoFrame,
void(CodecOutputBuffer* raw_output_buffer,
scoped_refptr<gpu::TextureOwner> texture_owner,
base::TimeDelta timestamp,
gfx::Size natural_size,
PromotionHintAggregator::NotifyPromotionHintCB promotion_hint_cb));
MOCK_METHOD1(MockRunAfterPendingVideoFrames,
void(base::OnceClosure* closure));
MOCK_METHOD0(CancelPendingCallbacks, void());
void SetSurfaceBundle(
scoped_refptr<CodecSurfaceBundle> surface_bundle) override {
MockSetSurfaceBundle(surface_bundle);
if (!surface_bundle) {
texture_owner_ = nullptr;
} else {
texture_owner_ = surface_bundle->overlay()
? nullptr
: surface_bundle->codec_buffer_wait_coordinator()
->texture_owner();
}
}
void CreateVideoFrame(
std::unique_ptr<CodecOutputBuffer> output_buffer,
base::TimeDelta timestamp,
gfx::Size natural_size,
PromotionHintAggregator::NotifyPromotionHintCB promotion_hint_cb,
VideoFrameFactory::OnceOutputCB output_cb) override {
MockCreateVideoFrame(output_buffer.get(), texture_owner_, timestamp,
natural_size, promotion_hint_cb);
last_output_buffer_ = std::move(output_buffer);
std::move(output_cb).Run(VideoFrame::CreateBlackFrame(gfx::Size(10, 10)));
}
void RunAfterPendingVideoFrames(base::OnceClosure closure) override {
last_closure_ = std::move(closure);
MockRunAfterPendingVideoFrames(&last_closure_);
}
std::unique_ptr<CodecOutputBuffer> last_output_buffer_;
scoped_refptr<gpu::TextureOwner> texture_owner_;
base::OnceClosure last_closure_;
};
class MediaCodecVideoDecoderTest : public testing::TestWithParam<VideoCodec> {
public:
MediaCodecVideoDecoderTest() : codec_(GetParam()) {}
void SetUp() override {
uint8_t data = 0;
fake_decoder_buffer_ = DecoderBuffer::CopyFrom(&data, 1);
codec_allocator_ = std::make_unique<FakeCodecAllocator>(
base::ThreadTaskRunnerHandle::Get());
device_info_ = std::make_unique<NiceMock<MockDeviceInfo>>();
}
void TearDown() override {
// For VP8, make MCVD skip the drain by resetting it. Otherwise, it's hard
// to finish the drain.
if (mcvd_ && codec_ == kCodecVP8 && codec_allocator_->most_recent_codec)
DoReset();
// MCVD calls DeleteSoon() on itself, so we have to run a RunLoop.
mcvd_.reset();
base::RunLoop().RunUntilIdle();
}
void CreateMcvd() {
auto surface_chooser =
std::make_unique<NiceMock<MockAndroidVideoSurfaceChooser>>();
surface_chooser_ = surface_chooser.get();
auto texture_owner = base::MakeRefCounted<NiceMock<gpu::MockTextureOwner>>(
0, nullptr, nullptr);
texture_owner_ = texture_owner.get();
auto video_frame_factory =
std::make_unique<NiceMock<MockVideoFrameFactory>>();
video_frame_factory_ = video_frame_factory.get();
// Set up VFF to pass |texture_owner_| via its InitCB.
ON_CALL(*video_frame_factory_, Initialize(ExpectedOverlayMode(), _))
.WillByDefault(RunCallback<1>(texture_owner));
auto* mcvd = new MediaCodecVideoDecoder(
gpu_preferences_, gpu_feature_info_, std::make_unique<NullMediaLog>(),
device_info_.get(), codec_allocator_.get(), std::move(surface_chooser),
base::BindRepeating(&CreateAndroidOverlayCb),
base::BindRepeating(&MediaCodecVideoDecoderTest::RequestOverlayInfoCb,
base::Unretained(this)),
std::move(video_frame_factory), /*lock=*/nullptr);
mcvd_ = std::make_unique<AsyncDestroyVideoDecoder<MediaCodecVideoDecoder>>(
base::WrapUnique(mcvd));
mcvd_raw_ = mcvd;
}
VideoFrameFactory::OverlayMode ExpectedOverlayMode() const {
const bool want_promotion_hint =
device_info_->IsSetOutputSurfaceSupported();
return want_promotion_hint
? VideoFrameFactory::OverlayMode::kRequestPromotionHints
: VideoFrameFactory::OverlayMode::kDontRequestPromotionHints;
}
void CreateCdm(bool has_media_crypto_context,
bool require_secure_video_decoder) {
cdm_ = std::make_unique<MockMediaCryptoContext>(has_media_crypto_context);
require_secure_video_decoder_ = require_secure_video_decoder;
// We need to send an object as the media crypto, but MCVD shouldn't
// use it for anything. Just send in some random java object, so that
// it's not null.
media_crypto_ = base::android::ScopedJavaGlobalRef<jobject>(
gl::SurfaceTexture::Create(0)->j_surface_texture());
}
// Just call Initialize(). MCVD will be waiting for a call to Decode() before
// continuining initialization.
bool Initialize(VideoDecoderConfig config) {
if (!mcvd_)
CreateMcvd();
bool result = false;
auto init_cb = [](bool* result_out, Status result) {
*result_out = result.is_ok();
};
mcvd_->Initialize(
config, false, cdm_.get(), base::BindOnce(init_cb, &result),
base::BindRepeating(&OutputCb, &most_recent_frame_), base::DoNothing());
base::RunLoop().RunUntilIdle();
// If there is a CDM available, then we expect that MCVD will be waiting
// for the media crypto object.
// TODO(liberato): why does CreateJavaObjectPtr() not link?
if (cdm_ && cdm_->media_crypto_ready_cb) {
std::move(cdm_->media_crypto_ready_cb)
.Run(std::make_unique<base::android::ScopedJavaGlobalRef<jobject>>(
media_crypto_),
require_secure_video_decoder_);
// The callback is consumed, mark that we ran it so tests can verify.
cdm_->ran_media_crypto_ready_cb = true;
base::RunLoop().RunUntilIdle();
}
return result;
}
// Call Initialize() and Decode() to start lazy init. MCVD will be waiting for
// a codec and have one decode pending.
MockAndroidOverlay* InitializeWithOverlay_OneDecodePending(
VideoDecoderConfig config) {
Initialize(config);
mcvd_->Decode(fake_decoder_buffer_, decode_cb_.Get());
OverlayInfo info;
info.routing_token = base::UnguessableToken::Deserialize(1, 2);
provide_overlay_info_cb_.Run(info);
auto overlay_ptr = std::make_unique<MockAndroidOverlay>();
auto* overlay = overlay_ptr.get();
if (!java_surface_) {
java_surface_ = base::android::ScopedJavaGlobalRef<jobject>(
gl::SurfaceTexture::Create(0)->j_surface_texture());
}
EXPECT_CALL(*overlay, GetJavaSurface())
.WillRepeatedly(ReturnRef(java_surface_));
surface_chooser_->ProvideOverlay(std::move(overlay_ptr));
return overlay;
}
// Call Initialize() and Decode() to start lazy init. MCVD will be waiting for
// a codec and have one decode pending.
void InitializeWithTextureOwner_OneDecodePending(VideoDecoderConfig config) {
Initialize(config);
mcvd_->Decode(fake_decoder_buffer_, decode_cb_.Get());
provide_overlay_info_cb_.Run(OverlayInfo());
surface_chooser_->ProvideTextureOwner();
}
// Fully initializes MCVD and returns the codec it's configured with. MCVD
// will have one decode pending.
MockMediaCodecBridge* InitializeFully_OneDecodePending(
VideoDecoderConfig config) {
InitializeWithTextureOwner_OneDecodePending(config);
return codec_allocator_->ProvideMockCodecAsync();
}
// Provide access to MCVD's private PumpCodec() to drive the state transitions
// that depend on queueing and dequeueing buffers. It uses |mcvd_raw_| so that
// it can be called after |mcvd_| is reset.
void PumpCodec() { mcvd_raw_->PumpCodec(false); }
// Start and finish a reset.
void DoReset() {
bool reset_complete = false;
mcvd_->Reset(base::BindOnce(
[](bool* reset_complete) { *reset_complete = true; }, &reset_complete));
base::RunLoop().RunUntilIdle();
if (!reset_complete) {
// Note that there might be more pending decodes, and this will arrive
// out of order. We assume that MCVD doesn't care.
codec_allocator_->most_recent_codec->ProduceOneOutput(
MockMediaCodecBridge::kEos);
PumpCodec();
EXPECT_TRUE(reset_complete);
}
}
void RequestOverlayInfoCb(bool restart_for_transitions,
ProvideOverlayInfoCB provide_overlay_info_cb) {
restart_for_transitions_ = restart_for_transitions;
provide_overlay_info_cb_ = std::move(provide_overlay_info_cb);
}
protected:
const VideoCodec codec_;
base::test::SingleThreadTaskEnvironment task_environment_;
base::android::ScopedJavaGlobalRef<jobject> java_surface_;
scoped_refptr<DecoderBuffer> fake_decoder_buffer_;
std::unique_ptr<MockDeviceInfo> device_info_;
std::unique_ptr<FakeCodecAllocator> codec_allocator_;
MockAndroidVideoSurfaceChooser* surface_chooser_;
gpu::MockTextureOwner* texture_owner_;
MockVideoFrameFactory* video_frame_factory_;
NiceMock<base::MockCallback<VideoDecoder::DecodeCB>> decode_cb_;
ProvideOverlayInfoCB provide_overlay_info_cb_;
bool restart_for_transitions_;
gpu::GpuPreferences gpu_preferences_;
gpu::GpuFeatureInfo gpu_feature_info_;
scoped_refptr<VideoFrame> most_recent_frame_;
// This is not an actual media crypto object.
base::android::ScopedJavaGlobalRef<jobject> media_crypto_;
bool require_secure_video_decoder_ = false;
// This must outlive |mcvd_| .
std::unique_ptr<MockMediaCryptoContext> cdm_;
// |mcvd_raw_| lets us call PumpCodec() even after |mcvd_| is dropped, for
// testing the teardown path.
MediaCodecVideoDecoder* mcvd_raw_;
std::unique_ptr<VideoDecoder> mcvd_;
};
// Tests which only work for a single codec.
class MediaCodecVideoDecoderAV1Test : public MediaCodecVideoDecoderTest {};
class MediaCodecVideoDecoderH264Test : public MediaCodecVideoDecoderTest {};
class MediaCodecVideoDecoderVp8Test : public MediaCodecVideoDecoderTest {};
class MediaCodecVideoDecoderVp9Test : public MediaCodecVideoDecoderTest {};
TEST_P(MediaCodecVideoDecoderTest, UnknownCodecIsRejected) {
ASSERT_FALSE(Initialize(TestVideoConfig::Invalid()));
}
TEST_P(MediaCodecVideoDecoderH264Test, H264IsSupported) {
ASSERT_TRUE(Initialize(TestVideoConfig::NormalH264()));
}
TEST_P(MediaCodecVideoDecoderVp8Test, SmallVp8IsRejected) {
auto configs = MediaCodecVideoDecoder::GetSupportedConfigs();
auto small_vp8_config = TestVideoConfig::Normal();
for (const auto& c : configs)
ASSERT_FALSE(c.Matches(small_vp8_config));
}
TEST_P(MediaCodecVideoDecoderAV1Test, Av1IsSupported) {
EXPECT_CALL(*device_info_, IsAv1DecoderAvailable()).WillOnce(Return(true));
ASSERT_TRUE(Initialize(TestVideoConfig::Normal(kCodecAV1)));
}
TEST_P(MediaCodecVideoDecoderTest, InitializeDoesntInitSurfaceOrCodec) {
CreateMcvd();
EXPECT_CALL(*video_frame_factory_, Initialize(ExpectedOverlayMode(), _))
.Times(0);
EXPECT_CALL(*surface_chooser_, MockUpdateState()).Times(0);
EXPECT_CALL(*codec_allocator_, MockCreateMediaCodecAsync()).Times(0);
Initialize(TestVideoConfig::Large(codec_));
}
TEST_P(MediaCodecVideoDecoderTest, FirstDecodeTriggersFrameFactoryInit) {
Initialize(TestVideoConfig::Large(codec_));
EXPECT_CALL(*video_frame_factory_, Initialize(ExpectedOverlayMode(), _));
mcvd_->Decode(fake_decoder_buffer_, decode_cb_.Get());
}
TEST_P(MediaCodecVideoDecoderTest,
FirstDecodeTriggersOverlayInfoRequestIfSupported) {
Initialize(TestVideoConfig::Large(codec_));
// Requesting overlay info sets this cb.
ASSERT_FALSE(provide_overlay_info_cb_);
mcvd_->Decode(fake_decoder_buffer_, decode_cb_.Get());
ASSERT_TRUE(provide_overlay_info_cb_);
}
TEST_P(MediaCodecVideoDecoderTest,
OverlayInfoIsNotRequestedIfOverlaysNotSupported) {
Initialize(TestVideoConfig::Large(codec_));
ON_CALL(*device_info_, SupportsOverlaySurfaces())
.WillByDefault(Return(false));
mcvd_->Decode(fake_decoder_buffer_, decode_cb_.Get());
ASSERT_FALSE(provide_overlay_info_cb_);
}
TEST_P(MediaCodecVideoDecoderTest, RestartForOverlayTransitionsFlagIsCorrect) {
ON_CALL(*device_info_, IsSetOutputSurfaceSupported())
.WillByDefault(Return(true));
Initialize(TestVideoConfig::Large(codec_));
mcvd_->Decode(fake_decoder_buffer_, decode_cb_.Get());
ASSERT_FALSE(restart_for_transitions_);
}
TEST_P(MediaCodecVideoDecoderTest,
OverlayInfoIsNotRequestedIfThreadedTextureMailboxesEnabled) {
gpu_preferences_.enable_threaded_texture_mailboxes = true;
Initialize(TestVideoConfig::Large(codec_));
mcvd_->Decode(fake_decoder_buffer_, decode_cb_.Get());
ASSERT_FALSE(provide_overlay_info_cb_);
}
TEST_P(MediaCodecVideoDecoderTest, OverlayInfoDuringInitUpdatesSurfaceChooser) {
InitializeWithTextureOwner_OneDecodePending(TestVideoConfig::Large(codec_));
EXPECT_CALL(*surface_chooser_, MockUpdateState());
provide_overlay_info_cb_.Run(OverlayInfo());
}
TEST_P(MediaCodecVideoDecoderTest, CodecIsCreatedAfterSurfaceChosen) {
Initialize(TestVideoConfig::Large(codec_));
mcvd_->Decode(fake_decoder_buffer_, decode_cb_.Get());
provide_overlay_info_cb_.Run(OverlayInfo());
EXPECT_CALL(*codec_allocator_, MockCreateMediaCodecAsync());
surface_chooser_->ProvideTextureOwner();
}
TEST_P(MediaCodecVideoDecoderTest, FrameFactoryInitFailureIsAnError) {
Initialize(TestVideoConfig::Large(codec_));
ON_CALL(*video_frame_factory_, Initialize(ExpectedOverlayMode(), _))
.WillByDefault(RunCallback<1>(nullptr));
EXPECT_CALL(decode_cb_, Run(IsDecodeErrorStatus())).Times(1);
EXPECT_CALL(*surface_chooser_, MockUpdateState()).Times(0);
mcvd_->Decode(fake_decoder_buffer_, decode_cb_.Get());
}
TEST_P(MediaCodecVideoDecoderTest, CodecCreationFailureIsAnError) {
InitializeWithTextureOwner_OneDecodePending(TestVideoConfig::Large(codec_));
mcvd_->Decode(fake_decoder_buffer_, decode_cb_.Get());
EXPECT_CALL(decode_cb_, Run(IsDecodeErrorStatus())).Times(2);
// Failing to create a codec should put MCVD into an error state.
codec_allocator_->ProvideNullCodecAsync();
}
TEST_P(MediaCodecVideoDecoderTest, CodecFailuresAreAnError) {
auto* codec =
InitializeFully_OneDecodePending(TestVideoConfig::Large(codec_));
EXPECT_CALL(*codec, DequeueInputBuffer(_, _))
.WillOnce(Return(MEDIA_CODEC_ERROR));
EXPECT_CALL(decode_cb_, Run(IsDecodeErrorStatus()));
PumpCodec();
}
TEST_P(MediaCodecVideoDecoderTest, AfterInitCompletesTheCodecIsPolled) {
auto* codec =
InitializeFully_OneDecodePending(TestVideoConfig::Large(codec_));
// Run a RunLoop until the first time the codec is polled for an available
// input buffer.
base::RunLoop loop;
EXPECT_CALL(*codec, DequeueInputBuffer(_, _))
.WillOnce(InvokeWithoutArgs([&loop]() {
loop.Quit();
return MEDIA_CODEC_TRY_AGAIN_LATER;
}));
loop.Run();
}
TEST_P(MediaCodecVideoDecoderTest, CodecIsReleasedOnDestruction) {
auto* codec =
InitializeFully_OneDecodePending(TestVideoConfig::Large(codec_));
EXPECT_CALL(*codec_allocator_, MockReleaseMediaCodec(codec));
}
TEST_P(MediaCodecVideoDecoderTest, SurfaceChooserIsUpdatedOnOverlayChanges) {
InitializeWithTextureOwner_OneDecodePending(TestVideoConfig::Large(codec_));
EXPECT_CALL(*surface_chooser_, MockReplaceOverlayFactory(_)).Times(2);
OverlayInfo info;
info.routing_token = base::UnguessableToken::Deserialize(1, 2);
provide_overlay_info_cb_.Run(info);
ASSERT_TRUE(surface_chooser_->factory_);
info.routing_token = base::UnguessableToken::Deserialize(3, 4);
provide_overlay_info_cb_.Run(info);
ASSERT_TRUE(surface_chooser_->factory_);
}
TEST_P(MediaCodecVideoDecoderTest, OverlayInfoUpdatesAreIgnoredInStateError) {
InitializeWithTextureOwner_OneDecodePending(TestVideoConfig::Large(codec_));
// Enter the error state.
codec_allocator_->ProvideNullCodecAsync();
EXPECT_CALL(*surface_chooser_, MockUpdateState()).Times(0);
OverlayInfo info;
info.routing_token = base::UnguessableToken::Deserialize(1, 2);
provide_overlay_info_cb_.Run(info);
}
TEST_P(MediaCodecVideoDecoderTest, DuplicateOverlayInfoUpdatesAreIgnored) {
InitializeWithTextureOwner_OneDecodePending(TestVideoConfig::Large(codec_));
// The second overlay info update should be ignored.
EXPECT_CALL(*surface_chooser_, MockReplaceOverlayFactory(_)).Times(1);
OverlayInfo info;
info.routing_token = base::UnguessableToken::Deserialize(1, 2);
provide_overlay_info_cb_.Run(info);
provide_overlay_info_cb_.Run(info);
}
TEST_P(MediaCodecVideoDecoderTest, CodecIsCreatedWithChosenOverlay) {
EXPECT_CALL(*codec_allocator_, MockCreateMediaCodecAsync());
InitializeWithOverlay_OneDecodePending(TestVideoConfig::Large(codec_));
EXPECT_TRUE(base::android::AttachCurrentThread()->IsSameObject(
java_surface_.obj(),
codec_allocator_->most_recent_config->surface.obj()));
}
TEST_P(MediaCodecVideoDecoderTest,
CodecCreationWeakPtrIsInvalidatedBySurfaceDestroyed) {
ON_CALL(*device_info_, IsSetOutputSurfaceSupported())
.WillByDefault(Return(false));
auto* overlay =
InitializeWithOverlay_OneDecodePending(TestVideoConfig::Large(codec_));
overlay->OnSurfaceDestroyed();
// MCVD handles release of the MediaCodec after WeakPtr invalidation.
EXPECT_CALL(*codec_allocator_, MockReleaseMediaCodec(NotNull()));
auto* codec = codec_allocator_->ProvideMockCodecAsync();
ASSERT_TRUE(!!codec);
}
TEST_P(MediaCodecVideoDecoderTest, SurfaceChangedWhileCodecCreationPending) {
auto* overlay =
InitializeWithOverlay_OneDecodePending(TestVideoConfig::Large(codec_));
overlay->OnSurfaceDestroyed();
auto codec = std::make_unique<NiceMock<MockMediaCodecBridge>>();
// SetSurface() is called as soon as the codec is created to switch away from
// the destroyed surface.
EXPECT_CALL(*codec, SetSurface(_)).WillOnce(Return(true));
codec_allocator_->ProvideMockCodecAsync(std::move(codec));
}
TEST_P(MediaCodecVideoDecoderTest, SurfaceDestroyedDoesSyncSurfaceTransition) {
auto* overlay =
InitializeWithOverlay_OneDecodePending(TestVideoConfig::Large(codec_));
auto* codec = codec_allocator_->ProvideMockCodecAsync();
// MCVD must synchronously switch the codec's surface (to surface
// texture), and delete the overlay.
EXPECT_CALL(*codec, SetSurface(_)).WillOnce(Return(true));
auto observer = overlay->CreateDestructionObserver();
observer->ExpectDestruction();
overlay->OnSurfaceDestroyed();
}
TEST_P(MediaCodecVideoDecoderTest,
SurfaceDestroyedReleasesCodecIfSetSurfaceIsNotSupported) {
ON_CALL(*device_info_, IsSetOutputSurfaceSupported())
.WillByDefault(Return(false));
auto* overlay =
InitializeWithOverlay_OneDecodePending(TestVideoConfig::Large(codec_));
auto* codec = codec_allocator_->ProvideMockCodecAsync();
// MCVD must synchronously release the codec.
EXPECT_CALL(*codec, SetSurface(_)).Times(0);
EXPECT_CALL(*codec_allocator_, MockReleaseMediaCodec(codec));
overlay->OnSurfaceDestroyed();
// Verify expectations before we delete the MCVD.
testing::Mock::VerifyAndClearExpectations(codec_allocator_.get());
}
TEST_P(MediaCodecVideoDecoderTest, PumpCodecPerformsPendingSurfaceTransitions) {
InitializeWithOverlay_OneDecodePending(TestVideoConfig::Large(codec_));
auto* codec = codec_allocator_->ProvideMockCodecAsync();
// Set a pending surface transition and then call PumpCodec().
surface_chooser_->ProvideTextureOwner();
EXPECT_CALL(*codec, SetSurface(_)).WillOnce(Return(true));
PumpCodec();
}
TEST_P(MediaCodecVideoDecoderTest,
SetSurfaceFailureReleasesTheCodecAndSignalsError) {
InitializeWithOverlay_OneDecodePending(TestVideoConfig::Large(codec_));
auto* codec = codec_allocator_->ProvideMockCodecAsync();
surface_chooser_->ProvideTextureOwner();
EXPECT_CALL(*codec, SetSurface(_)).WillOnce(Return(false));
EXPECT_CALL(decode_cb_, Run(IsDecodeErrorStatus())).Times(2);
EXPECT_CALL(*codec_allocator_, MockReleaseMediaCodec(codec));
mcvd_->Decode(fake_decoder_buffer_, decode_cb_.Get());
// Verify expectations before we delete the MCVD.
testing::Mock::VerifyAndClearExpectations(codec_allocator_.get());
}
TEST_P(MediaCodecVideoDecoderTest, SurfaceTransitionsCanBeCanceled) {
InitializeWithTextureOwner_OneDecodePending(TestVideoConfig::Large(codec_));
auto* codec = codec_allocator_->ProvideMockCodecAsync();
// Set a pending transition to an overlay, and then back to a texture owner.
// They should cancel each other out and leave the codec as-is.
EXPECT_CALL(*codec, SetSurface(_)).Times(0);
auto overlay = std::make_unique<MockAndroidOverlay>();
auto observer = overlay->CreateDestructionObserver();
surface_chooser_->ProvideOverlay(std::move(overlay));
// Switching back to texture owner should delete the pending overlay.
observer->ExpectDestruction();
surface_chooser_->ProvideTextureOwner();
observer.reset();
// Verify that Decode() does not transition the surface
mcvd_->Decode(fake_decoder_buffer_, decode_cb_.Get());
}
TEST_P(MediaCodecVideoDecoderTest, TransitionToSameSurfaceIsIgnored) {
InitializeWithTextureOwner_OneDecodePending(TestVideoConfig::Large(codec_));
auto* codec = codec_allocator_->ProvideMockCodecAsync();
EXPECT_CALL(*codec, SetSurface(_)).Times(0);
surface_chooser_->ProvideTextureOwner();
mcvd_->Decode(fake_decoder_buffer_, decode_cb_.Get());
}
TEST_P(MediaCodecVideoDecoderTest,
ResetBeforeCodecInitializedSucceedsImmediately) {
InitializeWithTextureOwner_OneDecodePending(TestVideoConfig::Large(codec_));
base::MockCallback<base::OnceClosure> reset_cb;
EXPECT_CALL(reset_cb, Run());
mcvd_->Reset(reset_cb.Get());
testing::Mock::VerifyAndClearExpectations(&reset_cb);
}
TEST_P(MediaCodecVideoDecoderTest, ResetAbortsPendingDecodes) {
InitializeWithTextureOwner_OneDecodePending(TestVideoConfig::Large(codec_));
EXPECT_CALL(decode_cb_, Run(HasStatusCode(StatusCode::kAborted)));
DoReset();
testing::Mock::VerifyAndClearExpectations(&decode_cb_);
}
TEST_P(MediaCodecVideoDecoderTest, ResetAbortsPendingEosDecode) {
// EOS is treated differently by MCVD. This verifies that it's also aborted.
auto* codec =
InitializeFully_OneDecodePending(TestVideoConfig::Large(codec_));
base::MockCallback<VideoDecoder::DecodeCB> eos_decode_cb;
mcvd_->Decode(DecoderBuffer::CreateEOSBuffer(), eos_decode_cb.Get());
// Accept the two pending decodes.
codec->AcceptOneInput();
PumpCodec();
codec->AcceptOneInput(MockMediaCodecBridge::kEos);
PumpCodec();
EXPECT_CALL(eos_decode_cb, Run(HasStatusCode(StatusCode::kAborted)));
DoReset();
// Should be run before |mcvd_| is destroyed.
testing::Mock::VerifyAndClearExpectations(&eos_decode_cb);
}
TEST_P(MediaCodecVideoDecoderTest, ResetDoesNotFlushAnAlreadyFlushedCodec) {
auto* codec =
InitializeFully_OneDecodePending(TestVideoConfig::Large(codec_));
// The codec is still in the flushed state so Reset() doesn't need to flush.
EXPECT_CALL(*codec, Flush()).Times(0);
base::MockCallback<base::OnceClosure> reset_cb;
EXPECT_CALL(reset_cb, Run());
mcvd_->Reset(reset_cb.Get());
testing::Mock::VerifyAndClearExpectations(&decode_cb_);
}
TEST_P(MediaCodecVideoDecoderVp8Test, ResetDrainsVP8CodecsBeforeFlushing) {
auto* codec =
InitializeFully_OneDecodePending(TestVideoConfig::Large(codec_));
// Accept the first decode to transition out of the flushed state.
codec->AcceptOneInput();
PumpCodec();
// The reset should not complete immediately because the codec needs to be
// drained.
EXPECT_CALL(*codec, Flush()).Times(0);
base::MockCallback<base::OnceClosure> reset_cb;
EXPECT_CALL(reset_cb, Run()).Times(0);
mcvd_->Reset(reset_cb.Get());
// The next input should be an EOS.
codec->AcceptOneInput(MockMediaCodecBridge::kEos);
PumpCodec();
testing::Mock::VerifyAndClearExpectations(codec);
// After the EOS is dequeued, the reset should complete.
EXPECT_CALL(reset_cb, Run());
codec->ProduceOneOutput(MockMediaCodecBridge::kEos);
PumpCodec();
testing::Mock::VerifyAndClearExpectations(&reset_cb);
}
TEST_P(MediaCodecVideoDecoderVp8Test, ResetDoesNotDrainVp8WithAsyncApi) {
EXPECT_CALL(*device_info_, IsAsyncApiSupported())
.WillRepeatedly(Return(true));
auto* codec =
InitializeFully_OneDecodePending(TestVideoConfig::Large(codec_));
// Accept the first decode to transition out of the flushed state.
codec->AcceptOneInput();
PumpCodec();
// The reset should complete immediately because the codec is not VP8 so
// it doesn't need draining. We don't expect a call to Flush on the codec
// since it will be deferred until the first decode after the reset.
base::MockCallback<base::OnceClosure> reset_cb;
EXPECT_CALL(reset_cb, Run());
mcvd_->Reset(reset_cb.Get());
// The reset should complete before destroying the codec, since TearDown will
// complete the drain for VP8. It still might not call reset since a drain
// for destroy probably doesn't, but either way we expect it before the drain.
testing::Mock::VerifyAndClearExpectations(&reset_cb);
}
TEST_P(MediaCodecVideoDecoderH264Test, ResetDoesNotDrainNonVp8Codecs) {
auto* codec =
InitializeFully_OneDecodePending(TestVideoConfig::Large(codec_));
// Accept the first decode to transition out of the flushed state.
codec->AcceptOneInput();
PumpCodec();
// The reset should complete immediately because the codec is not VP8 so
// it doesn't need draining. We don't expect a call to Flush on the codec
// since it will be deferred until the first decode after the reset.
base::MockCallback<base::OnceClosure> reset_cb;
EXPECT_CALL(reset_cb, Run());
mcvd_->Reset(reset_cb.Get());
// The reset should complete before destroying the codec, since TearDown will
// complete the drain for VP8. It still might not call reset since a drain
// for destroy probably doesn't, but either way we expect it before the drain.
testing::Mock::VerifyAndClearExpectations(&reset_cb);
}
TEST_P(MediaCodecVideoDecoderVp8Test, TeardownCompletesPendingReset) {
auto* codec =
InitializeFully_OneDecodePending(TestVideoConfig::Large(codec_));
// Accept the first decode to transition out of the flushed state.
codec->AcceptOneInput();
PumpCodec();
base::MockCallback<base::OnceClosure> reset_cb;
EXPECT_CALL(reset_cb, Run()).Times(0);
mcvd_->Reset(reset_cb.Get());
EXPECT_CALL(reset_cb, Run());
mcvd_.reset();
// VP8 codecs requiring draining for teardown to complete (tested below).
codec->ProduceOneOutput(MockMediaCodecBridge::kEos);
PumpCodec();
}
TEST_P(MediaCodecVideoDecoderTest, CodecFlushIsDeferredAfterDraining) {
auto* codec =
InitializeFully_OneDecodePending(TestVideoConfig::Large(codec_));
mcvd_->Decode(DecoderBuffer::CreateEOSBuffer(), decode_cb_.Get());
// Produce one output that VFF will hold onto.
codec->AcceptOneInput();
codec->ProduceOneOutput();
PumpCodec();
// Drain the codec.
EXPECT_CALL(*codec, Flush()).Times(0);
codec->AcceptOneInput(MockMediaCodecBridge::kEos);
codec->ProduceOneOutput(MockMediaCodecBridge::kEos);
PumpCodec();
// Create a pending decode. The codec should still not be flushed because
// there is an unrendered output buffer.
mcvd_->Decode(fake_decoder_buffer_, decode_cb_.Get());
PumpCodec();
// Releasing the output buffer should now trigger a flush.
video_frame_factory_->last_output_buffer_.reset();
EXPECT_CALL(*codec, Flush());
PumpCodec();
}
TEST_P(MediaCodecVideoDecoderTest, EosDecodeCbIsRunAfterEosIsDequeued) {
auto* codec =
InitializeFully_OneDecodePending(TestVideoConfig::Large(codec_));
codec->AcceptOneInput();
PumpCodec();
base::MockCallback<VideoDecoder::DecodeCB> eos_decode_cb;
EXPECT_CALL(eos_decode_cb, Run(_)).Times(0);
mcvd_->Decode(DecoderBuffer::CreateEOSBuffer(), eos_decode_cb.Get());
codec->AcceptOneInput(MockMediaCodecBridge::kEos);
PumpCodec();
// On dequeueing EOS, MCVD will post a closure to run eos_decode_cb after
// pending video frames.
EXPECT_CALL(*video_frame_factory_, MockRunAfterPendingVideoFrames(_));
codec->ProduceOneOutput(MockMediaCodecBridge::kEos);
PumpCodec();
EXPECT_CALL(eos_decode_cb, Run(IsOkStatus()));
std::move(video_frame_factory_->last_closure_).Run();
}
TEST_P(MediaCodecVideoDecoderTest, TeardownInvalidatesCodecCreationWeakPtr) {
InitializeWithTextureOwner_OneDecodePending(TestVideoConfig::Large(codec_));
mcvd_.reset();
EXPECT_CALL(*codec_allocator_, MockReleaseMediaCodec(NotNull()));
ASSERT_TRUE(codec_allocator_->ProvideMockCodecAsync());
}
TEST_P(MediaCodecVideoDecoderTest,
TeardownInvalidatesCodecCreationWeakPtrButDoesNotCallReleaseMediaCodec) {
InitializeWithTextureOwner_OneDecodePending(TestVideoConfig::Large(codec_));
mcvd_.reset();
// A null codec should not be released via ReleaseMediaCodec().
EXPECT_CALL(*codec_allocator_, MockReleaseMediaCodec(_)).Times(0);
codec_allocator_->ProvideNullCodecAsync();
}
TEST_P(MediaCodecVideoDecoderTest, TeardownDoesNotDrainFlushedCodecs) {
InitializeFully_OneDecodePending(TestVideoConfig::Large(codec_));
// Since we assert that MCVD is destructed by default, this test verifies that
// MCVD is destructed without requiring the codec to output an EOS buffer.
// We assert this since, otherwise, we'll complete the drain for VP8 codecs in
// TearDown. This guarantees that we won't, so any drain started by MCVD
// won't complete. Otherwise, this tests nothing. Note that 'Drained' here
// is a bit of a misnomer; the mock codec doesn't track flushed.
ASSERT_TRUE(codec_allocator_->most_recent_codec->IsDrained());
}
TEST_P(MediaCodecVideoDecoderH264Test, TeardownDoesNotDrainNonVp8Codecs) {
auto* codec =
InitializeFully_OneDecodePending(TestVideoConfig::Large(codec_));
// Accept the first decode to transition out of the flushed state.
codec->AcceptOneInput();
PumpCodec();
// Since we assert that MCVD is destructed by default, this test verifies that
// MCVD is destructed without requiring the codec to output an EOS buffer.
// Remember that we do not complete the drain for non-VP8 codecs in TearDown.
}
TEST_P(MediaCodecVideoDecoderVp8Test,
TeardownDrainsVp8CodecsBeforeDestruction) {
auto* codec =
InitializeFully_OneDecodePending(TestVideoConfig::Large(codec_));
// Accept the first decode to transition out of the flushed state.
codec->AcceptOneInput();
PumpCodec();
// MCVD should not be destructed immediately.
mcvd_.reset();
base::RunLoop().RunUntilIdle();
// It should be destructed after draining completes.
codec->AcceptOneInput(MockMediaCodecBridge::kEos);
codec->ProduceOneOutput(MockMediaCodecBridge::kEos);
EXPECT_CALL(*codec, Flush()).Times(0);
PumpCodec();
base::RunLoop().RunUntilIdle();
}
TEST_P(MediaCodecVideoDecoderTest, CdmInitializationWorksForL3) {
// Make sure that MCVD uses the cdm, and sends it along to the codec.
CreateCdm(true, false);
InitializeWithOverlay_OneDecodePending(
TestVideoConfig::NormalEncrypted(codec_));
ASSERT_TRUE(!!cdm_->ran_media_crypto_ready_cb);
ASSERT_EQ(surface_chooser_->current_state_.is_secure, true);
ASSERT_EQ(surface_chooser_->current_state_.is_required, false);
ASSERT_EQ(codec_allocator_->most_recent_config->codec_type, CodecType::kAny);
// We can't check for equality safely, but verify that something was provided.
ASSERT_TRUE(codec_allocator_->most_recent_config->media_crypto);
}
TEST_P(MediaCodecVideoDecoderTest, CdmInitializationWorksForL1) {
// Make sure that MCVD uses the cdm, and sends it along to the codec.
CreateCdm(true, true);
InitializeWithOverlay_OneDecodePending(
TestVideoConfig::NormalEncrypted(codec_));
ASSERT_TRUE(!!cdm_->ran_media_crypto_ready_cb);
ASSERT_EQ(surface_chooser_->current_state_.is_secure, true);
ASSERT_EQ(surface_chooser_->current_state_.is_required, true);
ASSERT_EQ(codec_allocator_->most_recent_config->codec_type,
CodecType::kSecure);
ASSERT_TRUE(codec_allocator_->most_recent_config->media_crypto);
}
TEST_P(MediaCodecVideoDecoderTest, CdmIsSetEvenForClearStream) {
// Make sure that MCVD uses the cdm, and sends it along to the codec.
CreateCdm(true, false);
// We use the Large config, since VPx can be rejected if it's too small, in
// favor of software decode, since this is unencrypted.
InitializeWithOverlay_OneDecodePending(TestVideoConfig::Large(codec_));
ASSERT_TRUE(!!cdm_->ran_media_crypto_ready_cb);
ASSERT_EQ(surface_chooser_->current_state_.is_secure, true);
ASSERT_EQ(surface_chooser_->current_state_.is_required, false);
ASSERT_NE(codec_allocator_->most_recent_config->codec_type,
CodecType::kSecure);
// We can't check for equality safely, but verify that something was provided.
ASSERT_TRUE(codec_allocator_->most_recent_config->media_crypto);
}
TEST_P(MediaCodecVideoDecoderTest, NoMediaCryptoContext_ClearStream) {
// Make sure that MCVD initializes for clear stream when MediaCryptoContext
// is not available.
CreateCdm(false, false);
InitializeWithOverlay_OneDecodePending(TestVideoConfig::Normal(codec_));
ASSERT_FALSE(!!cdm_->media_crypto_ready_cb);
ASSERT_FALSE(!!cdm_->ran_media_crypto_ready_cb);
ASSERT_EQ(surface_chooser_->current_state_.is_secure, false);
ASSERT_EQ(surface_chooser_->current_state_.is_required, false);
ASSERT_NE(codec_allocator_->most_recent_config->codec_type,
CodecType::kSecure);
ASSERT_FALSE(codec_allocator_->most_recent_config->media_crypto);
}
TEST_P(MediaCodecVideoDecoderTest, NoMediaCryptoContext_EncryptedStream) {
// Make sure that MCVD fails to initialize for encrypted stream when
// MediaCryptoContext is not available.
CreateCdm(false, false);
ASSERT_FALSE(Initialize(TestVideoConfig::NormalEncrypted(codec_)));
}
TEST_P(MediaCodecVideoDecoderTest, MissingMediaCryptoFailsInit) {
// Encrypted media that doesn't get a mediacrypto should fail to init.
CreateCdm(true, true);
media_crypto_ = nullptr;
ASSERT_FALSE(Initialize(TestVideoConfig::NormalEncrypted(codec_)));
}
TEST_P(MediaCodecVideoDecoderTest, MissingCdmFailsInit) {
// MCVD should fail init if we don't provide a cdm with an encrypted config.
ASSERT_FALSE(Initialize(TestVideoConfig::NormalEncrypted(codec_)));
}
TEST_P(MediaCodecVideoDecoderTest, VideoFramesArePowerEfficient) {
// MCVD should mark video frames as POWER_EFFICIENT.
auto* codec =
InitializeFully_OneDecodePending(TestVideoConfig::Large(codec_));
// Produce one output.
codec->AcceptOneInput();
codec->ProduceOneOutput();
EXPECT_CALL(*video_frame_factory_, MockCreateVideoFrame(_, _, _, _, _));
PumpCodec();
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(!!most_recent_frame_);
EXPECT_TRUE(most_recent_frame_->metadata().power_efficient);
}
TEST_P(MediaCodecVideoDecoderH264Test, CsdIsIncludedInCodecConfig) {
// Make sure that any CSD is included in the CodecConfig that MCVD uses to
// allocate the codec.
VideoDecoderConfig config = TestVideoConfig::NormalH264();
// Csd, excluding '0 0 0 1'.
std::vector<uint8_t> csd0 = {103, 77, 64, 30, 232, 128, 80, 23,
252, 184, 8, 128, 0, 0, 3, 0,
128, 0, 0, 30, 7, 139, 22, 137};
std::vector<uint8_t> csd1 = {104, 235, 239, 32};
std::vector<uint8_t> extra_data_separator = {1, 0, 4};
std::vector<uint8_t> extra_data = {1, 77, 64, 30, 255, 225, 0, 24};
extra_data.insert(extra_data.end(), csd0.begin(), csd0.end());
extra_data.insert(extra_data.end(), extra_data_separator.begin(),
extra_data_separator.end());
extra_data.insert(extra_data.end(), csd1.begin(), csd1.end());
config.SetExtraData(extra_data);
EXPECT_TRUE(InitializeFully_OneDecodePending(config));
// Prepend the headers and check for equality.
std::vector<uint8_t> csd_header = {0, 0, 0, 1};
csd0.insert(csd0.begin(), csd_header.begin(), csd_header.end());
EXPECT_EQ(csd0, codec_allocator_->most_recent_config->csd0);
csd1.insert(csd1.begin(), csd_header.begin(), csd_header.end());
EXPECT_EQ(csd1, codec_allocator_->most_recent_config->csd1);
}
TEST_P(MediaCodecVideoDecoderVp9Test, ColorSpaceIsIncludedInCodecConfig) {
VideoColorSpace color_space(VideoColorSpace::PrimaryID::BT2020,
VideoColorSpace::TransferID::SMPTEST2084,
VideoColorSpace::MatrixID::BT2020_CL,
gfx::ColorSpace::RangeID::LIMITED);
VideoDecoderConfig config =
TestVideoConfig::NormalWithColorSpace(kCodecVP9, color_space);
EXPECT_TRUE(InitializeFully_OneDecodePending(config));
EXPECT_EQ(color_space,
codec_allocator_->most_recent_config->container_color_space);
}
TEST_P(MediaCodecVideoDecoderVp9Test, HdrMetadataIsIncludedInCodecConfig) {
VideoDecoderConfig config = TestVideoConfig::Normal(kCodecVP9);
gfx::HDRMetadata hdr_metadata;
hdr_metadata.max_frame_average_light_level = 123;
hdr_metadata.max_content_light_level = 456;
hdr_metadata.color_volume_metadata.primary_r.set_x(0.1f);
hdr_metadata.color_volume_metadata.primary_r.set_y(0.2f);
hdr_metadata.color_volume_metadata.primary_g.set_x(0.3f);
hdr_metadata.color_volume_metadata.primary_g.set_y(0.4f);
hdr_metadata.color_volume_metadata.primary_b.set_x(0.5f);
hdr_metadata.color_volume_metadata.primary_b.set_y(0.6f);
hdr_metadata.color_volume_metadata.white_point.set_x(0.7f);
hdr_metadata.color_volume_metadata.white_point.set_y(0.8f);
hdr_metadata.color_volume_metadata.luminance_max = 1000;
hdr_metadata.color_volume_metadata.luminance_min = 0;
config.set_hdr_metadata(hdr_metadata);
EXPECT_TRUE(InitializeFully_OneDecodePending(config));
EXPECT_EQ(hdr_metadata, codec_allocator_->most_recent_config->hdr_metadata);
}
static std::vector<VideoCodec> GetTestList() {
std::vector<VideoCodec> test_codecs;
#if BUILDFLAG(USE_PROPRIETARY_CODECS)
if (MediaCodecUtil::IsMediaCodecAvailable())
test_codecs.push_back(kCodecH264);
#endif
if (MediaCodecUtil::IsVp8DecoderAvailable())
test_codecs.push_back(kCodecVP8);
if (MediaCodecUtil::IsVp9DecoderAvailable())
test_codecs.push_back(kCodecVP9);
if (MediaCodecUtil::IsAv1DecoderAvailable())
test_codecs.push_back(kCodecAV1);
return test_codecs;
}
#if BUILDFLAG(USE_PROPRIETARY_CODECS)
static std::vector<VideoCodec> GetH264IfAvailable() {
return MediaCodecUtil::IsMediaCodecAvailable()
? std::vector<VideoCodec>(1, kCodecH264)
: std::vector<VideoCodec>();
}
#endif
static std::vector<VideoCodec> GetVp8IfAvailable() {
return MediaCodecUtil::IsVp8DecoderAvailable()
? std::vector<VideoCodec>(1, kCodecVP8)
: std::vector<VideoCodec>();
}
// TODO(https://crbug.com/1179801): Uncomment once MediaCodecVideoDecoderVp9Test
// is fixed.
// static std::vector<VideoCodec> GetVp9IfAvailable() {
// return MediaCodecUtil::IsVp9DecoderAvailable()
// ? std::vector<VideoCodec>(1, kCodecVP9)
// : std::vector<VideoCodec>();
// }
static std::vector<VideoCodec> GetAv1IfAvailable() {
return MediaCodecUtil::IsAv1DecoderAvailable()
? std::vector<VideoCodec>(1, kCodecAV1)
: std::vector<VideoCodec>();
}
INSTANTIATE_TEST_SUITE_P(MediaCodecVideoDecoderTest,
MediaCodecVideoDecoderTest,
testing::ValuesIn(GetTestList()));
#if BUILDFLAG(USE_PROPRIETARY_CODECS)
INSTANTIATE_TEST_SUITE_P(MediaCodecVideoDecoderH264Test,
MediaCodecVideoDecoderH264Test,
testing::ValuesIn(GetH264IfAvailable()));
#endif
INSTANTIATE_TEST_SUITE_P(MediaCodecVideoDecoderVp8Test,
MediaCodecVideoDecoderVp8Test,
testing::ValuesIn(GetVp8IfAvailable()));
// TODO(https://crbug.com/1179801): Uncomment once MediaCodecVideoDecoderVp9Test
// is fixed.
// INSTANTIATE_TEST_SUITE_P(MediaCodecVideoDecoderVp9Test,
// MediaCodecVideoDecoderVp9Test,
// testing::ValuesIn(GetVp9IfAvailable()));
INSTANTIATE_TEST_SUITE_P(MediaCodecVideoDecoderAV1Test,
MediaCodecVideoDecoderAV1Test,
testing::ValuesIn(GetAv1IfAvailable()));
// TODO(https://crbug.com/1179801): Remove this annotation once
// MediaCodecVideoDecoderVp9Test is fixed.
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(MediaCodecVideoDecoderVp9Test);
// This test suite is empty on some OSes.
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(MediaCodecVideoDecoderAV1Test);
// For builds without proprietary codecs
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(MediaCodecVideoDecoderH264Test);
} // namespace media