Allow to specify list of supported input configurations.
... and populate corresponding metadata entries / perform
validation based on these.
Bug: 301023410
Test: atest virtual_camera_tests
Change-Id: I66f3cf2b013d5845b6fa7429294a1ed2157318f8
diff --git a/services/camera/virtualcamera/tests/Android.bp b/services/camera/virtualcamera/tests/Android.bp
index c30779c..bc46ba0 100644
--- a/services/camera/virtualcamera/tests/Android.bp
+++ b/services/camera/virtualcamera/tests/Android.bp
@@ -15,6 +15,7 @@
"libgmock",
],
srcs: ["EglUtilTest.cc",
+ "VirtualCameraDeviceTest.cc",
"VirtualCameraProviderTest.cc",
"VirtualCameraRenderThreadTest.cc",
"VirtualCameraServiceTest.cc",
diff --git a/services/camera/virtualcamera/tests/VirtualCameraDeviceTest.cc b/services/camera/virtualcamera/tests/VirtualCameraDeviceTest.cc
new file mode 100644
index 0000000..140ae65
--- /dev/null
+++ b/services/camera/virtualcamera/tests/VirtualCameraDeviceTest.cc
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <memory>
+
+#include "VirtualCameraDevice.h"
+#include "aidl/android/companion/virtualcamera/Format.h"
+#include "aidl/android/companion/virtualcamera/SupportedStreamConfiguration.h"
+#include "aidl/android/hardware/camera/device/CameraMetadata.h"
+#include "aidl/android/hardware/camera/device/StreamConfiguration.h"
+#include "android/binder_interface_utils.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "log/log_main.h"
+#include "system/camera_metadata.h"
+#include "utils/Errors.h"
+
+namespace android {
+namespace companion {
+namespace virtualcamera {
+namespace {
+
+using ::aidl::android::companion::virtualcamera::Format;
+using ::aidl::android::companion::virtualcamera::SupportedStreamConfiguration;
+using ::aidl::android::hardware::camera::device::CameraMetadata;
+using ::aidl::android::hardware::camera::device::Stream;
+using ::aidl::android::hardware::camera::device::StreamConfiguration;
+using ::aidl::android::hardware::camera::device::StreamType;
+using ::aidl::android::hardware::graphics::common::PixelFormat;
+using ::testing::UnorderedElementsAreArray;
+using metadata_stream_t =
+ camera_metadata_enum_android_scaler_available_stream_configurations_t;
+
+constexpr int kCameraId = 42;
+constexpr int kVgaWidth = 640;
+constexpr int kVgaHeight = 480;
+constexpr int kHdWidth = 1280;
+constexpr int kHdHeight = 720;
+
+struct AvailableStreamConfiguration {
+ const int width;
+ const int height;
+ const int pixelFormat;
+ const metadata_stream_t streamConfiguration;
+};
+
+bool operator==(const AvailableStreamConfiguration& a,
+ const AvailableStreamConfiguration& b) {
+ return a.width == b.width && a.height == b.height &&
+ a.pixelFormat == b.pixelFormat &&
+ a.streamConfiguration == b.streamConfiguration;
+}
+
+std::ostream& operator<<(std::ostream& os,
+ const AvailableStreamConfiguration& config) {
+ os << config.width << "x" << config.height << " (pixfmt "
+ << config.pixelFormat << ", streamConfiguration "
+ << config.streamConfiguration << ")";
+ return os;
+}
+
+std::vector<AvailableStreamConfiguration> getAvailableStreamConfigurations(
+ const CameraMetadata& metadata) {
+ const camera_metadata_t* const raw =
+ reinterpret_cast<const camera_metadata_t*>(metadata.metadata.data());
+ camera_metadata_ro_entry_t entry;
+ if (find_camera_metadata_ro_entry(
+ raw, ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS, &entry) !=
+ NO_ERROR) {
+ return {};
+ }
+
+ std::vector<AvailableStreamConfiguration> res;
+ for (int i = 0; i < entry.count; i += 4) {
+ res.push_back(AvailableStreamConfiguration{
+ .width = entry.data.i32[i + 1],
+ .height = entry.data.i32[i + 2],
+ .pixelFormat = entry.data.i32[i],
+ .streamConfiguration =
+ static_cast<metadata_stream_t>(entry.data.i32[i + 3])});
+ }
+ return res;
+}
+
+struct VirtualCameraConfigTestParam {
+ std::vector<SupportedStreamConfiguration> inputConfig;
+ std::vector<AvailableStreamConfiguration> expectedAvailableStreamConfigs;
+};
+
+class VirtualCameraDeviceTest
+ : public testing::TestWithParam<VirtualCameraConfigTestParam> {};
+
+TEST_P(VirtualCameraDeviceTest, cameraCharacteristicsForInputFormat) {
+ const VirtualCameraConfigTestParam& param = GetParam();
+ std::shared_ptr<VirtualCameraDevice> camera =
+ ndk::SharedRefBase::make<VirtualCameraDevice>(
+ kCameraId, param.inputConfig, /*virtualCameraClientCallback=*/nullptr);
+
+ CameraMetadata metadata;
+ ASSERT_TRUE(camera->getCameraCharacteristics(&metadata).isOk());
+ EXPECT_THAT(getAvailableStreamConfigurations(metadata),
+ UnorderedElementsAreArray(param.expectedAvailableStreamConfigs));
+
+ // Configuration needs to succeed for every available stream configuration
+ for (const AvailableStreamConfiguration& config :
+ param.expectedAvailableStreamConfigs) {
+ StreamConfiguration configuration{
+ .streams = std::vector<Stream>{Stream{
+ .streamType = StreamType::OUTPUT,
+ .width = config.width,
+ .height = config.height,
+ .format = static_cast<PixelFormat>(config.pixelFormat),
+ }}};
+ bool aidl_ret;
+ ASSERT_TRUE(
+ camera->isStreamCombinationSupported(configuration, &aidl_ret).isOk());
+ EXPECT_TRUE(aidl_ret);
+ }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ cameraCharacteristicsForInputFormat, VirtualCameraDeviceTest,
+ testing::Values(
+ VirtualCameraConfigTestParam{
+ .inputConfig = {SupportedStreamConfiguration{
+ .width = kVgaWidth,
+ .height = kVgaHeight,
+ .pixelFormat = Format::YUV_420_888}},
+ .expectedAvailableStreamConfigs =
+ {AvailableStreamConfiguration{
+ .width = kVgaWidth,
+ .height = kVgaHeight,
+ .pixelFormat = ANDROID_SCALER_AVAILABLE_FORMATS_YCbCr_420_888,
+ .streamConfiguration =
+ ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT},
+ AvailableStreamConfiguration{
+ .width = kVgaWidth,
+ .height = kVgaHeight,
+ .pixelFormat =
+ ANDROID_SCALER_AVAILABLE_FORMATS_IMPLEMENTATION_DEFINED,
+ .streamConfiguration =
+ ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT},
+ AvailableStreamConfiguration{
+ .width = kVgaWidth,
+ .height = kVgaHeight,
+ .pixelFormat = ANDROID_SCALER_AVAILABLE_FORMATS_BLOB,
+ .streamConfiguration =
+ ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT}}},
+ VirtualCameraConfigTestParam{
+ .inputConfig = {SupportedStreamConfiguration{
+ .width = kVgaWidth,
+ .height = kVgaHeight,
+ .pixelFormat = Format::YUV_420_888},
+ SupportedStreamConfiguration{
+ .width = kHdWidth,
+ .height = kHdHeight,
+ .pixelFormat = Format::YUV_420_888}},
+ .expectedAvailableStreamConfigs = {
+ AvailableStreamConfiguration{
+ .width = kVgaWidth,
+ .height = kVgaHeight,
+ .pixelFormat = ANDROID_SCALER_AVAILABLE_FORMATS_YCbCr_420_888,
+ .streamConfiguration =
+ ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT},
+ AvailableStreamConfiguration{
+ .width = kVgaWidth,
+ .height = kVgaHeight,
+ .pixelFormat =
+ ANDROID_SCALER_AVAILABLE_FORMATS_IMPLEMENTATION_DEFINED,
+ .streamConfiguration =
+ ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT},
+ AvailableStreamConfiguration{
+ .width = kVgaWidth,
+ .height = kVgaHeight,
+ .pixelFormat = ANDROID_SCALER_AVAILABLE_FORMATS_BLOB,
+ .streamConfiguration =
+ ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT},
+ AvailableStreamConfiguration{
+ .width = kHdWidth,
+ .height = kHdHeight,
+ .pixelFormat = ANDROID_SCALER_AVAILABLE_FORMATS_YCbCr_420_888,
+ .streamConfiguration =
+ ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT},
+ AvailableStreamConfiguration{
+ .width = kHdWidth,
+ .height = kHdHeight,
+ .pixelFormat =
+ ANDROID_SCALER_AVAILABLE_FORMATS_IMPLEMENTATION_DEFINED,
+ .streamConfiguration =
+ ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT},
+ AvailableStreamConfiguration{
+ .width = kHdWidth,
+ .height = kHdHeight,
+ .pixelFormat = ANDROID_SCALER_AVAILABLE_FORMATS_BLOB,
+ .streamConfiguration =
+ ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT}}}));
+
+} // namespace
+} // namespace virtualcamera
+} // namespace companion
+} // namespace android
diff --git a/services/camera/virtualcamera/tests/VirtualCameraProviderTest.cc b/services/camera/virtualcamera/tests/VirtualCameraProviderTest.cc
index 03fc2c2..615a77c 100644
--- a/services/camera/virtualcamera/tests/VirtualCameraProviderTest.cc
+++ b/services/camera/virtualcamera/tests/VirtualCameraProviderTest.cc
@@ -32,6 +32,8 @@
namespace virtualcamera {
namespace {
+using ::aidl::android::companion::virtualcamera::Format;
+using ::aidl::android::companion::virtualcamera::SupportedStreamConfiguration;
using ::aidl::android::hardware::camera::common::CameraDeviceStatus;
using ::aidl::android::hardware::camera::common::Status;
using ::aidl::android::hardware::camera::common::TorchModeStatus;
@@ -45,6 +47,8 @@
using ::testing::Not;
using ::testing::Return;
+constexpr int kVgaWidth = 640;
+constexpr int kVgaHeight = 480;
constexpr char kVirtualCameraNameRegex[] =
"device@[0-9]+\\.[0-9]+/virtual/[0-9]+";
@@ -75,6 +79,10 @@
std::shared_ptr<VirtualCameraProvider> mCameraProvider;
std::shared_ptr<MockCameraProviderCallback> mMockCameraProviderCallback =
ndk::SharedRefBase::make<MockCameraProviderCallback>();
+ std::vector<SupportedStreamConfiguration> mInputConfigs = {
+ SupportedStreamConfiguration{.width = kVgaWidth,
+ .height = kVgaHeight,
+ .pixelFormat = Format::YUV_420_888}};
};
TEST_F(VirtualCameraProviderTest, SetNullCameraCallbackFails) {
@@ -100,7 +108,8 @@
.WillOnce(Return(ndk::ScopedAStatus::ok()));
ASSERT_TRUE(mCameraProvider->setCallback(mMockCameraProviderCallback).isOk());
- std::shared_ptr<VirtualCameraDevice> camera = mCameraProvider->createCamera();
+ std::shared_ptr<VirtualCameraDevice> camera =
+ mCameraProvider->createCamera(mInputConfigs);
EXPECT_THAT(camera, Not(IsNull()));
EXPECT_THAT(camera->getCameraName(), MatchesRegex(kVirtualCameraNameRegex));
@@ -117,7 +126,8 @@
cameraDeviceStatusChange(_, CameraDeviceStatus::PRESENT))
.WillOnce(Return(ndk::ScopedAStatus::ok()));
- std::shared_ptr<VirtualCameraDevice> camera = mCameraProvider->createCamera();
+ std::shared_ptr<VirtualCameraDevice> camera =
+ mCameraProvider->createCamera(mInputConfigs);
ASSERT_TRUE(mCameraProvider->setCallback(mMockCameraProviderCallback).isOk());
// Created camera should be in the list of cameras.
@@ -128,7 +138,8 @@
TEST_F(VirtualCameraProviderTest, RemoveCamera) {
ASSERT_TRUE(mCameraProvider->setCallback(mMockCameraProviderCallback).isOk());
- std::shared_ptr<VirtualCameraDevice> camera = mCameraProvider->createCamera();
+ std::shared_ptr<VirtualCameraDevice> camera =
+ mCameraProvider->createCamera(mInputConfigs);
EXPECT_CALL(*mMockCameraProviderCallback,
cameraDeviceStatusChange(Eq(camera->getCameraName()),
@@ -144,7 +155,8 @@
TEST_F(VirtualCameraProviderTest, RemoveNonExistingCamera) {
ASSERT_TRUE(mCameraProvider->setCallback(mMockCameraProviderCallback).isOk());
- std::shared_ptr<VirtualCameraDevice> camera = mCameraProvider->createCamera();
+ std::shared_ptr<VirtualCameraDevice> camera =
+ mCameraProvider->createCamera(mInputConfigs);
// Removing non-existing camera should fail.
const std::string cameraName = "DefinitelyNoTCamera";
diff --git a/services/camera/virtualcamera/tests/VirtualCameraServiceTest.cc b/services/camera/virtualcamera/tests/VirtualCameraServiceTest.cc
index 4fd0b3b..04349b1 100644
--- a/services/camera/virtualcamera/tests/VirtualCameraServiceTest.cc
+++ b/services/camera/virtualcamera/tests/VirtualCameraServiceTest.cc
@@ -37,6 +37,7 @@
namespace {
using ::aidl::android::companion::virtualcamera::BnVirtualCameraCallback;
+using ::aidl::android::companion::virtualcamera::Format;
using ::aidl::android::companion::virtualcamera::VirtualCameraConfiguration;
using ::aidl::android::hardware::camera::common::CameraDeviceStatus;
using ::aidl::android::hardware::camera::common::TorchModeStatus;
@@ -51,8 +52,19 @@
using ::testing::Not;
using ::testing::SizeIs;
+constexpr int kVgaWidth = 640;
+constexpr int kVgaHeight = 480;
+
const VirtualCameraConfiguration kEmptyVirtualCameraConfiguration;
+VirtualCameraConfiguration createConfiguration(const int width, const int height,
+ const Format format) {
+ VirtualCameraConfiguration configuration;
+ configuration.supportedStreamConfigs.push_back(
+ {.width = width, .height = height, .pixelFormat = format});
+ return configuration;
+}
+
class MockCameraProviderCallback : public BnCameraProviderCallback {
public:
MOCK_METHOD(ndk::ScopedAStatus, cameraDeviceStatusChange,
@@ -89,7 +101,7 @@
ASSERT_TRUE(mCameraService
->registerCamera(mNdkOwnerToken,
- kEmptyVirtualCameraConfiguration, &aidlRet)
+ mVgaYUV420OnlyConfiguration, &aidlRet)
.isOk());
ASSERT_TRUE(aidlRet);
}
@@ -106,6 +118,12 @@
Eq(NO_ERROR));
}
+ std::vector<std::string> getCameraIds() {
+ std::vector<std::string> cameraIds;
+ EXPECT_TRUE(mCameraProvider->getCameraIdList(&cameraIds).isOk());
+ return cameraIds;
+ }
+
protected:
std::shared_ptr<VirtualCameraService> mCameraService;
std::shared_ptr<VirtualCameraProvider> mCameraProvider;
@@ -116,6 +134,9 @@
ndk::SpAIBinder mNdkOwnerToken;
int mDevNullFd;
+
+ VirtualCameraConfiguration mVgaYUV420OnlyConfiguration =
+ createConfiguration(kVgaWidth, kVgaHeight, Format::YUV_420_888);
};
TEST_F(VirtualCameraServiceTest, RegisterCameraSucceeds) {
@@ -125,10 +146,11 @@
ASSERT_TRUE(
mCameraService
- ->registerCamera(ndkToken, kEmptyVirtualCameraConfiguration, &aidlRet)
+ ->registerCamera(ndkToken, mVgaYUV420OnlyConfiguration, &aidlRet)
.isOk());
EXPECT_TRUE(aidlRet);
+ EXPECT_THAT(getCameraIds(), SizeIs(1));
}
TEST_F(VirtualCameraServiceTest, RegisterCameraTwiceSecondReturnsFalse) {
@@ -136,10 +158,67 @@
bool aidlRet;
ASSERT_TRUE(mCameraService
- ->registerCamera(mNdkOwnerToken,
- kEmptyVirtualCameraConfiguration, &aidlRet)
+ ->registerCamera(mNdkOwnerToken, mVgaYUV420OnlyConfiguration,
+ &aidlRet)
.isOk());
EXPECT_FALSE(aidlRet);
+ EXPECT_THAT(getCameraIds(), SizeIs(1));
+}
+
+TEST_F(VirtualCameraServiceTest, EmptyConfigurationFails) {
+ bool aidlRet;
+
+ ASSERT_FALSE(mCameraService
+ ->registerCamera(mNdkOwnerToken,
+ kEmptyVirtualCameraConfiguration, &aidlRet)
+ .isOk());
+ EXPECT_FALSE(aidlRet);
+ EXPECT_THAT(getCameraIds(), IsEmpty());
+}
+
+TEST_F(VirtualCameraServiceTest, ConfigurationWithUnsupportedPixelFormatFails) {
+ bool aidlRet;
+
+ VirtualCameraConfiguration config =
+ createConfiguration(kVgaWidth, kVgaHeight, Format::UNKNOWN);
+
+ ASSERT_FALSE(
+ mCameraService->registerCamera(mNdkOwnerToken, config, &aidlRet).isOk());
+ EXPECT_FALSE(aidlRet);
+ EXPECT_THAT(getCameraIds(), IsEmpty());
+}
+
+TEST_F(VirtualCameraServiceTest, ConfigurationWithTooHighResFails) {
+ bool aidlRet;
+ VirtualCameraConfiguration config =
+ createConfiguration(1000000, 1000000, Format::YUV_420_888);
+
+ ASSERT_FALSE(
+ mCameraService->registerCamera(mNdkOwnerToken, config, &aidlRet).isOk());
+ EXPECT_FALSE(aidlRet);
+ EXPECT_THAT(getCameraIds(), IsEmpty());
+}
+
+TEST_F(VirtualCameraServiceTest, ConfigurationWithUnalignedResolutionFails) {
+ bool aidlRet;
+ VirtualCameraConfiguration config =
+ createConfiguration(641, 481, Format::YUV_420_888);
+
+ ASSERT_FALSE(
+ mCameraService->registerCamera(mNdkOwnerToken, config, &aidlRet).isOk());
+ EXPECT_FALSE(aidlRet);
+ EXPECT_THAT(getCameraIds(), IsEmpty());
+}
+
+TEST_F(VirtualCameraServiceTest, ConfigurationWithNegativeResolutionFails) {
+ bool aidlRet;
+ VirtualCameraConfiguration config =
+ createConfiguration(-1, kVgaHeight, Format::YUV_420_888);
+
+ ASSERT_FALSE(
+ mCameraService->registerCamera(mNdkOwnerToken, config, &aidlRet).isOk());
+ EXPECT_FALSE(aidlRet);
+ EXPECT_THAT(getCameraIds(), IsEmpty());
}
TEST_F(VirtualCameraServiceTest, GetCamera) {
@@ -191,13 +270,13 @@
TEST_F(VirtualCameraServiceTest, TestCameraShellCmd) {
execute_shell_command("enable_test_camera");
- std::vector<std::string> cameraIds;
- EXPECT_TRUE(mCameraProvider->getCameraIdList(&cameraIds).isOk());
- EXPECT_THAT(cameraIds, SizeIs(1));
+ std::vector<std::string> cameraIdsAfterEnable = getCameraIds();
+ EXPECT_THAT(cameraIdsAfterEnable, SizeIs(1));
execute_shell_command("disable_test_camera");
- EXPECT_TRUE(mCameraProvider->getCameraIdList(&cameraIds).isOk());
- EXPECT_THAT(cameraIds, IsEmpty());
+
+ std::vector<std::string> cameraIdsAfterDisable = getCameraIds();
+ EXPECT_THAT(cameraIdsAfterDisable, IsEmpty());
}
} // namespace
diff --git a/services/camera/virtualcamera/tests/VirtualCameraSessionTest.cc b/services/camera/virtualcamera/tests/VirtualCameraSessionTest.cc
index 5da080b..0bc5210 100644
--- a/services/camera/virtualcamera/tests/VirtualCameraSessionTest.cc
+++ b/services/camera/virtualcamera/tests/VirtualCameraSessionTest.cc
@@ -17,9 +17,11 @@
#include <cstdint>
#include <memory>
+#include "VirtualCameraDevice.h"
#include "VirtualCameraSession.h"
#include "aidl/android/companion/virtualcamera/BnVirtualCameraCallback.h"
#include "aidl/android/companion/virtualcamera/IVirtualCameraCallback.h"
+#include "aidl/android/companion/virtualcamera/SupportedStreamConfiguration.h"
#include "aidl/android/hardware/camera/device/BnCameraDeviceCallback.h"
#include "aidl/android/hardware/camera/device/StreamConfiguration.h"
#include "aidl/android/hardware/graphics/common/PixelFormat.h"
@@ -37,10 +39,11 @@
constexpr int kWidth = 640;
constexpr int kHeight = 480;
constexpr int kStreamId = 0;
-const std::string kCameraName = "virtual_camera";
+constexpr int kCameraId = 42;
using ::aidl::android::companion::virtualcamera::BnVirtualCameraCallback;
using ::aidl::android::companion::virtualcamera::Format;
+using ::aidl::android::companion::virtualcamera::SupportedStreamConfiguration;
using ::aidl::android::hardware::camera::device::BnCameraDeviceCallback;
using ::aidl::android::hardware::camera::device::BufferRequest;
using ::aidl::android::hardware::camera::device::BufferRequestStatus;
@@ -99,8 +102,15 @@
ndk::SharedRefBase::make<MockCameraDeviceCallback>();
mMockVirtualCameraClientCallback =
ndk::SharedRefBase::make<MockVirtualCameraCallback>();
+ mVirtualCameraDevice = ndk::SharedRefBase::make<VirtualCameraDevice>(
+ kCameraId,
+ std::vector<SupportedStreamConfiguration>{
+ SupportedStreamConfiguration{.width = kWidth,
+ .height = kHeight,
+ .pixelFormat = Format::YUV_420_888}},
+ mMockVirtualCameraClientCallback);
mVirtualCameraSession = ndk::SharedRefBase::make<VirtualCameraSession>(
- kCameraName, mMockCameraDeviceCallback,
+ *mVirtualCameraDevice, mMockCameraDeviceCallback,
mMockVirtualCameraClientCallback);
ON_CALL(*mMockVirtualCameraClientCallback, onStreamConfigured)
@@ -112,6 +122,7 @@
protected:
std::shared_ptr<MockCameraDeviceCallback> mMockCameraDeviceCallback;
std::shared_ptr<MockVirtualCameraCallback> mMockVirtualCameraClientCallback;
+ std::shared_ptr<VirtualCameraDevice> mVirtualCameraDevice;
std::shared_ptr<VirtualCameraSession> mVirtualCameraSession;
};