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/VirtualCameraDevice.cc b/services/camera/virtualcamera/VirtualCameraDevice.cc
index 6c8c0f7..e21afb7 100644
--- a/services/camera/virtualcamera/VirtualCameraDevice.cc
+++ b/services/camera/virtualcamera/VirtualCameraDevice.cc
@@ -18,13 +18,19 @@
#define LOG_TAG "VirtualCameraDevice"
#include "VirtualCameraDevice.h"
+#include <algorithm>
+#include <array>
#include <chrono>
#include <cstdint>
+#include <iterator>
+#include <optional>
#include <string>
#include "VirtualCameraSession.h"
+#include "aidl/android/companion/virtualcamera/SupportedStreamConfiguration.h"
#include "aidl/android/hardware/camera/common/Status.h"
#include "aidl/android/hardware/camera/device/CameraMetadata.h"
+#include "aidl/android/hardware/camera/device/StreamConfiguration.h"
#include "android/binder_auto_utils.h"
#include "android/binder_status.h"
#include "log/log.h"
@@ -36,13 +42,16 @@
namespace companion {
namespace virtualcamera {
+using ::aidl::android::companion::virtualcamera::Format;
using ::aidl::android::companion::virtualcamera::IVirtualCameraCallback;
+using ::aidl::android::companion::virtualcamera::SupportedStreamConfiguration;
using ::aidl::android::hardware::camera::common::CameraResourceCost;
using ::aidl::android::hardware::camera::common::Status;
using ::aidl::android::hardware::camera::device::CameraMetadata;
using ::aidl::android::hardware::camera::device::ICameraDeviceCallback;
using ::aidl::android::hardware::camera::device::ICameraDeviceSession;
using ::aidl::android::hardware::camera::device::ICameraInjectionSession;
+using ::aidl::android::hardware::camera::device::Stream;
using ::aidl::android::hardware::camera::device::StreamConfiguration;
using ::aidl::android::hardware::camera::device::StreamRotation;
using ::aidl::android::hardware::camera::device::StreamType;
@@ -55,16 +64,68 @@
// Prefix of camera name - "device@1.1/virtual/{numerical_id}"
const char* kDevicePathPrefix = "device@1.1/virtual/";
-constexpr int32_t kVgaWidth = 640;
-constexpr int32_t kVgaHeight = 480;
constexpr std::chrono::nanoseconds kMinFrameDuration30Fps = 1s / 30;
constexpr int32_t kMaxJpegSize = 3 * 1024 * 1024 /*3MiB*/;
constexpr MetadataBuilder::ControlRegion kDefaultEmptyControlRegion{};
+struct Resolution {
+ Resolution(const int w, const int h) : width(w), height(h) {
+ }
+
+ bool operator<(const Resolution& other) const {
+ return width * height < other.width * other.height;
+ }
+
+ bool operator==(const Resolution& other) const {
+ return width == other.width && height == other.height;
+ }
+
+ const int width;
+ const int height;
+};
+
+std::optional<Resolution> getMaxResolution(
+ const std::vector<SupportedStreamConfiguration>& configs) {
+ auto itMax = std::max_element(configs.begin(), configs.end(),
+ [](const SupportedStreamConfiguration& a,
+ const SupportedStreamConfiguration& b) {
+ return a.width * b.height < a.width * b.height;
+ });
+ if (itMax == configs.end()) {
+ ALOGE(
+ "%s: empty vector of supported configurations, cannot find largest "
+ "resolution.",
+ __func__);
+ return std::nullopt;
+ }
+
+ return Resolution(itMax->width, itMax->height);
+}
+
+std::set<Resolution> getUniqueResolutions(
+ const std::vector<SupportedStreamConfiguration>& configs) {
+ std::set<Resolution> uniqueResolutions;
+ std::transform(configs.begin(), configs.end(),
+ std::inserter(uniqueResolutions, uniqueResolutions.begin()),
+ [](const SupportedStreamConfiguration& config) {
+ return Resolution(config.width, config.height);
+ });
+ return uniqueResolutions;
+}
+
// TODO(b/301023410) - Populate camera characteristics according to camera configuration.
-CameraMetadata initCameraCharacteristics() {
- auto metadata =
+std::optional<CameraMetadata> initCameraCharacteristics(
+ const std::vector<SupportedStreamConfiguration>& supportedInputConfig) {
+ if (!std::all_of(supportedInputConfig.begin(), supportedInputConfig.end(),
+ [](const SupportedStreamConfiguration& config) {
+ return config.pixelFormat == Format::YUV_420_888;
+ })) {
+ ALOGE("%s: input configuration contains unsupported pixel format", __func__);
+ return std::nullopt;
+ }
+
+ MetadataBuilder builder =
MetadataBuilder()
.setSupportedHardwareLevel(
ANDROID_INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL)
@@ -73,29 +134,8 @@
.setSensorOrientation(0)
.setAvailableFaceDetectModes({ANDROID_STATISTICS_FACE_DETECT_MODE_OFF})
.setControlAfAvailableModes({ANDROID_CONTROL_AF_MODE_OFF})
- .setAvailableOutputStreamConfigurations(
- {MetadataBuilder::StreamConfiguration{
- .width = kVgaWidth,
- .height = kVgaHeight,
- .format =
- ANDROID_SCALER_AVAILABLE_FORMATS_IMPLEMENTATION_DEFINED,
- .minFrameDuration = kMinFrameDuration30Fps,
- .minStallDuration = 0s},
- MetadataBuilder::StreamConfiguration{
- .width = kVgaWidth,
- .height = kVgaHeight,
- .format = ANDROID_SCALER_AVAILABLE_FORMATS_YCbCr_420_888,
- .minFrameDuration = kMinFrameDuration30Fps,
- .minStallDuration = 0s},
- {MetadataBuilder::StreamConfiguration{
- .width = kVgaWidth,
- .height = kVgaHeight,
- .format = ANDROID_SCALER_AVAILABLE_FORMATS_BLOB,
- .minFrameDuration = kMinFrameDuration30Fps,
- .minStallDuration = 0s}}})
.setControlAeAvailableFpsRange(10, 30)
.setControlMaxRegions(0, 0, 0)
- .setSensorActiveArraySize(0, 0, kVgaWidth, kVgaHeight)
.setControlAfRegions({kDefaultEmptyControlRegion})
.setControlAeRegions({kDefaultEmptyControlRegion})
.setControlAwbRegions({kDefaultEmptyControlRegion})
@@ -106,9 +146,66 @@
.setAvailableResultKeys({ANDROID_CONTROL_AF_MODE})
.setAvailableCapabilities(
{ANDROID_REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE})
- .setAvailableCharacteristicKeys()
- .build();
+ .setAvailableCharacteristicKeys();
+ // Active array size must correspond to largest supported input resolution.
+ std::optional<Resolution> maxResolution =
+ getMaxResolution(supportedInputConfig);
+ if (!maxResolution.has_value()) {
+ return std::nullopt;
+ }
+ builder.setSensorActiveArraySize(0, 0, maxResolution->width,
+ maxResolution->height);
+
+ std::vector<MetadataBuilder::StreamConfiguration> outputConfigurations;
+
+ // TODO(b/301023410) Add also all "standard" resolutions we can rescale the
+ // streams to (all standard resolutions with same aspect ratio).
+
+ // Add IMPLEMENTATION_DEFINED format for all supported input resolutions.
+ std::set<Resolution> uniqueResolutions =
+ getUniqueResolutions(supportedInputConfig);
+ std::transform(
+ uniqueResolutions.begin(), uniqueResolutions.end(),
+ std::back_inserter(outputConfigurations),
+ [](const Resolution& resolution) {
+ return MetadataBuilder::StreamConfiguration{
+ .width = resolution.width,
+ .height = resolution.height,
+ .format = ANDROID_SCALER_AVAILABLE_FORMATS_IMPLEMENTATION_DEFINED,
+ .minFrameDuration = kMinFrameDuration30Fps,
+ .minStallDuration = 0s};
+ });
+
+ // Add all supported configuration with explicit pixel format.
+ std::transform(supportedInputConfig.begin(), supportedInputConfig.end(),
+ std::back_inserter(outputConfigurations),
+ [](const SupportedStreamConfiguration& config) {
+ return MetadataBuilder::StreamConfiguration{
+ .width = config.width,
+ .height = config.height,
+ .format = static_cast<int>(config.pixelFormat),
+ .minFrameDuration = kMinFrameDuration30Fps,
+ .minStallDuration = 0s};
+ });
+
+ // TODO(b/301023410) We currently don't support rescaling for still capture,
+ // so only announce BLOB support for formats exactly matching the input.
+ std::transform(uniqueResolutions.begin(), uniqueResolutions.end(),
+ std::back_inserter(outputConfigurations),
+ [](const Resolution& resolution) {
+ return MetadataBuilder::StreamConfiguration{
+ .width = resolution.width,
+ .height = resolution.height,
+ .format = ANDROID_SCALER_AVAILABLE_FORMATS_BLOB,
+ .minFrameDuration = kMinFrameDuration30Fps,
+ .minStallDuration = 0s};
+ });
+
+ ALOGV("Adding %zu output configurations", outputConfigurations.size());
+ builder.setAvailableOutputStreamConfigurations(outputConfigurations);
+
+ auto metadata = builder.build();
if (metadata == nullptr) {
ALOGE("Failed to build metadata!");
return CameraMetadata();
@@ -121,10 +218,21 @@
VirtualCameraDevice::VirtualCameraDevice(
const uint32_t cameraId,
+ const std::vector<SupportedStreamConfiguration>& supportedInputConfig,
std::shared_ptr<IVirtualCameraCallback> virtualCameraClientCallback)
: mCameraId(cameraId),
- mVirtualCameraClientCallback(virtualCameraClientCallback) {
- mCameraCharacteristics = initCameraCharacteristics();
+ mVirtualCameraClientCallback(virtualCameraClientCallback),
+ mSupportedInputConfigurations(supportedInputConfig) {
+ std::optional<CameraMetadata> metadata =
+ initCameraCharacteristics(mSupportedInputConfigurations);
+ if (metadata.has_value()) {
+ mCameraCharacteristics = *metadata;
+ } else {
+ ALOGE(
+ "%s: Failed to initialize camera characteristic based on provided "
+ "configuration.",
+ __func__);
+ }
}
ndk::ScopedAStatus VirtualCameraDevice::getCameraCharacteristics(
@@ -168,29 +276,42 @@
return cameraStatus(Status::ILLEGAL_ARGUMENT);
}
- for (const auto& stream : in_streams.streams) {
+ *_aidl_return = isStreamCombinationSupported(in_streams);
+ return ndk::ScopedAStatus::ok();
+};
+
+bool VirtualCameraDevice::isStreamCombinationSupported(
+ const StreamConfiguration& streamConfiguration) const {
+ for (const Stream& stream : streamConfiguration.streams) {
ALOGV("%s: Configuration queried: %s", __func__, stream.toString().c_str());
if (stream.streamType == StreamType::INPUT) {
ALOGW("%s: Input stream type is not supported", __func__);
- *_aidl_return = false;
- return ndk::ScopedAStatus::ok();
+ return false;
}
// TODO(b/301023410) remove hardcoded format checks, verify against configuration.
- if (stream.width != 640 || stream.height != 480 ||
- stream.rotation != StreamRotation::ROTATION_0 ||
+ if (stream.rotation != StreamRotation::ROTATION_0 ||
(stream.format != PixelFormat::IMPLEMENTATION_DEFINED &&
stream.format != PixelFormat::YCBCR_420_888 &&
stream.format != PixelFormat::BLOB)) {
- *_aidl_return = false;
- return ndk::ScopedAStatus::ok();
+ ALOGV("Unsupported output stream type");
+ return false;
+ }
+
+ auto matchesSupportedInputConfig =
+ [&stream](const SupportedStreamConfiguration& config) {
+ return stream.width == config.width && stream.height == config.height;
+ };
+ if (std::none_of(mSupportedInputConfigurations.begin(),
+ mSupportedInputConfigurations.end(),
+ matchesSupportedInputConfig)) {
+ ALOGV("Requested config doesn't match any supported input config");
+ return false;
}
}
-
- *_aidl_return = true;
- return ndk::ScopedAStatus::ok();
-};
+ return true;
+}
ndk::ScopedAStatus VirtualCameraDevice::open(
const std::shared_ptr<ICameraDeviceCallback>& in_callback,
@@ -198,7 +319,7 @@
ALOGV("%s", __func__);
*_aidl_return = ndk::SharedRefBase::make<VirtualCameraSession>(
- std::to_string(mCameraId), in_callback, mVirtualCameraClientCallback);
+ *this, in_callback, mVirtualCameraClientCallback);
return ndk::ScopedAStatus::ok();
};
diff --git a/services/camera/virtualcamera/VirtualCameraDevice.h b/services/camera/virtualcamera/VirtualCameraDevice.h
index 0c95b7a..4c3cfc2 100644
--- a/services/camera/virtualcamera/VirtualCameraDevice.h
+++ b/services/camera/virtualcamera/VirtualCameraDevice.h
@@ -21,6 +21,7 @@
#include <memory>
#include "aidl/android/companion/virtualcamera/IVirtualCameraCallback.h"
+#include "aidl/android/companion/virtualcamera/SupportedStreamConfiguration.h"
#include "aidl/android/hardware/camera/device/BnCameraDevice.h"
namespace android {
@@ -34,6 +35,9 @@
public:
explicit VirtualCameraDevice(
uint32_t cameraId,
+ const std::vector<
+ aidl::android::companion::virtualcamera::SupportedStreamConfiguration>&
+ supportedInputConfig,
std::shared_ptr<
::aidl::android::companion::virtualcamera::IVirtualCameraCallback>
virtualCameraClientCallback = nullptr);
@@ -58,6 +62,10 @@
in_streams,
bool* _aidl_return) override;
+ bool isStreamCombinationSupported(
+ const ::aidl::android::hardware::camera::device::StreamConfiguration&
+ in_streams) const;
+
ndk::ScopedAStatus open(
const std::shared_ptr<
::aidl::android::hardware::camera::device::ICameraDeviceCallback>&
@@ -94,6 +102,10 @@
mVirtualCameraClientCallback;
::aidl::android::hardware::camera::device::CameraMetadata mCameraCharacteristics;
+
+ const std::vector<
+ aidl::android::companion::virtualcamera::SupportedStreamConfiguration>
+ mSupportedInputConfigurations;
};
} // namespace virtualcamera
diff --git a/services/camera/virtualcamera/VirtualCameraProvider.cc b/services/camera/virtualcamera/VirtualCameraProvider.cc
index b2bdd06..25a43d6 100644
--- a/services/camera/virtualcamera/VirtualCameraProvider.cc
+++ b/services/camera/virtualcamera/VirtualCameraProvider.cc
@@ -34,6 +34,7 @@
namespace virtualcamera {
using ::aidl::android::companion::virtualcamera::IVirtualCameraCallback;
+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::VendorTagSection;
@@ -154,9 +155,10 @@
}
std::shared_ptr<VirtualCameraDevice> VirtualCameraProvider::createCamera(
+ const std::vector<SupportedStreamConfiguration>& supportedInputConfig,
std::shared_ptr<IVirtualCameraCallback> virtualCameraClientCallback) {
auto camera = ndk::SharedRefBase::make<VirtualCameraDevice>(
- sNextId++, virtualCameraClientCallback);
+ sNextId++, supportedInputConfig, virtualCameraClientCallback);
std::shared_ptr<ICameraProviderCallback> callback;
{
const std::lock_guard<std::mutex> lock(mLock);
diff --git a/services/camera/virtualcamera/VirtualCameraProvider.h b/services/camera/virtualcamera/VirtualCameraProvider.h
index e0f72fa..d41a005 100644
--- a/services/camera/virtualcamera/VirtualCameraProvider.h
+++ b/services/camera/virtualcamera/VirtualCameraProvider.h
@@ -77,6 +77,9 @@
//
// TODO(b/301023410) - Add camera configuration.
std::shared_ptr<VirtualCameraDevice> createCamera(
+ const std::vector<
+ aidl::android::companion::virtualcamera::SupportedStreamConfiguration>&
+ supportedInputConfig,
std::shared_ptr<aidl::android::companion::virtualcamera::IVirtualCameraCallback>
virtualCameraClientCallback = nullptr);
diff --git a/services/camera/virtualcamera/VirtualCameraService.cc b/services/camera/virtualcamera/VirtualCameraService.cc
index 62dc08b..8afd901 100644
--- a/services/camera/virtualcamera/VirtualCameraService.cc
+++ b/services/camera/virtualcamera/VirtualCameraService.cc
@@ -15,7 +15,6 @@
*/
// #define LOG_NDEBUG 0
-#include "android/binder_status.h"
#define LOG_TAG "VirtualCameraService"
#include "VirtualCameraService.h"
@@ -27,9 +26,12 @@
#include "VirtualCameraDevice.h"
#include "VirtualCameraProvider.h"
+#include "aidl/android/companion/virtualcamera/Format.h"
+#include "aidl/android/companion/virtualcamera/VirtualCameraConfiguration.h"
#include "android/binder_auto_utils.h"
#include "android/binder_libbinder.h"
#include "binder/Status.h"
+#include "util/Util.h"
using ::android::binder::Status;
@@ -37,10 +39,14 @@
namespace companion {
namespace virtualcamera {
+using ::aidl::android::companion::virtualcamera::Format;
+using ::aidl::android::companion::virtualcamera::SupportedStreamConfiguration;
using ::aidl::android::companion::virtualcamera::VirtualCameraConfiguration;
namespace {
+constexpr int kVgaWidth = 640;
+constexpr int kVgaHeight = 480;
constexpr char kEnableTestCameraCmd[] = "enable_test_camera";
constexpr char kDisableTestCameraCmd[] = "disable_test_camera";
constexpr char kShellCmdHelp[] = R"(
@@ -49,6 +55,27 @@
* disable_test_camera
)";
+ndk::ScopedAStatus validateConfiguration(
+ const VirtualCameraConfiguration& configuration) {
+ if (configuration.supportedStreamConfigs.empty()) {
+ ALOGE("%s: No supported input configuration specified", __func__);
+ return ndk::ScopedAStatus::fromServiceSpecificError(
+ Status::EX_ILLEGAL_ARGUMENT);
+ }
+
+ for (const SupportedStreamConfiguration& config :
+ configuration.supportedStreamConfigs) {
+ if (!isFormatSupportedForInput(config.width, config.height,
+ config.pixelFormat)) {
+ ALOGE("%s: Requested unsupported input format: %d x %d (%d)", __func__,
+ config.width, config.height, static_cast<int>(config.pixelFormat));
+ return ndk::ScopedAStatus::fromServiceSpecificError(
+ Status::EX_ILLEGAL_ARGUMENT);
+ }
+ }
+ return ndk::ScopedAStatus::ok();
+}
+
} // namespace
VirtualCameraService::VirtualCameraService(
@@ -59,13 +86,18 @@
ndk::ScopedAStatus VirtualCameraService::registerCamera(
const ::ndk::SpAIBinder& token,
const VirtualCameraConfiguration& configuration, bool* _aidl_return) {
- (void)configuration;
if (_aidl_return == nullptr) {
return ndk::ScopedAStatus::fromServiceSpecificError(
Status::EX_ILLEGAL_ARGUMENT);
}
*_aidl_return = true;
+ auto status = validateConfiguration(configuration);
+ if (!status.isOk()) {
+ *_aidl_return = false;
+ return status;
+ }
+
std::lock_guard lock(mLock);
if (mTokenToCameraName.find(token) != mTokenToCameraName.end()) {
ALOGE(
@@ -74,11 +106,13 @@
"0x%" PRIxPTR,
reinterpret_cast<uintptr_t>(token.get()));
*_aidl_return = false;
+ return ndk::ScopedAStatus::ok();
}
// TODO(b/301023410) Validate configuration and pass it to the camera.
std::shared_ptr<VirtualCameraDevice> camera =
- mVirtualCameraProvider->createCamera(configuration.virtualCameraCallback);
+ mVirtualCameraProvider->createCamera(configuration.supportedStreamConfigs,
+ configuration.virtualCameraCallback);
if (camera == nullptr) {
ALOGE("Failed to create camera for binder token 0x%" PRIxPTR,
reinterpret_cast<uintptr_t>(token.get()));
@@ -159,7 +193,10 @@
mTestCameraToken.set(AIBinder_fromPlatformBinder(token));
bool ret;
- registerCamera(mTestCameraToken, VirtualCameraConfiguration(), &ret);
+ VirtualCameraConfiguration configuration;
+ configuration.supportedStreamConfigs.push_back(
+ {.width = kVgaWidth, .height = kVgaHeight, Format::YUV_420_888});
+ registerCamera(mTestCameraToken, configuration, &ret);
if (ret) {
dprintf(out, "Successfully registered test camera %s",
getCamera(mTestCameraToken)->getCameraName().c_str());
diff --git a/services/camera/virtualcamera/VirtualCameraSession.cc b/services/camera/virtualcamera/VirtualCameraSession.cc
index 55678b7..ff6235f 100644
--- a/services/camera/virtualcamera/VirtualCameraSession.cc
+++ b/services/camera/virtualcamera/VirtualCameraSession.cc
@@ -34,6 +34,7 @@
#include "CameraMetadata.h"
#include "EGL/egl.h"
+#include "VirtualCameraDevice.h"
#include "VirtualCameraRenderThread.h"
#include "VirtualCameraStream.h"
#include "aidl/android/hardware/camera/common/Status.h"
@@ -152,10 +153,10 @@
} // namespace
VirtualCameraSession::VirtualCameraSession(
- const std::string& cameraId,
+ VirtualCameraDevice& cameraDevice,
std::shared_ptr<ICameraDeviceCallback> cameraDeviceCallback,
std::shared_ptr<IVirtualCameraCallback> virtualCameraClientCallback)
- : mCameraId(cameraId),
+ : mCameraDevice(cameraDevice),
mCameraDeviceCallback(cameraDeviceCallback),
mVirtualCameraClientCallback(virtualCameraClientCallback) {
mRequestMetadataQueue = std::make_unique<RequestMetadataQueue>(
@@ -204,18 +205,14 @@
int inputWidth;
int inputHeight;
+ if (!mCameraDevice.isStreamCombinationSupported(in_requestedConfiguration)) {
+ ALOGE("%s: Requested stream configuration is not supported", __func__);
+ return cameraStatus(Status::ILLEGAL_ARGUMENT);
+ }
+
{
std::lock_guard<std::mutex> lock(mLock);
for (int i = 0; i < in_requestedConfiguration.streams.size(); ++i) {
- // TODO(b/301023410) remove hardcoded format checks, verify against configuration.
- if (streams[i].width != 640 || streams[i].height != 480 ||
- streams[i].rotation != StreamRotation::ROTATION_0 ||
- (streams[i].format != PixelFormat::IMPLEMENTATION_DEFINED &&
- streams[i].format != PixelFormat::YCBCR_420_888 &&
- streams[i].format != PixelFormat::BLOB)) {
- halStreams.clear();
- return cameraStatus(Status::ILLEGAL_ARGUMENT);
- }
halStreams[i] = getHalStream(streams[i]);
if (mSessionContext.initializeStream(streams[i])) {
ALOGV("Configured new stream: %s", streams[i].toString().c_str());
diff --git a/services/camera/virtualcamera/VirtualCameraSession.h b/services/camera/virtualcamera/VirtualCameraSession.h
index 440720e..6df5d58 100644
--- a/services/camera/virtualcamera/VirtualCameraSession.h
+++ b/services/camera/virtualcamera/VirtualCameraSession.h
@@ -34,6 +34,8 @@
namespace companion {
namespace virtualcamera {
+class VirtualCameraDevice;
+
// Implementation of ICameraDeviceSession AIDL interface to allow camera
// framework to read image data from open virtual camera device. This class
// encapsulates possibly several image streams for the same session.
@@ -44,7 +46,7 @@
// When virtualCameraClientCallback is null, the input surface will be filled
// with test pattern.
VirtualCameraSession(
- const std::string& cameraId,
+ VirtualCameraDevice& mCameraDevice,
std::shared_ptr<
::aidl::android::hardware::camera::device::ICameraDeviceCallback>
cameraDeviceCallback,
@@ -114,7 +116,7 @@
const ::aidl::android::hardware::camera::device::CaptureRequest& request)
EXCLUDES(mLock);
- const std::string mCameraId;
+ VirtualCameraDevice& mCameraDevice;
mutable std::mutex mLock;
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;
};
diff --git a/services/camera/virtualcamera/util/Util.cc b/services/camera/virtualcamera/util/Util.cc
index 78236a4..11dc3a6 100644
--- a/services/camera/virtualcamera/util/Util.cc
+++ b/services/camera/virtualcamera/util/Util.cc
@@ -18,10 +18,19 @@
#include <unistd.h>
+#include "jpeglib.h"
+
namespace android {
namespace companion {
namespace virtualcamera {
+// Lower bound for maximal supported texture size is at least 2048x2048
+// but on most platforms will be more.
+// TODO(b/301023410) - Query actual max texture size.
+constexpr int kMaxTextureSize = 2048;
+constexpr int kLibJpegDctSize = DCTSIZE;
+
+using ::aidl::android::companion::virtualcamera::Format;
using ::aidl::android::hardware::common::NativeHandle;
sp<Fence> importFence(const NativeHandle& aidlHandle) {
@@ -32,6 +41,29 @@
return sp<Fence>::make(::dup(aidlHandle.fds[0].get()));
}
+// Returns true if specified format is supported for virtual camera input.
+bool isFormatSupportedForInput(const int width, const int height,
+ const Format format) {
+ if (format != Format::YUV_420_888) {
+ // For now only YUV_420_888 is supported for input.
+ return false;
+ }
+
+ if (width <= 0 || height <= 0 || width > kMaxTextureSize ||
+ height > kMaxTextureSize) {
+ return false;
+ }
+
+ if (width % kLibJpegDctSize != 0 || height % kLibJpegDctSize != 0) {
+ // Input dimension needs to be multiple of libjpeg DCT size.
+ // TODO(b/301023410) This restriction can be removed once we add support for
+ // unaligned jpeg compression.
+ return false;
+ }
+
+ return true;
+}
+
} // namespace virtualcamera
} // namespace companion
} // namespace android
diff --git a/services/camera/virtualcamera/util/Util.h b/services/camera/virtualcamera/util/Util.h
index 1a0a458..b778321 100644
--- a/services/camera/virtualcamera/util/Util.h
+++ b/services/camera/virtualcamera/util/Util.h
@@ -19,6 +19,7 @@
#include <cstdint>
+#include "aidl/android/companion/virtualcamera/Format.h"
#include "aidl/android/hardware/camera/common/Status.h"
#include "aidl/android/hardware/camera/device/StreamBuffer.h"
#include "android/binder_auto_utils.h"
@@ -42,6 +43,11 @@
sp<Fence> importFence(
const ::aidl::android::hardware::common::NativeHandle& handle);
+// Returns true if specified format is supported for virtual camera input.
+bool isFormatSupportedForInput(
+ int width, int height,
+ ::aidl::android::companion::virtualcamera::Format format);
+
} // namespace virtualcamera
} // namespace companion
} // namespace android