blob: 303e3116a97816c55a52b95ca2fc3be8cf65597d [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/run_loop.h"
#include "base/test/mock_callback.h"
#include "base/test/scoped_task_environment.h"
#include "base/threading/thread_task_runner_handle.h"
#include "gpu/command_buffer/service/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_drm_bridge_cdm_context.h"
#include "media/base/decoder_buffer.h"
#include "media/base/gmock_callback_support.h"
#include "media/base/test_helpers.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/mock_surface_texture_gl_owner.h"
#include "media/gpu/android/video_frame_factory.h"
#include "testing/gtest/include/gtest/gtest.h"
using testing::InvokeWithoutArgs;
using testing::NiceMock;
using testing::NotNull;
using testing::Return;
using testing::SaveArg;
using testing::_;
namespace media {
namespace {
void OutputCb(const scoped_refptr<VideoFrame>&) {}
std::unique_ptr<AndroidOverlay> CreateAndroidOverlayCb(
const base::UnguessableToken&,
AndroidOverlayConfig) {
return nullptr;
}
// Make MCVD's destruction observable for teardown tests.
struct DestructionObservableMCVD : public DestructionObservable,
public MediaCodecVideoDecoder {
using MediaCodecVideoDecoder::MediaCodecVideoDecoder;
};
} // namespace
class MockVideoFrameFactory : public VideoFrameFactory {
public:
MOCK_METHOD2(Initialize, void(bool wants_promotion_hint, InitCb init_cb));
MOCK_METHOD1(MockSetSurfaceBundle, void(scoped_refptr<AVDASurfaceBundle>));
MOCK_METHOD6(
MockCreateVideoFrame,
void(CodecOutputBuffer* raw_output_buffer,
scoped_refptr<SurfaceTextureGLOwner> surface_texture,
base::TimeDelta timestamp,
gfx::Size natural_size,
PromotionHintAggregator::NotifyPromotionHintCB promotion_hint_cb,
VideoDecoder::OutputCB output_cb));
MOCK_METHOD1(MockRunAfterPendingVideoFrames,
void(base::OnceClosure* closure));
MOCK_METHOD0(CancelPendingCallbacks, void());
void SetSurfaceBundle(
scoped_refptr<AVDASurfaceBundle> surface_bundle) override {
MockSetSurfaceBundle(surface_bundle);
if (!surface_bundle) {
surface_texture_ = nullptr;
} else {
surface_texture_ =
surface_bundle->overlay ? nullptr : surface_bundle->surface_texture;
}
}
void CreateVideoFrame(
std::unique_ptr<CodecOutputBuffer> output_buffer,
base::TimeDelta timestamp,
gfx::Size natural_size,
PromotionHintAggregator::NotifyPromotionHintCB promotion_hint_cb,
VideoDecoder::OutputCB output_cb) override {
MockCreateVideoFrame(output_buffer.get(), surface_texture_, timestamp,
natural_size, promotion_hint_cb, output_cb);
last_output_buffer_ = std::move(output_buffer);
}
void RunAfterPendingVideoFrames(base::OnceClosure closure) override {
last_closure_ = std::move(closure);
MockRunAfterPendingVideoFrames(&last_closure_);
}
std::unique_ptr<CodecOutputBuffer> last_output_buffer_;
scoped_refptr<SurfaceTextureGLOwner> surface_texture_;
base::OnceClosure last_closure_;
};
class MediaCodecVideoDecoderTest : public testing::Test {
public:
MediaCodecVideoDecoderTest() = default;
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 {
// 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 surface_texture =
base::MakeRefCounted<NiceMock<MockSurfaceTextureGLOwner>>(0, nullptr,
nullptr);
surface_texture_ = surface_texture.get();
auto video_frame_factory =
std::make_unique<NiceMock<MockVideoFrameFactory>>();
video_frame_factory_ = video_frame_factory.get();
// Set up VFF to pass |surface_texture_| via its InitCb.
const bool want_promotion_hint =
device_info_->IsSetOutputSurfaceSupported();
ON_CALL(*video_frame_factory_, Initialize(want_promotion_hint, _))
.WillByDefault(RunCallback<1>(surface_texture));
auto* observable_mcvd = new DestructionObservableMCVD(
gpu_preferences_, device_info_.get(), codec_allocator_.get(),
std::move(surface_chooser),
base::BindRepeating(&CreateAndroidOverlayCb),
base::Bind(&MediaCodecVideoDecoderTest::RequestOverlayInfoCb,
base::Unretained(this)),
std::move(video_frame_factory));
mcvd_.reset(observable_mcvd);
mcvd_raw_ = observable_mcvd;
destruction_observer_ = observable_mcvd->CreateDestructionObserver();
// Ensure MCVD doesn't leak by default.
destruction_observer_->ExpectDestruction();
}
void CreateCdm(bool require_secure_video_decoder) {
cdm_ = std::make_unique<MockMediaDrmBridgeCdmContext>(cdm_id_);
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 = TestVideoConfig::Large(kCodecH264)) {
if (!mcvd_)
CreateMcvd();
bool result = false;
auto init_cb = [](bool* result_out, bool result) { *result_out = result; };
mcvd_->Initialize(config, false, cdm_.get(), base::Bind(init_cb, &result),
base::Bind(&OutputCb));
base::RunLoop().RunUntilIdle();
if (config.is_encrypted() && cdm_) {
// If the output is encrypted, then we expect that MCVD will be waiting
// for the media crypto object.
// TODO(liberato): why does CreateJavaObjectPtr() not link?
cdm_->media_crypto_ready_cb.Run(
std::make_unique<base::android::ScopedJavaGlobalRef<jobject>>(
media_crypto_),
require_secure_video_decoder_);
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 = TestVideoConfig::Large(kCodecH264)) {
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();
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 InitializeWithSurfaceTexture_OneDecodePending(
VideoDecoderConfig config = TestVideoConfig::Large(kCodecH264)) {
Initialize(config);
mcvd_->Decode(fake_decoder_buffer_, decode_cb_.Get());
provide_overlay_info_cb_.Run(OverlayInfo());
surface_chooser_->ProvideSurfaceTexture();
}
// Fully initializes MCVD and returns the codec it's configured with. MCVD
// will have one decode pending.
MockMediaCodecBridge* InitializeFully_OneDecodePending(
VideoDecoderConfig config = TestVideoConfig::Large(kCodecH264)) {
InitializeWithSurfaceTexture_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); }
void RequestOverlayInfoCb(
bool restart_for_transitions,
const ProvideOverlayInfoCB& provide_overlay_info_cb) {
restart_for_transitions_ = restart_for_transitions;
provide_overlay_info_cb_ = provide_overlay_info_cb;
}
protected:
base::test::ScopedTaskEnvironment scoped_task_environment_;
scoped_refptr<DecoderBuffer> fake_decoder_buffer_;
std::unique_ptr<MockDeviceInfo> device_info_;
std::unique_ptr<FakeCodecAllocator> codec_allocator_;
MockAndroidVideoSurfaceChooser* surface_chooser_;
MockSurfaceTextureGLOwner* surface_texture_;
MockVideoFrameFactory* video_frame_factory_;
NiceMock<base::MockCallback<VideoDecoder::DecodeCB>> decode_cb_;
std::unique_ptr<DestructionObserver> destruction_observer_;
ProvideOverlayInfoCB provide_overlay_info_cb_;
bool restart_for_transitions_;
gpu::GpuPreferences gpu_preferences_;
const int cdm_id_ = 123;
// This is not an actual media crypto object.
base::android::ScopedJavaGlobalRef<jobject> media_crypto_;
bool require_secure_video_decoder_ = false;
// |mcvd_raw_| lets us call PumpCodec() even after |mcvd_| is dropped, for
// testing the teardown path.
MediaCodecVideoDecoder* mcvd_raw_;
std::unique_ptr<MediaCodecVideoDecoder> mcvd_;
// This must outlive |mcvd_| .
std::unique_ptr<MockMediaDrmBridgeCdmContext> cdm_;
};
TEST_F(MediaCodecVideoDecoderTest, UnknownCodecIsRejected) {
ASSERT_FALSE(Initialize(TestVideoConfig::Invalid()));
}
TEST_F(MediaCodecVideoDecoderTest, H264IsSupported) {
// H264 is always supported by MCVD.
ASSERT_TRUE(Initialize(TestVideoConfig::NormalH264()));
}
TEST_F(MediaCodecVideoDecoderTest, SmallVp8IsRejected) {
ASSERT_FALSE(Initialize(TestVideoConfig::Normal()));
}
TEST_F(MediaCodecVideoDecoderTest, InitializeDoesntInitSurfaceOrCodec) {
CreateMcvd();
EXPECT_CALL(*video_frame_factory_, Initialize(_, _)).Times(0);
EXPECT_CALL(*surface_chooser_, MockUpdateState()).Times(0);
EXPECT_CALL(*codec_allocator_, MockCreateMediaCodecAsync(_, _)).Times(0);
Initialize();
}
TEST_F(MediaCodecVideoDecoderTest, FirstDecodeTriggersFrameFactoryInit) {
Initialize();
EXPECT_CALL(*video_frame_factory_, Initialize(_, _));
mcvd_->Decode(fake_decoder_buffer_, decode_cb_.Get());
}
TEST_F(MediaCodecVideoDecoderTest,
FirstDecodeTriggersOverlayInfoRequestIfSupported) {
Initialize();
// 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_F(MediaCodecVideoDecoderTest,
OverlayInfoIsNotRequestedIfOverlaysNotSupported) {
Initialize();
ON_CALL(*device_info_, SupportsOverlaySurfaces())
.WillByDefault(Return(false));
mcvd_->Decode(fake_decoder_buffer_, decode_cb_.Get());
ASSERT_FALSE(provide_overlay_info_cb_);
}
TEST_F(MediaCodecVideoDecoderTest, RestartForOverlayTransitionsFlagIsCorrect) {
ON_CALL(*device_info_, IsSetOutputSurfaceSupported())
.WillByDefault(Return(true));
Initialize();
mcvd_->Decode(fake_decoder_buffer_, decode_cb_.Get());
ASSERT_FALSE(restart_for_transitions_);
}
TEST_F(MediaCodecVideoDecoderTest,
OverlayInfoIsNotRequestedIfThreadedTextureMailboxesEnabled) {
gpu_preferences_.enable_threaded_texture_mailboxes = true;
Initialize();
mcvd_->Decode(fake_decoder_buffer_, decode_cb_.Get());
ASSERT_FALSE(provide_overlay_info_cb_);
}
TEST_F(MediaCodecVideoDecoderTest, OverlayInfoDuringInitUpdatesSurfaceChooser) {
InitializeWithSurfaceTexture_OneDecodePending();
EXPECT_CALL(*surface_chooser_, MockUpdateState());
provide_overlay_info_cb_.Run(OverlayInfo());
}
TEST_F(MediaCodecVideoDecoderTest, CodecIsCreatedAfterSurfaceChosen) {
Initialize();
mcvd_->Decode(fake_decoder_buffer_, decode_cb_.Get());
provide_overlay_info_cb_.Run(OverlayInfo());
EXPECT_CALL(*codec_allocator_, MockCreateMediaCodecAsync(_, NotNull()));
surface_chooser_->ProvideSurfaceTexture();
}
TEST_F(MediaCodecVideoDecoderTest, FrameFactoryInitFailureIsAnError) {
Initialize();
ON_CALL(*video_frame_factory_, Initialize(_, _))
.WillByDefault(RunCallback<1>(nullptr));
EXPECT_CALL(decode_cb_, Run(DecodeStatus::DECODE_ERROR)).Times(1);
EXPECT_CALL(*surface_chooser_, MockUpdateState()).Times(0);
mcvd_->Decode(fake_decoder_buffer_, decode_cb_.Get());
}
TEST_F(MediaCodecVideoDecoderTest, CodecCreationFailureIsAnError) {
InitializeWithSurfaceTexture_OneDecodePending();
mcvd_->Decode(fake_decoder_buffer_, decode_cb_.Get());
EXPECT_CALL(decode_cb_, Run(DecodeStatus::DECODE_ERROR)).Times(2);
// Failing to create a codec should put MCVD into an error state.
codec_allocator_->ProvideNullCodecAsync();
}
TEST_F(MediaCodecVideoDecoderTest, CodecFailuresAreAnError) {
auto* codec = InitializeFully_OneDecodePending();
EXPECT_CALL(*codec, DequeueInputBuffer(_, _))
.WillOnce(Return(MEDIA_CODEC_ERROR));
EXPECT_CALL(decode_cb_, Run(DecodeStatus::DECODE_ERROR));
PumpCodec();
}
TEST_F(MediaCodecVideoDecoderTest, AfterInitCompletesTheCodecIsPolled) {
auto* codec = InitializeFully_OneDecodePending();
// 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_F(MediaCodecVideoDecoderTest, CodecIsReleasedOnDestruction) {
auto* codec = InitializeFully_OneDecodePending();
EXPECT_CALL(*codec_allocator_, MockReleaseMediaCodec(codec, _, _));
}
TEST_F(MediaCodecVideoDecoderTest, SurfaceChooserIsUpdatedOnOverlayChanges) {
InitializeWithSurfaceTexture_OneDecodePending();
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_F(MediaCodecVideoDecoderTest, OverlayInfoUpdatesAreIgnoredInStateError) {
InitializeWithSurfaceTexture_OneDecodePending();
// 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_F(MediaCodecVideoDecoderTest, DuplicateOverlayInfoUpdatesAreIgnored) {
InitializeWithSurfaceTexture_OneDecodePending();
// 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_F(MediaCodecVideoDecoderTest, CodecIsCreatedWithChosenOverlay) {
AndroidOverlay* overlay_passed_to_codec = nullptr;
EXPECT_CALL(*codec_allocator_, MockCreateMediaCodecAsync(_, _))
.WillOnce(SaveArg<0>(&overlay_passed_to_codec));
auto* overlay = InitializeWithOverlay_OneDecodePending();
DCHECK_EQ(overlay, overlay_passed_to_codec);
}
TEST_F(MediaCodecVideoDecoderTest,
CodecCreationWeakPtrIsInvalidatedBySurfaceDestroyed) {
ON_CALL(*device_info_, IsSetOutputSurfaceSupported())
.WillByDefault(Return(false));
auto* overlay = InitializeWithOverlay_OneDecodePending();
overlay->OnSurfaceDestroyed();
// MCVD should invalidate its CodecAllocatorClient WeakPtr so that it doesn't
// receive the codec after surface destroyed. FakeCodecAllocator returns
// nullptr if the client pointer was invalidated.
ASSERT_FALSE(codec_allocator_->ProvideMockCodecAsync());
}
TEST_F(MediaCodecVideoDecoderTest, SurfaceChangedWhileCodecCreationPending) {
auto* overlay = InitializeWithOverlay_OneDecodePending();
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_F(MediaCodecVideoDecoderTest, SurfaceDestroyedDoesSyncSurfaceTransition) {
auto* overlay = InitializeWithOverlay_OneDecodePending();
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_F(MediaCodecVideoDecoderTest,
SurfaceDestroyedReleasesCodecIfSetSurfaceIsNotSupported) {
ON_CALL(*device_info_, IsSetOutputSurfaceSupported())
.WillByDefault(Return(false));
auto* overlay = InitializeWithOverlay_OneDecodePending();
auto* codec = codec_allocator_->ProvideMockCodecAsync();
// MCVD must synchronously release the codec.
EXPECT_CALL(*codec, SetSurface(_)).Times(0);
EXPECT_CALL(*codec_allocator_, MockReleaseMediaCodec(codec, NotNull(), _));
overlay->OnSurfaceDestroyed();
// Verify expectations before we delete the MCVD.
testing::Mock::VerifyAndClearExpectations(codec_allocator_.get());
}
TEST_F(MediaCodecVideoDecoderTest, PumpCodecPerformsPendingSurfaceTransitions) {
InitializeWithOverlay_OneDecodePending();
auto* codec = codec_allocator_->ProvideMockCodecAsync();
// Set a pending surface transition and then call PumpCodec().
surface_chooser_->ProvideSurfaceTexture();
EXPECT_CALL(*codec, SetSurface(_)).WillOnce(Return(true));
PumpCodec();
}
TEST_F(MediaCodecVideoDecoderTest,
SetSurfaceFailureReleasesTheCodecAndSignalsError) {
InitializeWithOverlay_OneDecodePending();
auto* codec = codec_allocator_->ProvideMockCodecAsync();
surface_chooser_->ProvideSurfaceTexture();
EXPECT_CALL(*codec, SetSurface(_)).WillOnce(Return(false));
EXPECT_CALL(decode_cb_, Run(DecodeStatus::DECODE_ERROR)).Times(2);
EXPECT_CALL(*codec_allocator_, MockReleaseMediaCodec(codec, NotNull(), _));
mcvd_->Decode(fake_decoder_buffer_, decode_cb_.Get());
// Verify expectations before we delete the MCVD.
testing::Mock::VerifyAndClearExpectations(codec_allocator_.get());
}
TEST_F(MediaCodecVideoDecoderTest, SurfaceTransitionsCanBeCanceled) {
InitializeWithSurfaceTexture_OneDecodePending();
auto* codec = codec_allocator_->ProvideMockCodecAsync();
// Set a pending transition to an overlay, and then back to a surface texture.
// 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 surface texture should delete the pending overlay.
observer->ExpectDestruction();
surface_chooser_->ProvideSurfaceTexture();
observer.reset();
// Verify that Decode() does not transition the surface
mcvd_->Decode(fake_decoder_buffer_, decode_cb_.Get());
}
TEST_F(MediaCodecVideoDecoderTest, TransitionToSameSurfaceIsIgnored) {
InitializeWithSurfaceTexture_OneDecodePending();
auto* codec = codec_allocator_->ProvideMockCodecAsync();
EXPECT_CALL(*codec, SetSurface(_)).Times(0);
surface_chooser_->ProvideSurfaceTexture();
mcvd_->Decode(fake_decoder_buffer_, decode_cb_.Get());
}
TEST_F(MediaCodecVideoDecoderTest,
ResetBeforeCodecInitializedSucceedsImmediately) {
InitializeWithSurfaceTexture_OneDecodePending();
base::MockCallback<base::Closure> reset_cb;
EXPECT_CALL(reset_cb, Run());
mcvd_->Reset(reset_cb.Get());
}
TEST_F(MediaCodecVideoDecoderTest, ResetAbortsPendingDecodes) {
InitializeWithSurfaceTexture_OneDecodePending();
EXPECT_CALL(decode_cb_, Run(DecodeStatus::ABORTED));
mcvd_->Reset(base::Bind(&base::DoNothing));
}
TEST_F(MediaCodecVideoDecoderTest, ResetAbortsPendingEosDecode) {
// EOS is treated differently by MCVD. This verifies that it's also aborted.
auto* codec = InitializeFully_OneDecodePending();
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(DecodeStatus::ABORTED));
mcvd_->Reset(base::Bind(&base::DoNothing));
}
TEST_F(MediaCodecVideoDecoderTest, ResetDoesNotFlushAnAlreadyFlushedCodec) {
auto* codec = InitializeFully_OneDecodePending();
// The codec is still in the flushed state so Reset() doesn't need to flush.
EXPECT_CALL(*codec, Flush()).Times(0);
base::MockCallback<base::Closure> reset_cb;
EXPECT_CALL(reset_cb, Run());
mcvd_->Reset(reset_cb.Get());
}
TEST_F(MediaCodecVideoDecoderTest, ResetDrainsVP8CodecsBeforeFlushing) {
auto* codec =
InitializeFully_OneDecodePending(TestVideoConfig::Large(kCodecVP8));
// 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::Closure> 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_F(MediaCodecVideoDecoderTest, ResetDoesNotDrainNonVp8Codecs) {
auto* codec = InitializeFully_OneDecodePending();
// 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.
EXPECT_CALL(*codec, Flush());
base::MockCallback<base::Closure> reset_cb;
EXPECT_CALL(reset_cb, Run());
mcvd_->Reset(reset_cb.Get());
}
TEST_F(MediaCodecVideoDecoderTest, TeardownCompletesPendingReset) {
auto* codec =
InitializeFully_OneDecodePending(TestVideoConfig::Large(kCodecVP8));
// Accept the first decode to transition out of the flushed state.
codec->AcceptOneInput();
PumpCodec();
base::MockCallback<base::Closure> 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_F(MediaCodecVideoDecoderTest, CodecFlushIsDeferredAfterDraining) {
auto* codec = InitializeFully_OneDecodePending();
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_F(MediaCodecVideoDecoderTest, EosDecodeCbIsRunAfterEosIsDequeued) {
auto* codec = InitializeFully_OneDecodePending();
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(DecodeStatus::OK));
std::move(video_frame_factory_->last_closure_).Run();
}
TEST_F(MediaCodecVideoDecoderTest, TeardownBeforeInitWorks) {
// Since we assert that MCVD is destructed by default, this test verifies that
// MCVD is destructed safely before Initialize().
}
TEST_F(MediaCodecVideoDecoderTest, TeardownInvalidatesCodecCreationWeakPtr) {
InitializeWithSurfaceTexture_OneDecodePending();
destruction_observer_->DoNotAllowDestruction();
mcvd_.reset();
// DeleteSoon() is now pending. Ensure it's safe if the codec creation
// completes before it runs.
ASSERT_FALSE(codec_allocator_->ProvideMockCodecAsync());
destruction_observer_->ExpectDestruction();
}
TEST_F(MediaCodecVideoDecoderTest, TeardownDoesNotDrainFlushedCodecs) {
InitializeFully_OneDecodePending();
// 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.
}
TEST_F(MediaCodecVideoDecoderTest, TeardownDoesNotDrainNonVp8Codecs) {
auto* codec = InitializeFully_OneDecodePending();
// 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.
}
TEST_F(MediaCodecVideoDecoderTest, TeardownDrainsVp8CodecsBeforeDestruction) {
auto* codec =
InitializeFully_OneDecodePending(TestVideoConfig::Large(kCodecVP8));
// Accept the first decode to transition out of the flushed state.
codec->AcceptOneInput();
PumpCodec();
// MCVD should not be destructed immediately.
destruction_observer_->DoNotAllowDestruction();
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);
destruction_observer_->ExpectDestruction();
PumpCodec();
base::RunLoop().RunUntilIdle();
}
TEST_F(MediaCodecVideoDecoderTest, CdmInitializationWorksForL3) {
// Make sure that MCVD uses the cdm, and sends it along to the codec.
CreateCdm(false);
EXPECT_CALL(*cdm_, RegisterPlayer(_, _));
InitializeWithOverlay_OneDecodePending(
TestVideoConfig::NormalEncrypted(kCodecH264));
ASSERT_TRUE(!!cdm_->new_key_cb);
ASSERT_TRUE(!!cdm_->cdm_unset_cb);
ASSERT_TRUE(!!cdm_->media_crypto_ready_cb);
ASSERT_EQ(surface_chooser_->current_state_.is_secure, true);
ASSERT_EQ(surface_chooser_->current_state_.is_required, false);
ASSERT_FALSE(codec_allocator_->most_recent_config->requires_secure_codec);
// We can't check for equality safely, but verify that something was provided.
ASSERT_TRUE(codec_allocator_->most_recent_config->media_crypto->obj());
// When |mcvd_| is destroyed, expect that it will unregister itself.
EXPECT_CALL(*cdm_,
UnregisterPlayer(MockMediaDrmBridgeCdmContext::kRegistrationId));
}
TEST_F(MediaCodecVideoDecoderTest, CdmInitializationWorksForL1) {
// Make sure that MCVD uses the cdm, and sends it along to the codec.
CreateCdm(true);
EXPECT_CALL(*cdm_, RegisterPlayer(_, _));
InitializeWithOverlay_OneDecodePending(
TestVideoConfig::NormalEncrypted(kCodecH264));
ASSERT_TRUE(!!cdm_->new_key_cb);
ASSERT_TRUE(!!cdm_->cdm_unset_cb);
ASSERT_TRUE(!!cdm_->media_crypto_ready_cb);
ASSERT_EQ(surface_chooser_->current_state_.is_secure, true);
ASSERT_EQ(surface_chooser_->current_state_.is_required, true);
ASSERT_TRUE(codec_allocator_->most_recent_config->requires_secure_codec);
ASSERT_TRUE(codec_allocator_->most_recent_config->media_crypto->obj());
// When |mcvd_| is destroyed, expect that it will unregister itself.
EXPECT_CALL(*cdm_,
UnregisterPlayer(MockMediaDrmBridgeCdmContext::kRegistrationId));
}
TEST_F(MediaCodecVideoDecoderTest, CdmIsIgnoredIfNotEncrypted) {
CreateCdm(true);
// It should not register or unregister.
EXPECT_CALL(*cdm_, RegisterPlayer(_, _)).Times(0);
EXPECT_CALL(*cdm_,
UnregisterPlayer(MockMediaDrmBridgeCdmContext::kRegistrationId))
.Times(0);
ASSERT_TRUE(Initialize(TestVideoConfig::NormalH264()));
ASSERT_TRUE(!cdm_->new_key_cb);
ASSERT_TRUE(!cdm_->cdm_unset_cb);
ASSERT_TRUE(!cdm_->media_crypto_ready_cb);
ASSERT_EQ(surface_chooser_->current_state_.is_secure, false);
ASSERT_EQ(surface_chooser_->current_state_.is_required, false);
}
TEST_F(MediaCodecVideoDecoderTest, MissingMediaCryptoFailsInit) {
// Encrypted media that doesn't get a mediacrypto should fail to init.
CreateCdm(true);
media_crypto_ = nullptr;
EXPECT_CALL(*cdm_, RegisterPlayer(_, _));
ASSERT_FALSE(Initialize(TestVideoConfig::NormalEncrypted(kCodecH264)));
EXPECT_CALL(*cdm_,
UnregisterPlayer(MockMediaDrmBridgeCdmContext::kRegistrationId));
}
TEST_F(MediaCodecVideoDecoderTest, MissingCdmFailsInit) {
// MCVD should fail init if we don't provide a cdm with an encrypted config.
ASSERT_FALSE(Initialize(TestVideoConfig::NormalEncrypted(kCodecH264)));
}
} // namespace media