blob: c47ce57fa6492eb96b14dc25bfea2e099245c125 [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/media_effects/media_effects_service.h"
#include <memory>
#include <optional>
#include "base/containers/span.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/files/scoped_temp_file.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/run_loop.h"
#include "base/test/test_future.h"
#include "components/user_prefs/test/test_browser_context_with_prefs.h"
#include "content/public/test/browser_task_environment.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "services/video_effects/public/cpp/buildflags.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/geometry/insets_f.h"
#if BUILDFLAG(ENABLE_VIDEO_EFFECTS)
#include "components/media_effects/media_effects_model_provider.h"
#include "services/video_effects/public/cpp/video_effects_service_host.h"
#include "services/video_effects/public/mojom/video_effects_processor.mojom.h"
#include "services/video_effects/public/mojom/video_effects_service.mojom-forward.h"
#include "services/video_effects/test/fake_video_effects_service.h"
#endif
namespace {
constexpr char kDeviceId[] = "test_device";
constexpr char kDeviceId1[] = "test_device_1";
constexpr char kDeviceId2[] = "test_device_2";
media::mojom::VideoEffectsConfigurationPtr GetConfigurationSync(
mojo::Remote<media::mojom::ReadonlyVideoEffectsManager>& effects_manager) {
base::test::TestFuture<media::mojom::VideoEffectsConfigurationPtr>
output_configuration;
effects_manager->GetConfiguration(output_configuration.GetCallback());
return output_configuration.Take();
}
void SetFramingSync(VideoEffectsManagerImpl& effects_manager,
float framing_padding_ratio) {
effects_manager.SetConfiguration(media::mojom::VideoEffectsConfiguration::New(
nullptr, nullptr,
media::mojom::Framing::New(gfx::InsetsF{framing_padding_ratio})));
}
#if BUILDFLAG(ENABLE_VIDEO_EFFECTS)
class FakeModelProvider : public MediaEffectsModelProvider {
public:
~FakeModelProvider() override = default;
// MediaEffectsModelProvider:
void AddObserver(Observer* observer) override {
observers_.AddObserver(observer);
if (model_path_) {
observer->OnBackgroundSegmentationModelUpdated(model_path_);
}
}
void RemoveObserver(Observer* observer) override {
observers_.RemoveObserver(observer);
}
// Sets the model path and notifies observers about it:
void SetModelPath(std::optional<base::FilePath> model_path) {
model_path_ = std::move(model_path);
for (auto& observer : observers_) {
observer.OnBackgroundSegmentationModelUpdated(model_path_);
}
}
base::WeakPtr<FakeModelProvider> weak_ptr() {
return weak_ptr_factory_.GetWeakPtr();
}
private:
base::ObserverList<Observer> observers_;
std::optional<base::FilePath> model_path_;
// Must be last:
base::WeakPtrFactory<FakeModelProvider> weak_ptr_factory_{this};
};
#endif
} // namespace
class MediaEffectsServiceTest : public testing::Test {
public:
MediaEffectsServiceTest() {
#if BUILDFLAG(ENABLE_VIDEO_EFFECTS)
auto model_provider = std::make_unique<FakeModelProvider>();
model_provider_ = model_provider->weak_ptr();
service_.emplace(browser_context_.prefs(), std::move(model_provider));
#else
service_.emplace(browser_context_.prefs());
#endif
}
protected:
content::BrowserTaskEnvironment task_environment_;
user_prefs::TestBrowserContextWithPrefs browser_context_;
std::optional<MediaEffectsService> service_;
#if BUILDFLAG(ENABLE_VIDEO_EFFECTS)
base::WeakPtr<FakeModelProvider> model_provider_;
#endif
};
TEST_F(MediaEffectsServiceTest, BindReadonlyVideoEffectsManager) {
mojo::Remote<media::mojom::ReadonlyVideoEffectsManager> effects_manager;
service_->BindReadonlyVideoEffectsManager(
kDeviceId, effects_manager.BindNewPipeAndPassReceiver());
EXPECT_TRUE(GetConfigurationSync(effects_manager)->framing.is_null());
const float kFramingPaddingRatio = 0.2;
SetFramingSync(service_->GetOrCreateVideoEffectsManager(kDeviceId),
kFramingPaddingRatio);
auto configuration = GetConfigurationSync(effects_manager);
EXPECT_EQ(gfx::InsetsF{kFramingPaddingRatio},
configuration->framing->padding_ratios);
}
TEST_F(
MediaEffectsServiceTest,
BindReadonlyVideoEffectsManager_TwoRegistrantsWithSameIdConnectToSameManager) {
mojo::Remote<media::mojom::ReadonlyVideoEffectsManager> effects_manager1;
service_->BindReadonlyVideoEffectsManager(
kDeviceId, effects_manager1.BindNewPipeAndPassReceiver());
const float kFramingPaddingRatio = 0.234;
SetFramingSync(service_->GetOrCreateVideoEffectsManager(kDeviceId),
kFramingPaddingRatio);
EXPECT_EQ(gfx::InsetsF{kFramingPaddingRatio},
GetConfigurationSync(effects_manager1)->framing->padding_ratios);
mojo::Remote<media::mojom::ReadonlyVideoEffectsManager> effects_manager2;
service_->BindReadonlyVideoEffectsManager(
kDeviceId, effects_manager2.BindNewPipeAndPassReceiver());
EXPECT_EQ(gfx::InsetsF{kFramingPaddingRatio},
GetConfigurationSync(effects_manager2)->framing->padding_ratios);
}
TEST_F(
MediaEffectsServiceTest,
BindReadonlyVideoEffectsManager_TwoRegistrantsWithDifferentIdConnectToDifferentManager) {
mojo::Remote<media::mojom::ReadonlyVideoEffectsManager> effects_manager1;
service_->BindReadonlyVideoEffectsManager(
kDeviceId1, effects_manager1.BindNewPipeAndPassReceiver());
const float kFramingPaddingRatio = 0.234;
SetFramingSync(service_->GetOrCreateVideoEffectsManager(kDeviceId1),
kFramingPaddingRatio);
EXPECT_EQ(gfx::InsetsF{kFramingPaddingRatio},
GetConfigurationSync(effects_manager1)->framing->padding_ratios);
mojo::Remote<media::mojom::ReadonlyVideoEffectsManager> effects_manager2;
service_->BindReadonlyVideoEffectsManager(
kDeviceId2, effects_manager2.BindNewPipeAndPassReceiver());
// Expect `framing` to be unset because it is a separate instance of
// `VideoEffectsManager`.
auto framing = std::move(GetConfigurationSync(effects_manager2)->framing);
EXPECT_TRUE(framing.is_null());
}
TEST_F(
MediaEffectsServiceTest,
OnLastReceiverDisconnected_ErasesTheManagerWhenAllReceiversAreDisconnected) {
mojo::Remote<media::mojom::ReadonlyVideoEffectsManager> effects_manager1;
service_->BindReadonlyVideoEffectsManager(
kDeviceId, effects_manager1.BindNewPipeAndPassReceiver());
mojo::Remote<media::mojom::ReadonlyVideoEffectsManager> effects_manager2;
service_->BindReadonlyVideoEffectsManager(
kDeviceId, effects_manager2.BindNewPipeAndPassReceiver());
const float kFramingPaddingRatio = 0.234;
SetFramingSync(service_->GetOrCreateVideoEffectsManager(kDeviceId),
kFramingPaddingRatio);
EXPECT_EQ(gfx::InsetsF{kFramingPaddingRatio},
GetConfigurationSync(effects_manager1)->framing->padding_ratios);
EXPECT_EQ(gfx::InsetsF{kFramingPaddingRatio},
GetConfigurationSync(effects_manager2)->framing->padding_ratios);
effects_manager1.reset();
effects_manager2.reset();
// Wait for the reset to complete
base::RunLoop().RunUntilIdle();
mojo::Remote<media::mojom::ReadonlyVideoEffectsManager> effects_manager3;
service_->BindReadonlyVideoEffectsManager(
kDeviceId, effects_manager3.BindNewPipeAndPassReceiver());
// Expect `framing` to be unset because it is a new instance of
// `VideoEffectsManager`.
EXPECT_TRUE(GetConfigurationSync(effects_manager3)->framing.is_null());
}
#if BUILDFLAG(ENABLE_VIDEO_EFFECTS)
TEST_F(
MediaEffectsServiceTest,
OnLastReceiverDisconnected_ErasesTheManagerWhenAllReceiversAreDisconnectedWithEffects) {
mojo::Remote<video_effects::mojom::VideoEffectsService> service;
video_effects::FakeVideoEffectsService fake_effects_service(
service.BindNewPipeAndPassReceiver());
auto service_reset =
video_effects::SetVideoEffectsServiceRemoteForTesting(&service);
mojo::Remote<media::mojom::ReadonlyVideoEffectsManager> effects_manager1;
service_->BindReadonlyVideoEffectsManager(
kDeviceId, effects_manager1.BindNewPipeAndPassReceiver());
mojo::Remote<media::mojom::ReadonlyVideoEffectsManager> effects_manager2;
service_->BindReadonlyVideoEffectsManager(
kDeviceId, effects_manager2.BindNewPipeAndPassReceiver());
auto effects_processor_future =
fake_effects_service.GetEffectsProcessorCreationFuture();
mojo::Remote<video_effects::mojom::VideoEffectsProcessor> effects_processor;
service_->BindVideoEffectsProcessor(
kDeviceId, effects_processor.BindNewPipeAndPassReceiver());
const float kFramingPaddingRatio = 0.234;
SetFramingSync(service_->GetOrCreateVideoEffectsManager(kDeviceId),
kFramingPaddingRatio);
EXPECT_EQ(gfx::InsetsF{kFramingPaddingRatio},
GetConfigurationSync(effects_manager1)->framing->padding_ratios);
EXPECT_EQ(gfx::InsetsF{kFramingPaddingRatio},
GetConfigurationSync(effects_manager2)->framing->padding_ratios);
// Wait for the fake effects service to create the processor:
EXPECT_TRUE(effects_processor_future.Wait());
ASSERT_EQ(fake_effects_service.GetProcessors().size(), 1u);
EXPECT_EQ(
gfx::InsetsF{kFramingPaddingRatio},
GetConfigurationSync(fake_effects_service.GetProcessors()[kDeviceId]
->GetVideoEffectsManager())
->framing->padding_ratios);
effects_manager1.reset();
effects_manager2.reset();
fake_effects_service.GetProcessors().erase(kDeviceId);
// Wait for the reset to complete
base::RunLoop().RunUntilIdle();
mojo::Remote<media::mojom::ReadonlyVideoEffectsManager> effects_manager3;
service_->BindReadonlyVideoEffectsManager(
kDeviceId, effects_manager3.BindNewPipeAndPassReceiver());
// Expect `framing` to be unset because it is a new instance of
// `VideoEffectsManager`.
EXPECT_TRUE(GetConfigurationSync(effects_manager3)->framing.is_null());
}
TEST_F(MediaEffectsServiceTest, BindVideoEffectsProcessor) {
// Tests that `MediaEffectsService::BindVideoEffectsProcessor()` works, i.e.
// causes the passed in remote to be connected.
mojo::Remote<video_effects::mojom::VideoEffectsService> service;
video_effects::FakeVideoEffectsService fake_effects_service(
service.BindNewPipeAndPassReceiver());
auto service_reset =
video_effects::SetVideoEffectsServiceRemoteForTesting(&service);
auto effects_processor_future =
fake_effects_service.GetEffectsProcessorCreationFuture();
mojo::Remote<video_effects::mojom::VideoEffectsProcessor> effects_processor;
service_->BindVideoEffectsProcessor(
kDeviceId, effects_processor.BindNewPipeAndPassReceiver());
EXPECT_TRUE(effects_processor_future.Wait());
EXPECT_TRUE(effects_processor.is_connected());
EXPECT_EQ(fake_effects_service.GetProcessors().size(), 1u);
}
TEST_F(
MediaEffectsServiceTest,
BindVideoEffectsProcessor_TwoProcessorsWithDifferentIdConnectToDifferentManager) {
constexpr char kDeviceId3[] = "test_device_3";
// Tests that `MediaEffectsService::BindVideoEffectsProcessor()` connects to
// a different manager if a different ID was used. This is validated by
// checking that the managers return different configurations. We also set a
// different config directly via effects manager interface (originating from
// a call to `MediaEffectsService::BindReadonlyVideoEffectsManager()`) so this
// test also checks that a correct relationship is established between manager
// and processor.
mojo::Remote<video_effects::mojom::VideoEffectsService> service;
video_effects::FakeVideoEffectsService fake_effects_service(
service.BindNewPipeAndPassReceiver());
auto service_reset =
video_effects::SetVideoEffectsServiceRemoteForTesting(&service);
mojo::Remote<media::mojom::ReadonlyVideoEffectsManager> effects_manager;
service_->BindReadonlyVideoEffectsManager(
kDeviceId1, effects_manager.BindNewPipeAndPassReceiver());
constexpr float kFramingPaddingRatio1 = 0.234;
SetFramingSync(service_->GetOrCreateVideoEffectsManager(kDeviceId1),
kFramingPaddingRatio1);
EXPECT_EQ(gfx::InsetsF{kFramingPaddingRatio1},
GetConfigurationSync(effects_manager)->framing->padding_ratios);
auto effects_processor_future1 =
fake_effects_service.GetEffectsProcessorCreationFuture();
mojo::Remote<video_effects::mojom::VideoEffectsProcessor> effects_processor1;
service_->BindVideoEffectsProcessor(
kDeviceId2, effects_processor1.BindNewPipeAndPassReceiver());
EXPECT_TRUE(effects_processor_future1.Wait());
ASSERT_EQ(fake_effects_service.GetProcessors().size(), 1u);
constexpr float kFramingPaddingRatio2 = 0.345;
SetFramingSync(service_->GetOrCreateVideoEffectsManager(kDeviceId2),
kFramingPaddingRatio2);
EXPECT_EQ(
gfx::InsetsF{kFramingPaddingRatio2},
GetConfigurationSync(fake_effects_service.GetProcessors()[kDeviceId2]
->GetVideoEffectsManager())
->framing->padding_ratios);
auto effects_processor_future2 =
fake_effects_service.GetEffectsProcessorCreationFuture();
mojo::Remote<video_effects::mojom::VideoEffectsProcessor> effects_processor2;
service_->BindVideoEffectsProcessor(
kDeviceId3, effects_processor2.BindNewPipeAndPassReceiver());
EXPECT_TRUE(effects_processor_future2.Wait());
ASSERT_EQ(fake_effects_service.GetProcessors().size(), 2u);
constexpr float kFramingPaddingRatio3 = 0.456;
SetFramingSync(service_->GetOrCreateVideoEffectsManager(kDeviceId3),
kFramingPaddingRatio3);
auto padding2 = std::move(
GetConfigurationSync(fake_effects_service.GetProcessors()[kDeviceId2]
->GetVideoEffectsManager())
->framing->padding_ratios);
auto padding3 = std::move(
GetConfigurationSync(fake_effects_service.GetProcessors()[kDeviceId3]
->GetVideoEffectsManager())
->framing->padding_ratios);
EXPECT_NE(padding2, padding3);
EXPECT_EQ(gfx::InsetsF{kFramingPaddingRatio2}, padding2);
EXPECT_EQ(gfx::InsetsF{kFramingPaddingRatio3}, padding3);
}
TEST_F(MediaEffectsServiceTest, ModelFileIsOpenedAndSentToVideoEffects) {
constexpr char kFirstModelBytes[] = "abcdefgh";
constexpr char kSecondModelBytes[] = "ijklmnop";
mojo::Remote<video_effects::mojom::VideoEffectsService> service;
video_effects::FakeVideoEffectsService fake_effects_service(
service.BindNewPipeAndPassReceiver());
auto service_reset =
video_effects::SetVideoEffectsServiceRemoteForTesting(&service);
// Setting the model file path for the first time propagates the model file to
// Video Effects Service: Prepare model file:
base::ScopedTempFile temporary_model_file_path1;
ASSERT_TRUE(temporary_model_file_path1.Create());
ASSERT_TRUE(
base::WriteFile(temporary_model_file_path1.path(), kFirstModelBytes));
// Set it on Media Effects Service:
auto model_opened_future =
fake_effects_service.GetBackgroundSegmentationModelFuture();
model_provider_->SetModelPath(temporary_model_file_path1.path());
auto model_file = model_opened_future.Take();
EXPECT_TRUE(model_file.IsValid());
// Validate that the contents match the contents of the model file:
std::string contents(sizeof(kFirstModelBytes), '\0');
ASSERT_TRUE(model_file.Read(0, base::as_writable_byte_span(contents)));
EXPECT_STREQ(contents.data(), kFirstModelBytes);
// Setting the model file path for the second time propagates the model file
// to Video Effects Service: Prepare model file:
base::ScopedTempFile temporary_model_file_path2;
ASSERT_TRUE(temporary_model_file_path2.Create());
ASSERT_TRUE(
base::WriteFile(temporary_model_file_path2.path(), kSecondModelBytes));
// Set it on Media Effects Service:
model_opened_future =
fake_effects_service.GetBackgroundSegmentationModelFuture();
model_provider_->SetModelPath(temporary_model_file_path2.path());
model_file = model_opened_future.Take();
EXPECT_TRUE(model_file.IsValid());
// Validate that the contents match the contents of the model file:
contents.resize(sizeof(kSecondModelBytes));
ASSERT_TRUE(model_file.Read(0, base::as_writable_byte_span(contents)));
EXPECT_STREQ(contents.data(), kSecondModelBytes);
// Setting the model file to a path that doesn't exist does not propagate the
// model to Video Effects Service: Set invalid path to the model:
base::ScopedTempDir temporary_directory;
ASSERT_TRUE(temporary_directory.CreateUniqueTempDir());
model_opened_future =
fake_effects_service.GetBackgroundSegmentationModelFuture();
model_provider_->SetModelPath(
temporary_directory.GetPath().AppendASCII("should_not_exist.tmp"));
model_file = model_opened_future.Take();
EXPECT_FALSE(model_file.IsValid());
model_opened_future =
fake_effects_service.GetBackgroundSegmentationModelFuture();
model_provider_->SetModelPath(std::nullopt);
model_file = model_opened_future.Take();
EXPECT_FALSE(model_file.IsValid());
}
#endif