blob: 85e95c93f71525615031b3c0631c1e60245283f6 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/modules/webcodecs/video_decoder.h"
#include "base/run_loop.h"
#include "build/build_config.h"
#include "media/base/mock_filters.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h"
#include "third_party/blink/renderer/bindings/core/v8/native_value_traits_impl.h"
#include "third_party/blink/renderer/bindings/core/v8/script_function.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise_tester.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_video_decoder_config.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_video_decoder_init.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_video_decoder_support.h"
#include "third_party/blink/renderer/core/testing/mock_function_scope.h"
#include "third_party/blink/renderer/modules/webcodecs/codec_pressure_manager.h"
#include "third_party/blink/renderer/modules/webcodecs/codec_pressure_manager_provider.h"
#include "third_party/blink/renderer/platform/testing/task_environment.h"
#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
// For FakeVideoDecoder.
#if BUILDFLAG(USE_PROPRIETARY_CODECS)
#include "media/filters/h264_to_annex_b_bitstream_converter.h" // nogncheck
#include "media/formats/mp4/box_definitions.h" // nogncheck
#endif
namespace blink {
namespace {
using testing::_;
using testing::Unused;
class FakeVideoDecoder : public VideoDecoder {
public:
FakeVideoDecoder(ScriptState* script_state,
const VideoDecoderInit* init,
ExceptionState& exception_state)
: VideoDecoder(script_state, init, exception_state) {}
~FakeVideoDecoder() override = default;
void SetupMockHardwareDecoder(base::RepeatingClosure quit_closure) {
mock_decoder_ = std::make_unique<media::MockVideoDecoder>(
/*is_platform_decoder=*/true, /*supports_decription=*/false,
/*decoder_id=*/1);
SetupExpectations(std::move(quit_closure));
}
void SetupMockSoftwareDecoder(base::RepeatingClosure quit_closure) {
mock_decoder_ = std::make_unique<media::MockVideoDecoder>(
/*is_platform_decoder=*/false, /*supports_decription=*/false,
/*decoder_id=*/2);
SetupExpectations(std::move(quit_closure));
}
private:
void SetHardwarePreference(HardwarePreference preference) override {}
void SetupExpectations(base::RepeatingClosure quit_closure) {
EXPECT_CALL(*mock_decoder_, GetMaxDecodeRequests())
.WillRepeatedly(testing::Return(4));
EXPECT_CALL(*mock_decoder_, Decode_(_, _))
.WillOnce([](Unused, media::VideoDecoder::DecodeCB& decode_cb) {
scheduler::GetSequencedTaskRunnerForTesting()->PostTask(
FROM_HERE,
blink::BindOnce(std::move(decode_cb), media::OkStatus()));
});
EXPECT_CALL(*mock_decoder_, Initialize_(_, _, _, _, _, _))
.WillOnce([quit_closure](Unused, Unused, Unused,
media::VideoDecoder::InitCB& init_cb, Unused,
Unused) {
scheduler::GetSequencedTaskRunnerForTesting()->PostTask(
FROM_HERE,
blink::BindOnce(std::move(init_cb), media::OkStatus()));
scheduler::GetSequencedTaskRunnerForTesting()->PostTask(
FROM_HERE, std::move(quit_closure));
});
}
MediaDecoderType* decoder() override { return mock_decoder_.get(); }
std::unique_ptr<media::MockVideoDecoder> mock_decoder_;
};
class VideoDecoderTest : public testing::Test {
public:
VideoDecoderTest() = default;
~VideoDecoderTest() override = default;
FakeVideoDecoder* CreateFakeDecoder(ScriptState* script_state,
VideoDecoderInit* init,
ExceptionState& exception_state) {
return MakeGarbageCollected<FakeVideoDecoder>(script_state, init,
exception_state);
}
VideoDecoderInit* CreateVideoDecoderInit(ScriptState* script_state,
MockFunctionScope& mock_functions) {
auto* init = MakeGarbageCollected<VideoDecoderInit>();
init->setOutput(V8VideoFrameOutputCallback::Create(
mock_functions.ExpectNoCall()->ToV8Function(script_state)));
init->setError(V8WebCodecsErrorCallback::Create(
mock_functions.ExpectNoCall()->ToV8Function(script_state)));
return init;
}
VideoDecoderConfig* CreateVideoConfig() {
auto* config = MakeGarbageCollected<VideoDecoderConfig>();
config->setCodec("vp09.00.10.08");
return config;
}
VideoDecoderSupport* ToVideoDecoderSupport(V8TestingScope* v8_scope,
ScriptValue value) {
return NativeValueTraits<VideoDecoderSupport>::NativeValue(
v8_scope->GetIsolate(), value.V8Value(), v8_scope->GetExceptionState());
}
test::TaskEnvironment task_environment_;
};
TEST_F(VideoDecoderTest, HardwareDecodersApplyPressure) {
V8TestingScope v8_scope;
MockFunctionScope mock_functions(v8_scope.GetScriptState());
auto& pressure_manager_provider =
CodecPressureManagerProvider::From(*v8_scope.GetExecutionContext());
auto* decoder_pressure_manager =
pressure_manager_provider.GetDecoderPressureManager();
auto* encoder_pressure_manager =
pressure_manager_provider.GetEncoderPressureManager();
auto* fake_decoder = CreateFakeDecoder(
v8_scope.GetScriptState(),
CreateVideoDecoderInit(v8_scope.GetScriptState(), mock_functions),
v8_scope.GetExceptionState());
ASSERT_TRUE(fake_decoder);
ASSERT_FALSE(v8_scope.GetExceptionState().HadException());
ASSERT_FALSE(fake_decoder->is_applying_codec_pressure());
ASSERT_EQ(0u, decoder_pressure_manager->pressure_for_testing());
ASSERT_EQ(0u, encoder_pressure_manager->pressure_for_testing());
{
base::RunLoop run_loop;
fake_decoder->SetupMockHardwareDecoder(run_loop.QuitClosure());
fake_decoder->configure(CreateVideoConfig(), v8_scope.GetExceptionState());
ASSERT_FALSE(v8_scope.GetExceptionState().HadException());
run_loop.Run();
}
// Make sure VideoDecoders apply pressure when configured with a HW decoder.
ASSERT_TRUE(fake_decoder->is_applying_codec_pressure());
ASSERT_EQ(1u, decoder_pressure_manager->pressure_for_testing());
ASSERT_EQ(0u, encoder_pressure_manager->pressure_for_testing());
{
base::RunLoop run_loop;
fake_decoder->SetupMockSoftwareDecoder(run_loop.QuitClosure());
fake_decoder->configure(CreateVideoConfig(), v8_scope.GetExceptionState());
ASSERT_FALSE(v8_scope.GetExceptionState().HadException());
run_loop.Run();
}
// Make sure the pressure is released when reconfigured with a SW decoder.
ASSERT_FALSE(fake_decoder->is_applying_codec_pressure());
ASSERT_EQ(0u, decoder_pressure_manager->pressure_for_testing());
ASSERT_EQ(0u, encoder_pressure_manager->pressure_for_testing());
}
TEST_F(VideoDecoderTest, ResetReleasesPressure) {
V8TestingScope v8_scope;
MockFunctionScope mock_functions(v8_scope.GetScriptState());
auto* fake_decoder = CreateFakeDecoder(
v8_scope.GetScriptState(),
CreateVideoDecoderInit(v8_scope.GetScriptState(), mock_functions),
v8_scope.GetExceptionState());
ASSERT_TRUE(fake_decoder);
ASSERT_FALSE(v8_scope.GetExceptionState().HadException());
// Create a HW decoder.
{
base::RunLoop run_loop;
fake_decoder->SetupMockHardwareDecoder(run_loop.QuitClosure());
fake_decoder->configure(CreateVideoConfig(), v8_scope.GetExceptionState());
ASSERT_FALSE(v8_scope.GetExceptionState().HadException());
run_loop.Run();
}
// Make sure VideoDecoders apply pressure when configured with a HW decoder.
ASSERT_TRUE(fake_decoder->is_applying_codec_pressure());
// Satisfy reclamation preconditions.
fake_decoder->SimulateLifecycleStateForTesting(
scheduler::SchedulingLifecycleState::kHidden);
fake_decoder->SetGlobalPressureExceededFlag(true);
// The reclamation timer should be running.
EXPECT_TRUE(fake_decoder->IsReclamationTimerActiveForTesting());
// Reset the codec.
fake_decoder->reset(v8_scope.GetExceptionState());
// The underlying codec might not be internally released.
ASSERT_TRUE(fake_decoder->is_applying_codec_pressure());
EXPECT_TRUE(fake_decoder->IsReclamationTimerActiveForTesting());
// Reclaiming the codec after a period of inactivity should release pressure.
fake_decoder->SimulateCodecReclaimedForTesting();
ASSERT_FALSE(fake_decoder->is_applying_codec_pressure());
EXPECT_FALSE(fake_decoder->IsReclamationTimerActiveForTesting());
}
TEST_F(VideoDecoderTest, isConfigureSupportedWithInvalidSWConfig) {
V8TestingScope v8_scope;
auto* config = MakeGarbageCollected<VideoDecoderConfig>();
config->setCodec("invalid video codec");
config->setHardwareAcceleration(V8HardwarePreference::Enum::kPreferSoftware);
auto promise = VideoDecoder::isConfigSupported(
v8_scope.GetScriptState(), config, v8_scope.GetExceptionState());
ASSERT_FALSE(v8_scope.GetExceptionState().HadException());
ScriptPromiseTester tester(v8_scope.GetScriptState(), promise);
tester.WaitUntilSettled();
ASSERT_TRUE(tester.IsFulfilled());
auto* result = ToVideoDecoderSupport(&v8_scope, tester.Value());
EXPECT_FALSE(result->supported());
}
TEST_F(VideoDecoderTest, isConfigureSupportedWithInvalidHWConfig) {
V8TestingScope v8_scope;
auto* config = MakeGarbageCollected<VideoDecoderConfig>();
config->setCodec("invalid video codec");
config->setHardwareAcceleration(V8HardwarePreference::Enum::kPreferHardware);
auto promise = VideoDecoder::isConfigSupported(
v8_scope.GetScriptState(), config, v8_scope.GetExceptionState());
ASSERT_FALSE(v8_scope.GetExceptionState().HadException());
ScriptPromiseTester tester(v8_scope.GetScriptState(), promise);
tester.WaitUntilSettled();
ASSERT_TRUE(tester.IsFulfilled());
auto* result = ToVideoDecoderSupport(&v8_scope, tester.Value());
EXPECT_FALSE(result->supported());
}
} // namespace
} // namespace blink