Move virtual camera service to frameworks/av/services
Bug: 311647154
Bug: 301023410
Test: atest virtual_camera_tests
Test: build & flash & adb shell cmd virtual_camera help
Change-Id: I6d43a2b70f454c9c01ec2abcae9f138cd78c6a85
diff --git a/services/camera/virtualcamera/Android.bp b/services/camera/virtualcamera/Android.bp
new file mode 100644
index 0000000..0375a7c
--- /dev/null
+++ b/services/camera/virtualcamera/Android.bp
@@ -0,0 +1,99 @@
+package {
+ // See: http://go/android-license-faq
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_defaults {
+ name: "libvirtualcamera_defaults",
+ shared_libs: [
+ "android.hardware.common-V2-ndk",
+ "android.hardware.common.fmq-V1-ndk",
+ "libbinder",
+ "libbinder_ndk",
+ "libcamera_metadata",
+ "liblog",
+ "libfmq",
+ "libgui",
+ "libjpeg",
+ "libnativewindow",
+ "libbase",
+ "libcutils",
+ "libui",
+ "libutils",
+ "libEGL",
+ "libGLESv2",
+ "libGLESv3",
+ ],
+ static_libs: [
+ "android.hardware.camera.common@1.0-helper",
+ "android.hardware.camera.common-V1-ndk",
+ "android.hardware.camera.device-V2-ndk",
+ "android.hardware.camera.metadata-V2-ndk",
+ "android.hardware.camera.provider-V2-ndk",
+ "libaidlcommonsupport",
+ "virtual_camera_service_aidl-ndk",
+ ],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wformat",
+ "-Wthread-safety",
+ "-DLOG_NDEBUG=0"
+ ],
+}
+
+cc_library_static {
+ name: "libvirtualcamera_utils",
+ srcs: [
+ "util/JpegUtil.cc",
+ "util/MetadataBuilder.cc",
+ "util/Util.cc",
+ "util/TestPatternHelper.cc",
+ "util/EglDisplayContext.cc",
+ "util/EglFramebuffer.cc",
+ "util/EglProgram.cc",
+ "util/EglSurfaceTexture.cc",
+ "util/EglUtil.cc",
+ ],
+ defaults: [
+ "libvirtualcamera_defaults",
+ ],
+}
+
+cc_library_static {
+ name: "libvirtualcamera",
+ srcs: [
+ "VirtualCameraProvider.cc",
+ "VirtualCameraDevice.cc",
+ "VirtualCameraSession.cc",
+ "VirtualCameraStream.cc",
+ "VirtualCameraService.cc",
+ "VirtualCameraSessionContext.cc",
+ "VirtualCameraRenderThread.cc",
+ ],
+ defaults: [
+ "libvirtualcamera_defaults",
+ ],
+ static_libs: [
+ "libvirtualcamera_utils",
+ ],
+ export_include_dirs: ["."],
+ min_sdk_version: "current",
+}
+
+cc_binary {
+ name: "virtual_camera",
+ srcs: ["main.cc"],
+ defaults: [
+ "libvirtualcamera_defaults",
+ ],
+ static_libs: [
+ "libvirtualcamera",
+ "libvirtualcamera_utils",
+ ],
+ // Remove before flight.
+ // We don't want the service to be started and discovered
+ // yet - remove comments on the lines below in order to
+ // test locally.
+ // init_rc: ["virtual_camera.hal.rc"],
+}
diff --git a/services/camera/virtualcamera/TEST_MAPPING b/services/camera/virtualcamera/TEST_MAPPING
new file mode 100644
index 0000000..04b4025
--- /dev/null
+++ b/services/camera/virtualcamera/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "postsubmit": [
+ {
+ "name": "virtual_camera_tests"
+ }
+ ]
+}
diff --git a/services/camera/virtualcamera/VirtualCameraDevice.cc b/services/camera/virtualcamera/VirtualCameraDevice.cc
new file mode 100644
index 0000000..6c8c0f7
--- /dev/null
+++ b/services/camera/virtualcamera/VirtualCameraDevice.cc
@@ -0,0 +1,248 @@
+/*
+ * 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.
+ */
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "VirtualCameraDevice"
+#include "VirtualCameraDevice.h"
+
+#include <chrono>
+#include <cstdint>
+#include <string>
+
+#include "VirtualCameraSession.h"
+#include "aidl/android/hardware/camera/common/Status.h"
+#include "aidl/android/hardware/camera/device/CameraMetadata.h"
+#include "android/binder_auto_utils.h"
+#include "android/binder_status.h"
+#include "log/log.h"
+#include "system/camera_metadata.h"
+#include "util/MetadataBuilder.h"
+#include "util/Util.h"
+
+namespace android {
+namespace companion {
+namespace virtualcamera {
+
+using ::aidl::android::companion::virtualcamera::IVirtualCameraCallback;
+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::StreamConfiguration;
+using ::aidl::android::hardware::camera::device::StreamRotation;
+using ::aidl::android::hardware::camera::device::StreamType;
+using ::aidl::android::hardware::graphics::common::PixelFormat;
+
+namespace {
+
+using namespace std::chrono_literals;
+
+// 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{};
+
+// TODO(b/301023410) - Populate camera characteristics according to camera configuration.
+CameraMetadata initCameraCharacteristics() {
+ auto metadata =
+ MetadataBuilder()
+ .setSupportedHardwareLevel(
+ ANDROID_INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL)
+ .setFlashAvailable(false)
+ .setLensFacing(ANDROID_LENS_FACING_EXTERNAL)
+ .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})
+ .setControlAeCompensationRange(0, 1)
+ .setControlAeCompensationStep(camera_metadata_rational_t{0, 1})
+ .setMaxJpegSize(kMaxJpegSize)
+ .setAvailableRequestKeys({ANDROID_CONTROL_AF_MODE})
+ .setAvailableResultKeys({ANDROID_CONTROL_AF_MODE})
+ .setAvailableCapabilities(
+ {ANDROID_REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE})
+ .setAvailableCharacteristicKeys()
+ .build();
+
+ if (metadata == nullptr) {
+ ALOGE("Failed to build metadata!");
+ return CameraMetadata();
+ }
+
+ return std::move(*metadata);
+}
+
+} // namespace
+
+VirtualCameraDevice::VirtualCameraDevice(
+ const uint32_t cameraId,
+ std::shared_ptr<IVirtualCameraCallback> virtualCameraClientCallback)
+ : mCameraId(cameraId),
+ mVirtualCameraClientCallback(virtualCameraClientCallback) {
+ mCameraCharacteristics = initCameraCharacteristics();
+}
+
+ndk::ScopedAStatus VirtualCameraDevice::getCameraCharacteristics(
+ CameraMetadata* _aidl_return) {
+ ALOGV("%s", __func__);
+ if (_aidl_return == nullptr) {
+ return cameraStatus(Status::ILLEGAL_ARGUMENT);
+ }
+
+ *_aidl_return = mCameraCharacteristics;
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus VirtualCameraDevice::getPhysicalCameraCharacteristics(
+ const std::string& in_physicalCameraId, CameraMetadata* _aidl_return) {
+ ALOGV("%s: physicalCameraId %s", __func__, in_physicalCameraId.c_str());
+ (void)_aidl_return;
+
+ // VTS tests expect this call to fail with illegal argument status for
+ // all publicly advertised camera ids.
+ // Because we don't support physical camera ids, we just always
+ // fail with illegal argument (there's no valid argument to provide).
+ return cameraStatus(Status::ILLEGAL_ARGUMENT);
+}
+
+ndk::ScopedAStatus VirtualCameraDevice::getResourceCost(
+ CameraResourceCost* _aidl_return) {
+ ALOGV("%s", __func__);
+ if (_aidl_return == nullptr) {
+ return cameraStatus(Status::ILLEGAL_ARGUMENT);
+ }
+ _aidl_return->resourceCost = 100; // ¯\_(ツ)_/¯
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus VirtualCameraDevice::isStreamCombinationSupported(
+ const StreamConfiguration& in_streams, bool* _aidl_return) {
+ ALOGV("%s", __func__);
+
+ if (_aidl_return == nullptr) {
+ return cameraStatus(Status::ILLEGAL_ARGUMENT);
+ }
+
+ for (const auto& stream : in_streams.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();
+ }
+
+ // TODO(b/301023410) remove hardcoded format checks, verify against configuration.
+ if (stream.width != 640 || stream.height != 480 ||
+ 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();
+ }
+ }
+
+ *_aidl_return = true;
+ return ndk::ScopedAStatus::ok();
+};
+
+ndk::ScopedAStatus VirtualCameraDevice::open(
+ const std::shared_ptr<ICameraDeviceCallback>& in_callback,
+ std::shared_ptr<ICameraDeviceSession>* _aidl_return) {
+ ALOGV("%s", __func__);
+
+ *_aidl_return = ndk::SharedRefBase::make<VirtualCameraSession>(
+ std::to_string(mCameraId), in_callback, mVirtualCameraClientCallback);
+
+ return ndk::ScopedAStatus::ok();
+};
+
+ndk::ScopedAStatus VirtualCameraDevice::openInjectionSession(
+ const std::shared_ptr<ICameraDeviceCallback>& in_callback,
+ std::shared_ptr<ICameraInjectionSession>* _aidl_return) {
+ ALOGV("%s", __func__);
+
+ (void)in_callback;
+ (void)_aidl_return;
+ return cameraStatus(Status::OPERATION_NOT_SUPPORTED);
+}
+
+ndk::ScopedAStatus VirtualCameraDevice::setTorchMode(bool in_on) {
+ ALOGV("%s: on = %s", __func__, in_on ? "on" : "off");
+ return cameraStatus(Status::OPERATION_NOT_SUPPORTED);
+}
+
+ndk::ScopedAStatus VirtualCameraDevice::turnOnTorchWithStrengthLevel(
+ int32_t in_torchStrength) {
+ ALOGV("%s: torchStrength = %d", __func__, in_torchStrength);
+ return cameraStatus(Status::OPERATION_NOT_SUPPORTED);
+}
+
+ndk::ScopedAStatus VirtualCameraDevice::getTorchStrengthLevel(
+ int32_t* _aidl_return) {
+ (void)_aidl_return;
+ return cameraStatus(Status::OPERATION_NOT_SUPPORTED);
+}
+
+binder_status_t VirtualCameraDevice::dump(int fd, const char** args,
+ uint32_t numArgs) {
+ // TODO(b/301023410) Implement.
+ (void)fd;
+ (void)args;
+ (void)numArgs;
+ return STATUS_OK;
+}
+
+std::string VirtualCameraDevice::getCameraName() const {
+ return std::string(kDevicePathPrefix) + std::to_string(mCameraId);
+}
+
+} // namespace virtualcamera
+} // namespace companion
+} // namespace android
diff --git a/services/camera/virtualcamera/VirtualCameraDevice.h b/services/camera/virtualcamera/VirtualCameraDevice.h
new file mode 100644
index 0000000..0c95b7a
--- /dev/null
+++ b/services/camera/virtualcamera/VirtualCameraDevice.h
@@ -0,0 +1,103 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_COMPANION_VIRTUALCAMERA_VIRTUALCAMERADEVICE_H
+#define ANDROID_COMPANION_VIRTUALCAMERA_VIRTUALCAMERADEVICE_H
+
+#include <cstdint>
+#include <memory>
+
+#include "aidl/android/companion/virtualcamera/IVirtualCameraCallback.h"
+#include "aidl/android/hardware/camera/device/BnCameraDevice.h"
+
+namespace android {
+namespace companion {
+namespace virtualcamera {
+
+// Representation of single virtual camera device, implements
+// ICameraDevice AIDL to expose camera to camera framework.
+class VirtualCameraDevice
+ : public ::aidl::android::hardware::camera::device::BnCameraDevice {
+ public:
+ explicit VirtualCameraDevice(
+ uint32_t cameraId,
+ std::shared_ptr<
+ ::aidl::android::companion::virtualcamera::IVirtualCameraCallback>
+ virtualCameraClientCallback = nullptr);
+
+ virtual ~VirtualCameraDevice() override = default;
+
+ ndk::ScopedAStatus getCameraCharacteristics(
+ ::aidl::android::hardware::camera::device::CameraMetadata* _aidl_return)
+ override;
+
+ ndk::ScopedAStatus getPhysicalCameraCharacteristics(
+ const std::string& in_physicalCameraId,
+ ::aidl::android::hardware::camera::device::CameraMetadata* _aidl_return)
+ override;
+
+ ndk::ScopedAStatus getResourceCost(
+ ::aidl::android::hardware::camera::common::CameraResourceCost*
+ _aidl_return) override;
+
+ ndk::ScopedAStatus isStreamCombinationSupported(
+ const ::aidl::android::hardware::camera::device::StreamConfiguration&
+ in_streams,
+ bool* _aidl_return) override;
+
+ ndk::ScopedAStatus open(
+ const std::shared_ptr<
+ ::aidl::android::hardware::camera::device::ICameraDeviceCallback>&
+ in_callback,
+ std::shared_ptr<
+ ::aidl::android::hardware::camera::device::ICameraDeviceSession>*
+ _aidl_return) override;
+
+ ndk::ScopedAStatus openInjectionSession(
+ const std::shared_ptr<
+ ::aidl::android::hardware::camera::device::ICameraDeviceCallback>&
+ in_callback,
+ std::shared_ptr<
+ ::aidl::android::hardware::camera::device::ICameraInjectionSession>*
+ _aidl_return) override;
+
+ ndk::ScopedAStatus setTorchMode(bool in_on) override;
+
+ ndk::ScopedAStatus turnOnTorchWithStrengthLevel(
+ int32_t in_torchStrength) override;
+
+ ndk::ScopedAStatus getTorchStrengthLevel(int32_t* _aidl_return) override;
+
+ binder_status_t dump(int fd, const char** args, uint32_t numArgs) override;
+
+ // Returns unique virtual camera name in form
+ // "device@{major}.{minor}/virtual/{numerical_id}"
+ std::string getCameraName() const;
+
+ private:
+ const uint32_t mCameraId;
+ const std::shared_ptr<
+ ::aidl::android::companion::virtualcamera::IVirtualCameraCallback>
+ mVirtualCameraClientCallback;
+
+ ::aidl::android::hardware::camera::device::CameraMetadata mCameraCharacteristics;
+};
+
+} // namespace virtualcamera
+} // namespace companion
+} // namespace android
+
+#endif // ANDROID_COMPANION_VIRTUALCAMERA_VIRTUALCAMERADEVICE_H
diff --git a/services/camera/virtualcamera/VirtualCameraProvider.cc b/services/camera/virtualcamera/VirtualCameraProvider.cc
new file mode 100644
index 0000000..b2bdd06
--- /dev/null
+++ b/services/camera/virtualcamera/VirtualCameraProvider.cc
@@ -0,0 +1,219 @@
+/*
+ * 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.
+ */
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "VirtualCameraProvider"
+#include "VirtualCameraProvider.h"
+
+#include <atomic>
+#include <memory>
+#include <mutex>
+#include <tuple>
+#include <utility>
+
+#include "VirtualCameraDevice.h"
+#include "aidl/android/hardware/camera/common/Status.h"
+#include "log/log.h"
+#include "util/Util.h"
+
+namespace android {
+namespace companion {
+namespace virtualcamera {
+
+using ::aidl::android::companion::virtualcamera::IVirtualCameraCallback;
+using ::aidl::android::hardware::camera::common::CameraDeviceStatus;
+using ::aidl::android::hardware::camera::common::Status;
+using ::aidl::android::hardware::camera::common::VendorTagSection;
+using ::aidl::android::hardware::camera::device::ICameraDevice;
+using ::aidl::android::hardware::camera::provider::CameraIdAndStreamCombination;
+using ::aidl::android::hardware::camera::provider::ConcurrentCameraIdCombination;
+using ::aidl::android::hardware::camera::provider::ICameraProviderCallback;
+
+// TODO(b/301023410) Make camera id range configurable / dynamic
+// based on already registered devices.
+std::atomic_int VirtualCameraProvider::sNextId{42};
+
+ndk::ScopedAStatus VirtualCameraProvider::setCallback(
+ const std::shared_ptr<ICameraProviderCallback>& in_callback) {
+ ALOGV("%s", __func__);
+
+ if (in_callback == nullptr) {
+ return cameraStatus(Status::ILLEGAL_ARGUMENT);
+ }
+
+ {
+ const std::lock_guard<std::mutex> lock(mLock);
+ mCameraProviderCallback = in_callback;
+
+ for (const auto& [cameraName, _] : mCameras) {
+ auto ret = mCameraProviderCallback->cameraDeviceStatusChange(
+ cameraName, CameraDeviceStatus::PRESENT);
+ if (!ret.isOk()) {
+ ALOGE("Failed to announce camera status change: %s",
+ ret.getDescription().c_str());
+ }
+ }
+ }
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus VirtualCameraProvider::getVendorTags(
+ std::vector<VendorTagSection>* _aidl_return) {
+ ALOGV("%s", __func__);
+
+ if (_aidl_return == nullptr) {
+ return cameraStatus(Status::ILLEGAL_ARGUMENT);
+ }
+
+ // No vendor tags supported.
+ _aidl_return->clear();
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus VirtualCameraProvider::getCameraIdList(
+ std::vector<std::string>* _aidl_return) {
+ ALOGV("%s", __func__);
+
+ if (_aidl_return == nullptr) {
+ return cameraStatus(Status::ILLEGAL_ARGUMENT);
+ }
+
+ {
+ const std::lock_guard<std::mutex> lock(mLock);
+ _aidl_return->clear();
+ _aidl_return->reserve(mCameras.size());
+ for (const auto& [cameraName, _] : mCameras) {
+ _aidl_return->emplace_back(cameraName);
+ }
+ }
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus VirtualCameraProvider::getCameraDeviceInterface(
+ const std::string& in_cameraDeviceName,
+ std::shared_ptr<ICameraDevice>* _aidl_return) {
+ ALOGV("%s cameraDeviceName %s", __func__, in_cameraDeviceName.c_str());
+
+ if (_aidl_return == nullptr) {
+ return cameraStatus(Status::ILLEGAL_ARGUMENT);
+ }
+
+ {
+ const std::lock_guard<std::mutex> lock(mLock);
+ const auto it = mCameras.find(in_cameraDeviceName);
+ *_aidl_return = (it == mCameras.end()) ? nullptr : it->second;
+ }
+
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus VirtualCameraProvider::notifyDeviceStateChange(
+ int64_t in_deviceState) {
+ ALOGV("%s", __func__);
+ (void)in_deviceState;
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus VirtualCameraProvider::getConcurrentCameraIds(
+ std::vector<ConcurrentCameraIdCombination>* _aidl_return) {
+ ALOGV("%s", __func__);
+ if (_aidl_return == nullptr) {
+ return cameraStatus(Status::ILLEGAL_ARGUMENT);
+ }
+
+ // No support for any concurrent combination.
+ _aidl_return->clear();
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus VirtualCameraProvider::isConcurrentStreamCombinationSupported(
+ const std::vector<CameraIdAndStreamCombination>& in_configs,
+ bool* _aidl_return) {
+ ALOGV("%s", __func__);
+ (void)in_configs;
+ if (_aidl_return == nullptr) {
+ return cameraStatus(Status::ILLEGAL_ARGUMENT);
+ }
+
+ // No support for any stream combination at the moment.
+ *_aidl_return = false;
+ return ndk::ScopedAStatus::ok();
+}
+
+std::shared_ptr<VirtualCameraDevice> VirtualCameraProvider::createCamera(
+ std::shared_ptr<IVirtualCameraCallback> virtualCameraClientCallback) {
+ auto camera = ndk::SharedRefBase::make<VirtualCameraDevice>(
+ sNextId++, virtualCameraClientCallback);
+ std::shared_ptr<ICameraProviderCallback> callback;
+ {
+ const std::lock_guard<std::mutex> lock(mLock);
+ if (mCameras.find(camera->getCameraName()) != mCameras.end()) {
+ ALOGE("Camera with identical name already exists.");
+ return nullptr;
+ }
+ mCameras.emplace(std::piecewise_construct,
+ std::forward_as_tuple(camera->getCameraName()),
+ std::forward_as_tuple(camera));
+ callback = mCameraProviderCallback;
+ }
+
+ if (callback != nullptr) {
+ auto ret = callback->cameraDeviceStatusChange(camera->getCameraName(),
+ CameraDeviceStatus::PRESENT);
+ if (!ret.isOk()) {
+ ALOGE("Failed to announce camera %s status change (PRESENT): %s",
+ camera->getCameraName().c_str(), ret.getDescription().c_str());
+ }
+ }
+ return camera;
+}
+
+std::shared_ptr<VirtualCameraDevice> VirtualCameraProvider::getCamera(
+ const std::string& cameraName) {
+ const std::lock_guard<std::mutex> lock(mLock);
+ auto it = mCameras.find(cameraName);
+ return it == mCameras.end() ? nullptr : it->second;
+}
+
+bool VirtualCameraProvider::removeCamera(const std::string& name) {
+ std::shared_ptr<ICameraProviderCallback> callback;
+ {
+ const std::lock_guard<std::mutex> lock(mLock);
+ auto it = mCameras.find(name);
+ if (it == mCameras.end()) {
+ ALOGE("Cannot remove camera %s: no such camera", name.c_str());
+ return false;
+ }
+ // TODO(b/301023410) Gracefully shut down camera.
+ mCameras.erase(it);
+ callback = mCameraProviderCallback;
+ }
+
+ if (callback != nullptr) {
+ auto ret = callback->cameraDeviceStatusChange(
+ name, CameraDeviceStatus::NOT_PRESENT);
+ if (!ret.isOk()) {
+ ALOGE("Failed to announce camera %s status change (NOT_PRESENT): %s",
+ name.c_str(), ret.getDescription().c_str());
+ }
+ }
+
+ return true;
+}
+
+} // namespace virtualcamera
+} // namespace companion
+} // namespace android
diff --git a/services/camera/virtualcamera/VirtualCameraProvider.h b/services/camera/virtualcamera/VirtualCameraProvider.h
new file mode 100644
index 0000000..e0f72fa
--- /dev/null
+++ b/services/camera/virtualcamera/VirtualCameraProvider.h
@@ -0,0 +1,105 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_COMPANION_VIRTUALCAMERA_VIRTUALCAMERAPROVIDER_H
+#define ANDROID_COMPANION_VIRTUALCAMERA_VIRTUALCAMERAPROVIDER_H
+
+#include <atomic>
+#include <map>
+#include <memory>
+#include <mutex>
+
+#include "VirtualCameraDevice.h"
+#include "aidl/android/companion/virtualcamera/BnVirtualCameraCallback.h"
+#include "aidl/android/hardware/camera/common/VendorTagSection.h"
+#include "aidl/android/hardware/camera/device/ICameraDevice.h"
+#include "aidl/android/hardware/camera/provider/BnCameraProvider.h"
+#include "aidl/android/hardware/camera/provider/CameraIdAndStreamCombination.h"
+#include "aidl/android/hardware/camera/provider/ConcurrentCameraIdCombination.h"
+#include "aidl/android/hardware/camera/provider/ICameraProviderCallback.h"
+#include "utils/Mutex.h"
+
+namespace android {
+namespace companion {
+namespace virtualcamera {
+
+// Entry point for virtual camera HAL.
+// Allows to create and keep track of virtual camera and implements
+// IAudioProvider AIDL interface to expose virtual camera devices to camera framework.
+class VirtualCameraProvider
+ : public ::aidl::android::hardware::camera::provider::BnCameraProvider {
+ public:
+ ~VirtualCameraProvider() override = default;
+
+ ndk::ScopedAStatus setCallback(
+ const std::shared_ptr<
+ ::aidl::android::hardware::camera::provider::ICameraProviderCallback>&
+ in_callback) override;
+
+ ndk::ScopedAStatus getVendorTags(
+ std::vector<::aidl::android::hardware::camera::common::VendorTagSection>*
+ _aidl_return) override;
+
+ ndk::ScopedAStatus getCameraIdList(
+ std::vector<std::string>* _aidl_return) override;
+
+ ndk::ScopedAStatus getCameraDeviceInterface(
+ const std::string& in_cameraDeviceName,
+ std::shared_ptr<::aidl::android::hardware::camera::device::ICameraDevice>*
+ _aidl_return) override;
+
+ ndk::ScopedAStatus notifyDeviceStateChange(int64_t in_deviceState) override;
+
+ ndk::ScopedAStatus getConcurrentCameraIds(
+ std::vector<::aidl::android::hardware::camera::provider::
+ ConcurrentCameraIdCombination>* _aidl_return) override;
+
+ ndk::ScopedAStatus isConcurrentStreamCombinationSupported(
+ const std::vector<::aidl::android::hardware::camera::provider::
+ CameraIdAndStreamCombination>& in_configs,
+ bool* _aidl_return) override;
+
+ // Create new virtual camera devices
+ // Returns nullptr if creation was not successful.
+ //
+ // TODO(b/301023410) - Add camera configuration.
+ std::shared_ptr<VirtualCameraDevice> createCamera(
+ std::shared_ptr<aidl::android::companion::virtualcamera::IVirtualCameraCallback>
+ virtualCameraClientCallback = nullptr);
+
+ std::shared_ptr<VirtualCameraDevice> getCamera(const std::string& name);
+
+ bool removeCamera(const std::string& name);
+
+ private:
+ std::mutex mLock;
+
+ std::shared_ptr<
+ ::aidl::android::hardware::camera::provider::ICameraProviderCallback>
+ mCameraProviderCallback GUARDED_BY(mLock);
+
+ std::map<std::string, std::shared_ptr<VirtualCameraDevice>> mCameras
+ GUARDED_BY(mLock);
+
+ // Numerical id to assign to next created camera.
+ static std::atomic_int sNextId;
+};
+
+} // namespace virtualcamera
+} // namespace companion
+} // namespace android
+
+#endif // ANDROID_SERVICES_VIRTUAL_CAMERA_VIRTUALCAMERAPROVIDER_H
diff --git a/services/camera/virtualcamera/VirtualCameraRenderThread.cc b/services/camera/virtualcamera/VirtualCameraRenderThread.cc
new file mode 100644
index 0000000..582e47f
--- /dev/null
+++ b/services/camera/virtualcamera/VirtualCameraRenderThread.cc
@@ -0,0 +1,447 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "VirtualCameraRenderThread"
+#include "VirtualCameraRenderThread.h"
+
+#include <chrono>
+#include <cstddef>
+#include <future>
+#include <memory>
+#include <mutex>
+#include <thread>
+
+#include "VirtualCameraSessionContext.h"
+#include "aidl/android/hardware/camera/common/Status.h"
+#include "aidl/android/hardware/camera/device/BufferStatus.h"
+#include "aidl/android/hardware/camera/device/CameraMetadata.h"
+#include "aidl/android/hardware/camera/device/CaptureResult.h"
+#include "aidl/android/hardware/camera/device/ErrorCode.h"
+#include "aidl/android/hardware/camera/device/ICameraDeviceCallback.h"
+#include "aidl/android/hardware/camera/device/NotifyMsg.h"
+#include "aidl/android/hardware/camera/device/ShutterMsg.h"
+#include "aidl/android/hardware/camera/device/StreamBuffer.h"
+#include "android-base/thread_annotations.h"
+#include "android/binder_auto_utils.h"
+#include "android/hardware_buffer.h"
+#include "util/EglFramebuffer.h"
+#include "util/JpegUtil.h"
+#include "util/MetadataBuilder.h"
+#include "util/TestPatternHelper.h"
+#include "util/Util.h"
+#include "utils/Errors.h"
+
+namespace android {
+namespace companion {
+namespace virtualcamera {
+
+using ::aidl::android::hardware::camera::common::Status;
+using ::aidl::android::hardware::camera::device::BufferStatus;
+using ::aidl::android::hardware::camera::device::CameraMetadata;
+using ::aidl::android::hardware::camera::device::CaptureResult;
+using ::aidl::android::hardware::camera::device::ErrorCode;
+using ::aidl::android::hardware::camera::device::ErrorMsg;
+using ::aidl::android::hardware::camera::device::ICameraDeviceCallback;
+using ::aidl::android::hardware::camera::device::NotifyMsg;
+using ::aidl::android::hardware::camera::device::ShutterMsg;
+using ::aidl::android::hardware::camera::device::Stream;
+using ::aidl::android::hardware::camera::device::StreamBuffer;
+using ::aidl::android::hardware::graphics::common::PixelFormat;
+using ::android::base::ScopedLockAssertion;
+
+namespace {
+
+using namespace std::chrono_literals;
+
+static constexpr std::chrono::milliseconds kAcquireFenceTimeout = 500ms;
+
+CameraMetadata createCaptureResultMetadata(
+ const std::chrono::nanoseconds timestamp) {
+ std::unique_ptr<CameraMetadata> metadata =
+ MetadataBuilder().setSensorTimestamp(timestamp).build();
+ if (metadata == nullptr) {
+ ALOGE("%s: Failed to build capture result metadata", __func__);
+ return CameraMetadata();
+ }
+ return std::move(*metadata);
+}
+
+NotifyMsg createShutterNotifyMsg(int frameNumber,
+ std::chrono::nanoseconds timestamp) {
+ NotifyMsg msg;
+ msg.set<NotifyMsg::Tag::shutter>(ShutterMsg{
+ .frameNumber = frameNumber,
+ .timestamp = timestamp.count(),
+ });
+ return msg;
+}
+
+NotifyMsg createBufferErrorNotifyMsg(int frameNumber, int streamId) {
+ NotifyMsg msg;
+ msg.set<NotifyMsg::Tag::error>(ErrorMsg{.frameNumber = frameNumber,
+ .errorStreamId = streamId,
+ .errorCode = ErrorCode::ERROR_BUFFER});
+ return msg;
+}
+
+NotifyMsg createRequestErrorNotifyMsg(int frameNumber) {
+ NotifyMsg msg;
+ msg.set<NotifyMsg::Tag::error>(ErrorMsg{
+ .frameNumber = frameNumber, .errorCode = ErrorCode::ERROR_REQUEST});
+ return msg;
+}
+
+} // namespace
+
+CaptureRequestBuffer::CaptureRequestBuffer(int streamId, int bufferId,
+ sp<Fence> fence)
+ : mStreamId(streamId), mBufferId(bufferId), mFence(fence) {
+}
+
+int CaptureRequestBuffer::getStreamId() const {
+ return mStreamId;
+}
+
+int CaptureRequestBuffer::getBufferId() const {
+ return mBufferId;
+}
+
+sp<Fence> CaptureRequestBuffer::getFence() const {
+ return mFence;
+}
+
+VirtualCameraRenderThread::VirtualCameraRenderThread(
+ VirtualCameraSessionContext& sessionContext, const int mWidth,
+ const int mHeight,
+ std::shared_ptr<ICameraDeviceCallback> cameraDeviceCallback, bool testMode)
+ : mCameraDeviceCallback(cameraDeviceCallback),
+ mInputSurfaceWidth(mWidth),
+ mInputSurfaceHeight(mHeight),
+ mTestMode(testMode),
+ mSessionContext(sessionContext) {
+}
+
+VirtualCameraRenderThread::~VirtualCameraRenderThread() {
+ stop();
+ if (mThread.joinable()) {
+ mThread.join();
+ }
+}
+
+ProcessCaptureRequestTask::ProcessCaptureRequestTask(
+ int frameNumber, const std::vector<CaptureRequestBuffer>& requestBuffers)
+ : mFrameNumber(frameNumber), mBuffers(requestBuffers) {
+}
+
+int ProcessCaptureRequestTask::getFrameNumber() const {
+ return mFrameNumber;
+}
+
+const std::vector<CaptureRequestBuffer>& ProcessCaptureRequestTask::getBuffers()
+ const {
+ return mBuffers;
+}
+
+void VirtualCameraRenderThread::enqueueTask(
+ std::unique_ptr<ProcessCaptureRequestTask> task) {
+ std::lock_guard<std::mutex> lock(mLock);
+ mQueue.emplace_back(std::move(task));
+ mCondVar.notify_one();
+}
+
+void VirtualCameraRenderThread::flush() {
+ std::lock_guard<std::mutex> lock(mLock);
+ for (auto task = std::move(mQueue.front()); !mQueue.empty();
+ mQueue.pop_front()) {
+ flushCaptureRequest(*task);
+ }
+}
+
+void VirtualCameraRenderThread::start() {
+ mThread = std::thread(&VirtualCameraRenderThread::threadLoop, this);
+}
+
+void VirtualCameraRenderThread::stop() {
+ {
+ std::lock_guard<std::mutex> lock(mLock);
+ mPendingExit = true;
+ mCondVar.notify_one();
+ }
+}
+
+sp<Surface> VirtualCameraRenderThread::getInputSurface() {
+ return mInputSurfacePromise.get_future().get();
+}
+
+std::unique_ptr<ProcessCaptureRequestTask>
+VirtualCameraRenderThread::dequeueTask() {
+ std::unique_lock<std::mutex> lock(mLock);
+ // Clang's thread safety analysis doesn't perform alias analysis,
+ // so it doesn't support moveable std::unique_lock.
+ //
+ // Lock assertion below is basically explicit declaration that
+ // the lock is held in this scope, which is true, since it's only
+ // released during waiting inside mCondVar.wait calls.
+ ScopedLockAssertion lockAssertion(mLock);
+
+ mCondVar.wait(lock, [this]() REQUIRES(mLock) {
+ return mPendingExit || !mQueue.empty();
+ });
+ if (mPendingExit) {
+ return nullptr;
+ }
+ std::unique_ptr<ProcessCaptureRequestTask> task = std::move(mQueue.front());
+ mQueue.pop_front();
+ return task;
+}
+
+void VirtualCameraRenderThread::threadLoop() {
+ ALOGV("Render thread starting");
+
+ mEglDisplayContext = std::make_unique<EglDisplayContext>();
+ mEglTextureProgram = std::make_unique<EglTextureProgram>();
+ mEglSurfaceTexture = std::make_unique<EglSurfaceTexture>(mInputSurfaceWidth,
+ mInputSurfaceHeight);
+ mInputSurfacePromise.set_value(mEglSurfaceTexture->getSurface());
+
+ while (std::unique_ptr<ProcessCaptureRequestTask> task = dequeueTask()) {
+ processCaptureRequest(*task);
+ }
+
+ ALOGV("Render thread exiting");
+}
+
+void VirtualCameraRenderThread::processCaptureRequest(
+ const ProcessCaptureRequestTask& request) {
+ const std::chrono::nanoseconds timestamp =
+ std::chrono::duration_cast<std::chrono::nanoseconds>(
+ std::chrono::steady_clock::now().time_since_epoch());
+
+ CaptureResult captureResult;
+ captureResult.fmqResultSize = 0;
+ captureResult.frameNumber = request.getFrameNumber();
+ captureResult.partialResult = 1;
+ captureResult.inputBuffer.streamId = -1;
+ captureResult.physicalCameraMetadata.resize(0);
+ captureResult.result = createCaptureResultMetadata(timestamp);
+
+ const std::vector<CaptureRequestBuffer>& buffers = request.getBuffers();
+ captureResult.outputBuffers.resize(buffers.size());
+
+ if (mTestMode) {
+ // In test mode let's just render something to the Surface ourselves.
+ renderTestPatternYCbCr420(mEglSurfaceTexture->getSurface(),
+ request.getFrameNumber());
+ }
+
+ mEglSurfaceTexture->updateTexture();
+
+ for (int i = 0; i < buffers.size(); ++i) {
+ const CaptureRequestBuffer& reqBuffer = buffers[i];
+ StreamBuffer& resBuffer = captureResult.outputBuffers[i];
+ resBuffer.streamId = reqBuffer.getStreamId();
+ resBuffer.bufferId = reqBuffer.getBufferId();
+ resBuffer.status = BufferStatus::OK;
+
+ const std::optional<Stream> streamConfig =
+ mSessionContext.getStreamConfig(reqBuffer.getStreamId());
+
+ if (!streamConfig.has_value()) {
+ resBuffer.status = BufferStatus::ERROR;
+ continue;
+ }
+
+ auto status = streamConfig->format == PixelFormat::BLOB
+ ? renderIntoBlobStreamBuffer(
+ reqBuffer.getStreamId(), reqBuffer.getBufferId(),
+ streamConfig->bufferSize, reqBuffer.getFence())
+ : renderIntoImageStreamBuffer(reqBuffer.getStreamId(),
+ reqBuffer.getBufferId(),
+ reqBuffer.getFence());
+ if (!status.isOk()) {
+ resBuffer.status = BufferStatus::ERROR;
+ }
+ }
+
+ std::vector<NotifyMsg> notifyMsg{
+ createShutterNotifyMsg(request.getFrameNumber(), timestamp)};
+ for (const StreamBuffer& resBuffer : captureResult.outputBuffers) {
+ if (resBuffer.status != BufferStatus::OK) {
+ notifyMsg.push_back(createBufferErrorNotifyMsg(request.getFrameNumber(),
+ resBuffer.streamId));
+ }
+ }
+
+ auto status = mCameraDeviceCallback->notify(notifyMsg);
+ if (!status.isOk()) {
+ ALOGE("%s: notify call failed: %s", __func__,
+ status.getDescription().c_str());
+ return;
+ }
+
+ std::vector<::aidl::android::hardware::camera::device::CaptureResult>
+ captureResults(1);
+ captureResults[0] = std::move(captureResult);
+
+ status = mCameraDeviceCallback->processCaptureResult(captureResults);
+ if (!status.isOk()) {
+ ALOGE("%s: processCaptureResult call failed: %s", __func__,
+ status.getDescription().c_str());
+ return;
+ }
+
+ ALOGD("%s: Successfully called processCaptureResult", __func__);
+}
+
+void VirtualCameraRenderThread::flushCaptureRequest(
+ const ProcessCaptureRequestTask& request) {
+ const std::chrono::nanoseconds timestamp =
+ std::chrono::duration_cast<std::chrono::nanoseconds>(
+ std::chrono::steady_clock::now().time_since_epoch());
+
+ CaptureResult captureResult;
+ captureResult.fmqResultSize = 0;
+ captureResult.frameNumber = request.getFrameNumber();
+ captureResult.inputBuffer.streamId = -1;
+ captureResult.result = createCaptureResultMetadata(timestamp);
+
+ const std::vector<CaptureRequestBuffer>& buffers = request.getBuffers();
+ captureResult.outputBuffers.resize(buffers.size());
+
+ for (int i = 0; i < buffers.size(); ++i) {
+ const CaptureRequestBuffer& reqBuffer = buffers[i];
+ StreamBuffer& resBuffer = captureResult.outputBuffers[i];
+ resBuffer.streamId = reqBuffer.getStreamId();
+ resBuffer.bufferId = reqBuffer.getBufferId();
+ resBuffer.status = BufferStatus::ERROR;
+ sp<Fence> fence = reqBuffer.getFence();
+ if (fence != nullptr && fence->isValid()) {
+ resBuffer.releaseFence.fds.emplace_back(fence->dup());
+ }
+ }
+
+ auto status = mCameraDeviceCallback->notify(
+ {createRequestErrorNotifyMsg(request.getFrameNumber())});
+ if (!status.isOk()) {
+ ALOGE("%s: notify call failed: %s", __func__,
+ status.getDescription().c_str());
+ return;
+ }
+
+ std::vector<::aidl::android::hardware::camera::device::CaptureResult>
+ captureResults(1);
+ captureResults[0] = std::move(captureResult);
+
+ status = mCameraDeviceCallback->processCaptureResult(captureResults);
+ if (!status.isOk()) {
+ ALOGE("%s: processCaptureResult call failed: %s", __func__,
+ status.getDescription().c_str());
+ }
+}
+
+ndk::ScopedAStatus VirtualCameraRenderThread::renderIntoBlobStreamBuffer(
+ const int streamId, const int bufferId, const size_t bufferSize,
+ sp<Fence> fence) {
+ ALOGV("%s", __func__);
+ sp<GraphicBuffer> gBuffer = mEglSurfaceTexture->getCurrentBuffer();
+ std::shared_ptr<AHardwareBuffer> hwBuffer =
+ mSessionContext.fetchHardwareBuffer(streamId, bufferId);
+
+ AHardwareBuffer_Planes planes_info;
+
+ int32_t rawFence = fence != nullptr ? fence->get() : -1;
+ int result = AHardwareBuffer_lockPlanes(hwBuffer.get(),
+ AHARDWAREBUFFER_USAGE_CPU_READ_RARELY,
+ rawFence, nullptr, &planes_info);
+ if (result != OK) {
+ ALOGE("%s: Failed to lock planes for BLOB buffer: %d", __func__, result);
+ return cameraStatus(Status::INTERNAL_ERROR);
+ }
+
+ android_ycbcr ycbcr;
+ status_t status =
+ gBuffer->lockYCbCr(AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN, &ycbcr);
+ ALOGV("Locked buffers");
+ if (status != NO_ERROR) {
+ AHardwareBuffer_unlock(hwBuffer.get(), nullptr);
+ ALOGE("%s: Failed to lock graphic buffer: %d", __func__, status);
+ return cameraStatus(Status::INTERNAL_ERROR);
+ }
+
+ bool success = compressJpeg(gBuffer->getWidth(), gBuffer->getHeight(), ycbcr,
+ bufferSize, planes_info.planes[0].data);
+
+ status_t res = gBuffer->unlock();
+ if (res != NO_ERROR) {
+ ALOGE("Failed to unlock graphic buffer: %d", res);
+ }
+ AHardwareBuffer_unlock(hwBuffer.get(), nullptr);
+ ALOGV("Unlocked buffers");
+ return success ? ndk::ScopedAStatus::ok()
+ : cameraStatus(Status::INTERNAL_ERROR);
+}
+
+ndk::ScopedAStatus VirtualCameraRenderThread::renderIntoImageStreamBuffer(
+ int streamId, int bufferId, sp<Fence> fence) {
+ ALOGV("%s", __func__);
+
+ const std::chrono::nanoseconds before =
+ std::chrono::duration_cast<std::chrono::nanoseconds>(
+ std::chrono::steady_clock::now().time_since_epoch());
+
+ // Render test pattern using EGL.
+ std::shared_ptr<EglFrameBuffer> framebuffer =
+ mSessionContext.fetchOrCreateEglFramebuffer(
+ mEglDisplayContext->getEglDisplay(), streamId, bufferId);
+ if (framebuffer == nullptr) {
+ ALOGE(
+ "%s: Failed to get EGL framebuffer corresponding to buffer id "
+ "%d for streamId %d",
+ __func__, bufferId, streamId);
+ return cameraStatus(Status::ILLEGAL_ARGUMENT);
+ }
+
+ // Wait for fence to clear.
+ if (fence != nullptr && fence->isValid()) {
+ status_t ret = fence->wait(kAcquireFenceTimeout.count());
+ if (ret != 0) {
+ ALOGE(
+ "Timeout while waiting for the acquire fence for buffer %d"
+ " for streamId %d",
+ bufferId, streamId);
+ return cameraStatus(Status::INTERNAL_ERROR);
+ }
+ }
+
+ mEglDisplayContext->makeCurrent();
+ framebuffer->beforeDraw();
+
+ mEglTextureProgram->draw(mEglSurfaceTexture->updateTexture());
+ framebuffer->afterDraw();
+
+ const std::chrono::nanoseconds after =
+ std::chrono::duration_cast<std::chrono::nanoseconds>(
+ std::chrono::steady_clock::now().time_since_epoch());
+
+ ALOGV("Rendering to buffer %d, stream %d took %lld ns", bufferId, streamId,
+ after.count() - before.count());
+
+ return ndk::ScopedAStatus::ok();
+}
+
+} // namespace virtualcamera
+} // namespace companion
+} // namespace android
diff --git a/services/camera/virtualcamera/VirtualCameraRenderThread.h b/services/camera/virtualcamera/VirtualCameraRenderThread.h
new file mode 100644
index 0000000..30de7c2
--- /dev/null
+++ b/services/camera/virtualcamera/VirtualCameraRenderThread.h
@@ -0,0 +1,170 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_COMPANION_VIRTUALCAMERA_VIRTUALCAMERARENDERTHREAD_H
+#define ANDROID_COMPANION_VIRTUALCAMERA_VIRTUALCAMERARENDERTHREAD_H
+
+#include <deque>
+#include <future>
+#include <memory>
+#include <thread>
+
+#include "VirtualCameraSessionContext.h"
+#include "aidl/android/hardware/camera/device/ICameraDeviceCallback.h"
+#include "util/EglDisplayContext.h"
+#include "util/EglProgram.h"
+#include "util/EglSurfaceTexture.h"
+
+namespace android {
+namespace companion {
+namespace virtualcamera {
+
+// Represents single output buffer of capture request.
+class CaptureRequestBuffer {
+ public:
+ CaptureRequestBuffer(int streamId, int bufferId, sp<Fence> fence = nullptr);
+
+ int getStreamId() const;
+ int getBufferId() const;
+ sp<Fence> getFence() const;
+
+ private:
+ const int mStreamId;
+ const int mBufferId;
+ const sp<Fence> mFence;
+};
+
+// Represents single capture request to fill set of buffers.
+class ProcessCaptureRequestTask {
+ public:
+ ProcessCaptureRequestTask(
+ int frameNumber, const std::vector<CaptureRequestBuffer>& requestBuffers);
+
+ // Returns frame number corresponding to the request.
+ int getFrameNumber() const;
+
+ // Get reference to vector describing output buffers corresponding
+ // to this request.
+ //
+ // Note that the vector is owned by the ProcessCaptureRequestTask instance,
+ // so it cannot be access outside of its lifetime.
+ const std::vector<CaptureRequestBuffer>& getBuffers() const;
+
+ private:
+ const int mFrameNumber;
+ const std::vector<CaptureRequestBuffer> mBuffers;
+};
+
+// Wraps dedicated rendering thread and rendering business with corresponding
+// input surface.
+class VirtualCameraRenderThread {
+ public:
+ // Create VirtualCameraRenderThread instance:
+ // * sessionContext - VirtualCameraSessionContext reference for shared access
+ // to mapped buffers.
+ // * inputWidth - requested width of input surface ("virtual camera sensor")
+ // * inputHeight - requested height of input surface ("virtual camera sensor")
+ // * cameraDeviceCallback - callback for corresponding camera instance
+ // * testMode - when set to true, test pattern is rendered to input surface
+ // before each capture request is processed to simulate client input.
+ VirtualCameraRenderThread(
+ VirtualCameraSessionContext& sessionContext, int inputWidth,
+ int inputHeight,
+ std::shared_ptr<
+ ::aidl::android::hardware::camera::device::ICameraDeviceCallback>
+ cameraDeviceCallback,
+ bool testMode = false);
+
+ ~VirtualCameraRenderThread();
+
+ // Start rendering thread.
+ void start();
+ // Stop rendering thread.
+ void stop();
+
+ // Equeue capture task for processing on render thread.
+ void enqueueTask(std::unique_ptr<ProcessCaptureRequestTask> task)
+ EXCLUDES(mLock);
+
+ // Flush all in-flight requests.
+ void flush() EXCLUDES(mLock);
+
+ // Returns input surface corresponding to "virtual camera sensor".
+ sp<Surface> getInputSurface();
+
+ private:
+ std::unique_ptr<ProcessCaptureRequestTask> dequeueTask() EXCLUDES(mLock);
+
+ // Rendering thread entry point.
+ void threadLoop();
+
+ // Process single capture request task (always called on render thread).
+ void processCaptureRequest(const ProcessCaptureRequestTask& captureRequestTask);
+
+ // Flush single capture request task returning the error status immediately.
+ void flushCaptureRequest(const ProcessCaptureRequestTask& captureRequestTask);
+
+ // TODO(b/301023410) - Refactor the actual rendering logic off this class for
+ // easier testability.
+
+ // Render current image to the BLOB buffer.
+ // If fence is specified, this function will block until the fence is cleared
+ // before writing to the buffer.
+ // Always called on render thread.
+ ndk::ScopedAStatus renderIntoBlobStreamBuffer(const int streamId,
+ const int bufferId,
+ const size_t bufferSize,
+ sp<Fence> fence = nullptr);
+
+ // Render current image to the YCbCr buffer.
+ // If fence is specified, this function will block until the fence is cleared
+ // before writing to the buffer.
+ // Always called on render thread.
+ ndk::ScopedAStatus renderIntoImageStreamBuffer(int streamId, int bufferId,
+ sp<Fence> fence = nullptr);
+
+ // Camera callback
+ const std::shared_ptr<
+ ::aidl::android::hardware::camera::device::ICameraDeviceCallback>
+ mCameraDeviceCallback;
+
+ const int mInputSurfaceWidth;
+ const int mInputSurfaceHeight;
+ const int mTestMode;
+
+ VirtualCameraSessionContext& mSessionContext;
+
+ std::thread mThread;
+
+ // Blocking queue implementation.
+ std::mutex mLock;
+ std::deque<std::unique_ptr<ProcessCaptureRequestTask>> mQueue GUARDED_BY(mLock);
+ std::condition_variable mCondVar;
+ volatile bool mPendingExit GUARDED_BY(mLock);
+
+ // EGL helpers - constructed and accessed only from rendering thread.
+ std::unique_ptr<EglDisplayContext> mEglDisplayContext;
+ std::unique_ptr<EglTextureProgram> mEglTextureProgram;
+ std::unique_ptr<EglSurfaceTexture> mEglSurfaceTexture;
+
+ std::promise<sp<Surface>> mInputSurfacePromise;
+};
+
+} // namespace virtualcamera
+} // namespace companion
+} // namespace android
+
+#endif // ANDROID_COMPANION_VIRTUALCAMERA_VIRTUALCAMERARENDERTHREAD_H
diff --git a/services/camera/virtualcamera/VirtualCameraService.cc b/services/camera/virtualcamera/VirtualCameraService.cc
new file mode 100644
index 0000000..62dc08b
--- /dev/null
+++ b/services/camera/virtualcamera/VirtualCameraService.cc
@@ -0,0 +1,181 @@
+/*
+ * 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.
+ */
+
+// #define LOG_NDEBUG 0
+#include "android/binder_status.h"
+#define LOG_TAG "VirtualCameraService"
+#include "VirtualCameraService.h"
+
+#include <cinttypes>
+#include <cstdint>
+#include <cstdio>
+#include <memory>
+#include <mutex>
+
+#include "VirtualCameraDevice.h"
+#include "VirtualCameraProvider.h"
+#include "android/binder_auto_utils.h"
+#include "android/binder_libbinder.h"
+#include "binder/Status.h"
+
+using ::android::binder::Status;
+
+namespace android {
+namespace companion {
+namespace virtualcamera {
+
+using ::aidl::android::companion::virtualcamera::VirtualCameraConfiguration;
+
+namespace {
+
+constexpr char kEnableTestCameraCmd[] = "enable_test_camera";
+constexpr char kDisableTestCameraCmd[] = "disable_test_camera";
+constexpr char kShellCmdHelp[] = R"(
+Available commands:
+ * enable_test_camera
+ * disable_test_camera
+)";
+
+} // namespace
+
+VirtualCameraService::VirtualCameraService(
+ std::shared_ptr<VirtualCameraProvider> virtualCameraProvider)
+ : mVirtualCameraProvider(virtualCameraProvider) {
+}
+
+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;
+
+ std::lock_guard lock(mLock);
+ if (mTokenToCameraName.find(token) != mTokenToCameraName.end()) {
+ ALOGE(
+ "Attempt to register camera corresponding to already registered binder "
+ "token: "
+ "0x%" PRIxPTR,
+ reinterpret_cast<uintptr_t>(token.get()));
+ *_aidl_return = false;
+ }
+
+ // TODO(b/301023410) Validate configuration and pass it to the camera.
+ std::shared_ptr<VirtualCameraDevice> camera =
+ mVirtualCameraProvider->createCamera(configuration.virtualCameraCallback);
+ if (camera == nullptr) {
+ ALOGE("Failed to create camera for binder token 0x%" PRIxPTR,
+ reinterpret_cast<uintptr_t>(token.get()));
+ *_aidl_return = false;
+ return ndk::ScopedAStatus::fromServiceSpecificError(
+ Status::EX_SERVICE_SPECIFIC);
+ }
+
+ mTokenToCameraName[token] = camera->getCameraName();
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus VirtualCameraService::unregisterCamera(
+ const ::ndk::SpAIBinder& token) {
+ std::lock_guard lock(mLock);
+
+ auto it = mTokenToCameraName.find(token);
+ if (it == mTokenToCameraName.end()) {
+ ALOGE(
+ "Attempt to unregister camera corresponding to unknown binder token: "
+ "0x%" PRIxPTR,
+ reinterpret_cast<uintptr_t>(token.get()));
+ return ndk::ScopedAStatus::ok();
+ }
+
+ mVirtualCameraProvider->removeCamera(it->second);
+
+ return ndk::ScopedAStatus::ok();
+}
+
+std::shared_ptr<VirtualCameraDevice> VirtualCameraService::getCamera(
+ const ::ndk::SpAIBinder& token) {
+ if (token == nullptr) {
+ return nullptr;
+ }
+
+ std::lock_guard lock(mLock);
+ auto it = mTokenToCameraName.find(token);
+ if (it == mTokenToCameraName.end()) {
+ return nullptr;
+ }
+
+ return mVirtualCameraProvider->getCamera(it->second);
+}
+
+binder_status_t VirtualCameraService::handleShellCommand(int in, int out,
+ int err,
+ const char** args,
+ uint32_t numArgs) {
+ if (numArgs <= 0) {
+ dprintf(out, kShellCmdHelp);
+ }
+
+ if (args == nullptr || args[0] == nullptr) {
+ return STATUS_BAD_VALUE;
+ }
+ const char* const cmd = args[0];
+ if (strcmp(kEnableTestCameraCmd, cmd) == 0) {
+ enableTestCameraCmd(in, err);
+ } else if (strcmp(kDisableTestCameraCmd, cmd) == 0) {
+ disableTestCameraCmd(in);
+ } else {
+ dprintf(out, kShellCmdHelp);
+ }
+
+ fsync(out);
+ return STATUS_OK;
+}
+
+void VirtualCameraService::enableTestCameraCmd(const int out, const int err) {
+ if (mTestCameraToken != nullptr) {
+ dprintf(out, "Test camera is already enabled (%s).",
+ getCamera(mTestCameraToken)->getCameraName().c_str());
+ return;
+ }
+
+ sp<BBinder> token = sp<BBinder>::make();
+ mTestCameraToken.set(AIBinder_fromPlatformBinder(token));
+
+ bool ret;
+ registerCamera(mTestCameraToken, VirtualCameraConfiguration(), &ret);
+ if (ret) {
+ dprintf(out, "Successfully registered test camera %s",
+ getCamera(mTestCameraToken)->getCameraName().c_str());
+ } else {
+ dprintf(err, "Failed to create test camera");
+ }
+}
+
+void VirtualCameraService::disableTestCameraCmd(const int out) {
+ if (mTestCameraToken == nullptr) {
+ dprintf(out, "Test camera is not registered.");
+ }
+ unregisterCamera(mTestCameraToken);
+ mTestCameraToken.set(nullptr);
+}
+
+} // namespace virtualcamera
+} // namespace companion
+} // namespace android
diff --git a/services/camera/virtualcamera/VirtualCameraService.h b/services/camera/virtualcamera/VirtualCameraService.h
new file mode 100644
index 0000000..cf68c1b
--- /dev/null
+++ b/services/camera/virtualcamera/VirtualCameraService.h
@@ -0,0 +1,85 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_COMPANION_VIRTUALCAMERA_VIRTUALCAMERASERVICE_H
+#define ANDROID_COMPANION_VIRTUALCAMERA_VIRTUALCAMERASERVICE_H
+
+#include <memory>
+#include <mutex>
+#include <unordered_map>
+
+#include "VirtualCameraDevice.h"
+#include "VirtualCameraProvider.h"
+#include "aidl/android/companion/virtualcamera/BnVirtualCameraService.h"
+
+namespace android {
+namespace companion {
+namespace virtualcamera {
+
+// Implementation of Virtual Camera Service for managing virtual camera devices.
+class VirtualCameraService
+ : public aidl::android::companion::virtualcamera::BnVirtualCameraService {
+ public:
+ VirtualCameraService(
+ std::shared_ptr<VirtualCameraProvider> virtualCameraProvider);
+
+ // Register camera corresponding to the binder token.
+ ndk::ScopedAStatus registerCamera(
+ const ::ndk::SpAIBinder& token,
+ const ::aidl::android::companion::virtualcamera::VirtualCameraConfiguration&
+ configuration,
+ bool* _aidl_return) override EXCLUDES(mLock);
+
+ // Unregisters camera corresponding to the binder token.
+ ndk::ScopedAStatus unregisterCamera(const ::ndk::SpAIBinder& token) override
+ EXCLUDES(mLock);
+
+ // Returns VirtualCameraDevice corresponding to binder token or nullptr if
+ // there's no camera asociated with the token.
+ std::shared_ptr<VirtualCameraDevice> getCamera(const ::ndk::SpAIBinder& token)
+ EXCLUDES(mLock);
+
+ // Handle cmd shell commands `adb shell cmd virtual_camera_service` [args].
+ binder_status_t handleShellCommand(int in, int out, int err, const char** args,
+ uint32_t numArgs) override;
+
+ private:
+ // Create and enable test camera instance if there's none.
+ void enableTestCameraCmd(int out, int err);
+ // Disable and destroy test camera instance if there's one.
+ void disableTestCameraCmd(int out);
+
+ std::shared_ptr<VirtualCameraProvider> mVirtualCameraProvider;
+
+ std::mutex mLock;
+ struct BinderTokenHash {
+ std::size_t operator()(const ::ndk::SpAIBinder& key) const {
+ return std::hash<void*>{}(reinterpret_cast<void*>(key.get()));
+ }
+ };
+ // Map Binder tokens to names of cameras managed by camera provider.
+ std::unordered_map<::ndk::SpAIBinder, std::string, BinderTokenHash>
+ mTokenToCameraName GUARDED_BY(mLock);
+
+ // Local binder token for test camera instance, or nullptr if there's none.
+ ::ndk::SpAIBinder mTestCameraToken;
+};
+
+} // namespace virtualcamera
+} // namespace companion
+} // namespace android
+
+#endif // ANDROID_COMPANION_VIRTUALCAMERA_VIRTUALCAMERASERVICE_H
diff --git a/services/camera/virtualcamera/VirtualCameraSession.cc b/services/camera/virtualcamera/VirtualCameraSession.cc
new file mode 100644
index 0000000..55678b7
--- /dev/null
+++ b/services/camera/virtualcamera/VirtualCameraSession.cc
@@ -0,0 +1,436 @@
+/*
+ * 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.
+ */
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "VirtualCameraSession"
+#include "VirtualCameraSession.h"
+
+#include <atomic>
+#include <chrono>
+#include <cstddef>
+#include <cstdint>
+#include <cstring>
+#include <map>
+#include <memory>
+#include <mutex>
+#include <optional>
+#include <tuple>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include "CameraMetadata.h"
+#include "EGL/egl.h"
+#include "VirtualCameraRenderThread.h"
+#include "VirtualCameraStream.h"
+#include "aidl/android/hardware/camera/common/Status.h"
+#include "aidl/android/hardware/camera/device/BufferCache.h"
+#include "aidl/android/hardware/camera/device/BufferStatus.h"
+#include "aidl/android/hardware/camera/device/CaptureRequest.h"
+#include "aidl/android/hardware/camera/device/HalStream.h"
+#include "aidl/android/hardware/camera/device/NotifyMsg.h"
+#include "aidl/android/hardware/camera/device/ShutterMsg.h"
+#include "aidl/android/hardware/camera/device/StreamBuffer.h"
+#include "aidl/android/hardware/camera/device/StreamConfiguration.h"
+#include "aidl/android/hardware/camera/device/StreamRotation.h"
+#include "aidl/android/hardware/graphics/common/BufferUsage.h"
+#include "aidl/android/hardware/graphics/common/PixelFormat.h"
+#include "android/hardware_buffer.h"
+#include "android/native_window_aidl.h"
+#include "fmq/AidlMessageQueue.h"
+#include "system/camera_metadata.h"
+#include "ui/GraphicBuffer.h"
+#include "util/EglDisplayContext.h"
+#include "util/EglFramebuffer.h"
+#include "util/EglProgram.h"
+#include "util/JpegUtil.h"
+#include "util/MetadataBuilder.h"
+#include "util/TestPatternHelper.h"
+#include "util/Util.h"
+
+namespace android {
+namespace companion {
+namespace virtualcamera {
+
+using ::aidl::android::companion::virtualcamera::Format;
+using ::aidl::android::companion::virtualcamera::IVirtualCameraCallback;
+using ::aidl::android::hardware::camera::common::Status;
+using ::aidl::android::hardware::camera::device::BufferCache;
+using ::aidl::android::hardware::camera::device::CameraMetadata;
+using ::aidl::android::hardware::camera::device::CameraOfflineSessionInfo;
+using ::aidl::android::hardware::camera::device::CaptureRequest;
+using ::aidl::android::hardware::camera::device::HalStream;
+using ::aidl::android::hardware::camera::device::ICameraDeviceCallback;
+using ::aidl::android::hardware::camera::device::ICameraOfflineSession;
+using ::aidl::android::hardware::camera::device::RequestTemplate;
+using ::aidl::android::hardware::camera::device::Stream;
+using ::aidl::android::hardware::camera::device::StreamBuffer;
+using ::aidl::android::hardware::camera::device::StreamConfiguration;
+using ::aidl::android::hardware::camera::device::StreamRotation;
+using ::aidl::android::hardware::common::fmq::MQDescriptor;
+using ::aidl::android::hardware::common::fmq::SynchronizedReadWrite;
+using ::aidl::android::hardware::graphics::common::BufferUsage;
+using ::aidl::android::hardware::graphics::common::PixelFormat;
+using ::android::base::unique_fd;
+
+namespace {
+
+using metadata_ptr =
+ std::unique_ptr<camera_metadata_t, void (*)(camera_metadata_t*)>;
+
+using namespace std::chrono_literals;
+
+// Size of request/result metadata fast message queue.
+// Setting to 0 to always disables FMQ.
+static constexpr size_t kMetadataMsgQueueSize = 0;
+
+// Maximum number of buffers to use per single stream.
+static constexpr size_t kMaxStreamBuffers = 2;
+
+CameraMetadata createDefaultRequestSettings(RequestTemplate type) {
+ hardware::camera::common::V1_0::helper::CameraMetadata metadataHelper;
+
+ camera_metadata_enum_android_control_capture_intent_t intent =
+ ANDROID_CONTROL_CAPTURE_INTENT_PREVIEW;
+ switch (type) {
+ case RequestTemplate::PREVIEW:
+ intent = ANDROID_CONTROL_CAPTURE_INTENT_PREVIEW;
+ break;
+ case RequestTemplate::STILL_CAPTURE:
+ intent = ANDROID_CONTROL_CAPTURE_INTENT_STILL_CAPTURE;
+ break;
+ case RequestTemplate::VIDEO_RECORD:
+ intent = ANDROID_CONTROL_CAPTURE_INTENT_VIDEO_RECORD;
+ break;
+ case RequestTemplate::VIDEO_SNAPSHOT:
+ intent = ANDROID_CONTROL_CAPTURE_INTENT_VIDEO_SNAPSHOT;
+ break;
+ default:
+ // Leave default.
+ break;
+ }
+
+ auto metadata = MetadataBuilder().setControlCaptureIntent(intent).build();
+ return (metadata != nullptr) ? std::move(*metadata) : CameraMetadata();
+}
+
+HalStream getHalStream(const Stream& stream) {
+ HalStream halStream;
+ halStream.id = stream.id;
+ halStream.physicalCameraId = stream.physicalCameraId;
+ halStream.maxBuffers = kMaxStreamBuffers;
+
+ if (stream.format == PixelFormat::IMPLEMENTATION_DEFINED) {
+ // If format is implementation defined we need it to override
+ // it with actual format.
+ // TODO(b/301023410) Override with the format based on the
+ // camera configuration, once we support more formats.
+ halStream.overrideFormat = PixelFormat::YCBCR_420_888;
+ } else {
+ halStream.overrideFormat = stream.format;
+ }
+ halStream.overrideDataSpace = stream.dataSpace;
+
+ halStream.producerUsage = BufferUsage::GPU_RENDER_TARGET;
+ halStream.supportOffline = false;
+ return halStream;
+}
+
+} // namespace
+
+VirtualCameraSession::VirtualCameraSession(
+ const std::string& cameraId,
+ std::shared_ptr<ICameraDeviceCallback> cameraDeviceCallback,
+ std::shared_ptr<IVirtualCameraCallback> virtualCameraClientCallback)
+ : mCameraId(cameraId),
+ mCameraDeviceCallback(cameraDeviceCallback),
+ mVirtualCameraClientCallback(virtualCameraClientCallback) {
+ mRequestMetadataQueue = std::make_unique<RequestMetadataQueue>(
+ kMetadataMsgQueueSize, false /* non blocking */);
+ if (!mRequestMetadataQueue->isValid()) {
+ ALOGE("%s: invalid request fmq", __func__);
+ }
+
+ mResultMetadataQueue = std::make_shared<ResultMetadataQueue>(
+ kMetadataMsgQueueSize, false /* non blocking */);
+ if (!mResultMetadataQueue->isValid()) {
+ ALOGE("%s: invalid result fmq", __func__);
+ }
+}
+
+ndk::ScopedAStatus VirtualCameraSession::close() {
+ ALOGV("%s", __func__);
+
+ if (mVirtualCameraClientCallback != nullptr) {
+ mVirtualCameraClientCallback->onStreamClosed(/*streamId=*/0);
+ }
+
+ mSessionContext.closeAllStreams();
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus VirtualCameraSession::configureStreams(
+ const StreamConfiguration& in_requestedConfiguration,
+ std::vector<HalStream>* _aidl_return) {
+ ALOGV("%s: requestedConfiguration: %s", __func__,
+ in_requestedConfiguration.toString().c_str());
+
+ if (_aidl_return == nullptr) {
+ return cameraStatus(Status::ILLEGAL_ARGUMENT);
+ }
+
+ mSessionContext.removeStreamsNotInStreamConfiguration(
+ in_requestedConfiguration);
+
+ auto& streams = in_requestedConfiguration.streams;
+ auto& halStreams = *_aidl_return;
+ halStreams.clear();
+ halStreams.resize(in_requestedConfiguration.streams.size());
+
+ sp<Surface> inputSurface = nullptr;
+ int inputWidth;
+ int inputHeight;
+
+ {
+ 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());
+ }
+ }
+
+ inputWidth = streams[0].width;
+ inputHeight = streams[0].height;
+ if (mRenderThread == nullptr) {
+ // If there's no client callback, start camera in test mode.
+ const bool testMode = mVirtualCameraClientCallback == nullptr;
+ mRenderThread = std::make_unique<VirtualCameraRenderThread>(
+ mSessionContext, inputWidth, inputHeight, mCameraDeviceCallback,
+ testMode);
+ mRenderThread->start();
+ inputSurface = mRenderThread->getInputSurface();
+ }
+ }
+
+ if (mVirtualCameraClientCallback != nullptr && inputSurface != nullptr) {
+ // TODO(b/301023410) Pass streamId based on client input stream id once
+ // support for multiple input streams is implemented. For now we always
+ // create single texture.
+ mVirtualCameraClientCallback->onStreamConfigured(
+ /*streamId=*/0, aidl::android::view::Surface(inputSurface.get()),
+ inputWidth, inputHeight, Format::YUV_420_888);
+ }
+
+ mFirstRequest.store(true);
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus VirtualCameraSession::constructDefaultRequestSettings(
+ RequestTemplate in_type, CameraMetadata* _aidl_return) {
+ ALOGV("%s: type %d", __func__, static_cast<int32_t>(in_type));
+
+ switch (in_type) {
+ case RequestTemplate::PREVIEW:
+ case RequestTemplate::STILL_CAPTURE:
+ case RequestTemplate::VIDEO_RECORD: {
+ *_aidl_return = createDefaultRequestSettings(in_type);
+ return ndk::ScopedAStatus::ok();
+ }
+ case RequestTemplate::VIDEO_SNAPSHOT:
+ case RequestTemplate::MANUAL:
+ case RequestTemplate::ZERO_SHUTTER_LAG:
+ // Don't support VIDEO_SNAPSHOT, MANUAL, ZSL templates
+ return ndk::ScopedAStatus::fromServiceSpecificError(
+ static_cast<int32_t>(Status::ILLEGAL_ARGUMENT));
+ ;
+ default:
+ ALOGE("%s: unknown request template type %d", __FUNCTION__,
+ static_cast<int>(in_type));
+ return ndk::ScopedAStatus::fromServiceSpecificError(
+ static_cast<int32_t>(Status::ILLEGAL_ARGUMENT));
+ ;
+ }
+}
+
+ndk::ScopedAStatus VirtualCameraSession::flush() {
+ ALOGV("%s", __func__);
+ std::lock_guard<std::mutex> lock(mLock);
+ mRenderThread->flush();
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus VirtualCameraSession::getCaptureRequestMetadataQueue(
+ MQDescriptor<int8_t, SynchronizedReadWrite>* _aidl_return) {
+ ALOGV("%s", __func__);
+ *_aidl_return = mRequestMetadataQueue->dupeDesc();
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus VirtualCameraSession::getCaptureResultMetadataQueue(
+ MQDescriptor<int8_t, SynchronizedReadWrite>* _aidl_return) {
+ ALOGV("%s", __func__);
+ *_aidl_return = mResultMetadataQueue->dupeDesc();
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus VirtualCameraSession::isReconfigurationRequired(
+ const CameraMetadata& in_oldSessionParams,
+ const CameraMetadata& in_newSessionParams, bool* _aidl_return) {
+ ALOGV("%s: oldSessionParams: %s newSessionParams: %s", __func__,
+ in_newSessionParams.toString().c_str(),
+ in_oldSessionParams.toString().c_str());
+
+ if (_aidl_return == nullptr) {
+ return ndk::ScopedAStatus::fromServiceSpecificError(
+ static_cast<int32_t>(Status::ILLEGAL_ARGUMENT));
+ }
+
+ *_aidl_return = true;
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus VirtualCameraSession::processCaptureRequest(
+ const std::vector<CaptureRequest>& in_requests,
+ const std::vector<BufferCache>& in_cachesToRemove, int32_t* _aidl_return) {
+ ALOGV("%s", __func__);
+
+ if (!in_cachesToRemove.empty()) {
+ mSessionContext.removeBufferCaches(in_cachesToRemove);
+ }
+
+ for (const auto& captureRequest : in_requests) {
+ auto status = processCaptureRequest(captureRequest);
+ if (!status.isOk()) {
+ return status;
+ }
+ }
+ *_aidl_return = in_requests.size();
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus VirtualCameraSession::signalStreamFlush(
+ const std::vector<int32_t>& in_streamIds, int32_t in_streamConfigCounter) {
+ ALOGV("%s", __func__);
+
+ (void)in_streamIds;
+ (void)in_streamConfigCounter;
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus VirtualCameraSession::switchToOffline(
+ const std::vector<int32_t>& in_streamsToKeep,
+ CameraOfflineSessionInfo* out_offlineSessionInfo,
+ std::shared_ptr<ICameraOfflineSession>* _aidl_return) {
+ ALOGV("%s", __func__);
+
+ (void)in_streamsToKeep;
+ (void)out_offlineSessionInfo;
+
+ if (_aidl_return == nullptr) {
+ return ndk::ScopedAStatus::fromServiceSpecificError(
+ static_cast<int32_t>(Status::ILLEGAL_ARGUMENT));
+ }
+
+ *_aidl_return = nullptr;
+ return cameraStatus(Status::OPERATION_NOT_SUPPORTED);
+}
+
+ndk::ScopedAStatus VirtualCameraSession::repeatingRequestEnd(
+ int32_t in_frameNumber, const std::vector<int32_t>& in_streamIds) {
+ ALOGV("%s", __func__);
+ (void)in_frameNumber;
+ (void)in_streamIds;
+ return ndk::ScopedAStatus::ok();
+}
+
+std::set<int> VirtualCameraSession::getStreamIds() const {
+ return mSessionContext.getStreamIds();
+}
+
+ndk::ScopedAStatus VirtualCameraSession::processCaptureRequest(
+ const CaptureRequest& request) {
+ ALOGD("%s: request: %s", __func__, request.toString().c_str());
+
+ if (mFirstRequest.exchange(false) && request.settings.metadata.empty()) {
+ return cameraStatus(Status::ILLEGAL_ARGUMENT);
+ }
+
+ std::shared_ptr<ICameraDeviceCallback> cameraCallback = nullptr;
+ {
+ std::lock_guard<std::mutex> lock(mLock);
+ cameraCallback = mCameraDeviceCallback;
+ }
+
+ if (cameraCallback == nullptr) {
+ ALOGE(
+ "%s: processCaptureRequest called, but there's no camera callback "
+ "configured",
+ __func__);
+ return cameraStatus(Status::INTERNAL_ERROR);
+ }
+
+ if (!mSessionContext.importBuffersFromCaptureRequest(request)) {
+ ALOGE("Failed to import buffers from capture request.");
+ return cameraStatus(Status::INTERNAL_ERROR);
+ }
+
+ std::vector<CaptureRequestBuffer> taskBuffers;
+ taskBuffers.reserve(request.outputBuffers.size());
+ for (const StreamBuffer& streamBuffer : request.outputBuffers) {
+ taskBuffers.emplace_back(streamBuffer.streamId, streamBuffer.bufferId,
+ importFence(streamBuffer.acquireFence));
+ }
+
+ {
+ std::lock_guard<std::mutex> lock(mLock);
+ if (mRenderThread == nullptr) {
+ ALOGE(
+ "%s: processCaptureRequest (frameNumber %d)called before configure "
+ "(render thread not initialized)",
+ __func__, request.frameNumber);
+ return cameraStatus(Status::INTERNAL_ERROR);
+ }
+ mRenderThread->enqueueTask(std::make_unique<ProcessCaptureRequestTask>(
+ request.frameNumber, taskBuffers));
+ }
+
+ if (mVirtualCameraClientCallback != nullptr) {
+ auto status = mVirtualCameraClientCallback->onProcessCaptureRequest(
+ /*streamId=*/0, request.frameNumber);
+ if (!status.isOk()) {
+ ALOGE(
+ "Failed to invoke onProcessCaptureRequest client callback for frame "
+ "%d",
+ request.frameNumber);
+ }
+ }
+
+ return ndk::ScopedAStatus::ok();
+}
+
+} // namespace virtualcamera
+} // namespace companion
+} // namespace android
diff --git a/services/camera/virtualcamera/VirtualCameraSession.h b/services/camera/virtualcamera/VirtualCameraSession.h
new file mode 100644
index 0000000..440720e
--- /dev/null
+++ b/services/camera/virtualcamera/VirtualCameraSession.h
@@ -0,0 +1,147 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_COMPANION_VIRTUALCAMERA_VIRTUALCAMERASESSION_H
+#define ANDROID_COMPANION_VIRTUALCAMERA_VIRTUALCAMERASESSION_H
+
+#include <memory>
+#include <set>
+
+#include "VirtualCameraRenderThread.h"
+#include "VirtualCameraSessionContext.h"
+#include "aidl/android/companion/virtualcamera/IVirtualCameraCallback.h"
+#include "aidl/android/hardware/camera/device/BnCameraDeviceSession.h"
+#include "aidl/android/hardware/camera/device/ICameraDeviceCallback.h"
+#include "utils/Mutex.h"
+
+namespace android {
+
+template <typename T, typename U>
+struct AidlMessageQueue;
+namespace companion {
+namespace virtualcamera {
+
+// 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.
+class VirtualCameraSession
+ : public ::aidl::android::hardware::camera::device::BnCameraDeviceSession {
+ public:
+ // Construct new virtual camera session.
+ // When virtualCameraClientCallback is null, the input surface will be filled
+ // with test pattern.
+ VirtualCameraSession(
+ const std::string& cameraId,
+ std::shared_ptr<
+ ::aidl::android::hardware::camera::device::ICameraDeviceCallback>
+ cameraDeviceCallback,
+ std::shared_ptr<
+ ::aidl::android::companion::virtualcamera::IVirtualCameraCallback>
+ virtualCameraClientCallback = nullptr);
+
+ virtual ~VirtualCameraSession() override = default;
+
+ ndk::ScopedAStatus close() override;
+
+ ndk::ScopedAStatus configureStreams(
+ const ::aidl::android::hardware::camera::device::StreamConfiguration&
+ in_requestedConfiguration,
+ std::vector<::aidl::android::hardware::camera::device::HalStream>*
+ _aidl_return) override EXCLUDES(mLock);
+
+ ndk::ScopedAStatus constructDefaultRequestSettings(
+ ::aidl::android::hardware::camera::device::RequestTemplate in_type,
+ ::aidl::android::hardware::camera::device::CameraMetadata* _aidl_return)
+ override;
+
+ ndk::ScopedAStatus flush() override EXCLUDES(mLock);
+
+ ndk::ScopedAStatus getCaptureRequestMetadataQueue(
+ ::aidl::android::hardware::common::fmq::MQDescriptor<
+ int8_t, ::aidl::android::hardware::common::fmq::SynchronizedReadWrite>*
+ _aidl_return) override;
+
+ ndk::ScopedAStatus getCaptureResultMetadataQueue(
+ ::aidl::android::hardware::common::fmq::MQDescriptor<
+ int8_t, ::aidl::android::hardware::common::fmq::SynchronizedReadWrite>*
+ _aidl_return) override;
+
+ ndk::ScopedAStatus isReconfigurationRequired(
+ const ::aidl::android::hardware::camera::device::CameraMetadata&
+ in_oldSessionParams,
+ const ::aidl::android::hardware::camera::device::CameraMetadata&
+ in_newSessionParams,
+ bool* _aidl_return) override;
+
+ ndk::ScopedAStatus processCaptureRequest(
+ const std::vector<::aidl::android::hardware::camera::device::CaptureRequest>&
+ in_requests,
+ const std::vector<::aidl::android::hardware::camera::device::BufferCache>&
+ in_cachesToRemove,
+ int32_t* _aidl_return) override;
+
+ ndk::ScopedAStatus signalStreamFlush(const std::vector<int32_t>& in_streamIds,
+ int32_t in_streamConfigCounter) override;
+
+ ndk::ScopedAStatus switchToOffline(
+ const std::vector<int32_t>& in_streamsToKeep,
+ ::aidl::android::hardware::camera::device::CameraOfflineSessionInfo*
+ out_offlineSessionInfo,
+ std::shared_ptr<
+ ::aidl::android::hardware::camera::device::ICameraOfflineSession>*
+ _aidl_return) override;
+
+ ndk::ScopedAStatus repeatingRequestEnd(
+ int32_t in_frameNumber, const std::vector<int32_t>& in_streamIds) override;
+
+ std::set<int> getStreamIds() const EXCLUDES(mLock);
+
+ private:
+ ndk::ScopedAStatus processCaptureRequest(
+ const ::aidl::android::hardware::camera::device::CaptureRequest& request)
+ EXCLUDES(mLock);
+
+ const std::string mCameraId;
+
+ mutable std::mutex mLock;
+
+ std::shared_ptr<::aidl::android::hardware::camera::device::ICameraDeviceCallback>
+ mCameraDeviceCallback GUARDED_BY(mLock);
+
+ const std::shared_ptr<
+ ::aidl::android::companion::virtualcamera::IVirtualCameraCallback>
+ mVirtualCameraClientCallback;
+
+ VirtualCameraSessionContext mSessionContext;
+
+ using RequestMetadataQueue = AidlMessageQueue<
+ int8_t, ::aidl::android::hardware::common::fmq::SynchronizedReadWrite>;
+ std::unique_ptr<RequestMetadataQueue> mRequestMetadataQueue;
+
+ using ResultMetadataQueue = AidlMessageQueue<
+ int8_t, ::aidl::android::hardware::common::fmq::SynchronizedReadWrite>;
+ std::shared_ptr<ResultMetadataQueue> mResultMetadataQueue;
+
+ std::atomic_bool mFirstRequest{true};
+
+ std::unique_ptr<VirtualCameraRenderThread> mRenderThread GUARDED_BY(mLock);
+};
+
+} // namespace virtualcamera
+} // namespace companion
+} // namespace android
+
+#endif // ANDROID_SERVICES_VIRTUAL_CAMERA_VIRTUALCAMERASESSION_H
diff --git a/services/camera/virtualcamera/VirtualCameraSessionContext.cc b/services/camera/virtualcamera/VirtualCameraSessionContext.cc
new file mode 100644
index 0000000..284ad05
--- /dev/null
+++ b/services/camera/virtualcamera/VirtualCameraSessionContext.cc
@@ -0,0 +1,171 @@
+/*
+ * 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 "VirtualCameraSessionContext.h"
+
+#include <memory>
+#include <mutex>
+#include <unordered_set>
+
+#include "VirtualCameraStream.h"
+#include "aidl/android/hardware/camera/device/StreamConfiguration.h"
+
+namespace android {
+namespace companion {
+namespace virtualcamera {
+
+using ::aidl::android::hardware::camera::device::BufferCache;
+using ::aidl::android::hardware::camera::device::Stream;
+using ::aidl::android::hardware::camera::device::StreamBuffer;
+using ::aidl::android::hardware::camera::device::StreamConfiguration;
+
+bool VirtualCameraSessionContext::initializeStream(
+ const ::aidl::android::hardware::camera::device::Stream& stream) {
+ std::lock_guard<std::mutex> lock(mLock);
+
+ auto s = std::make_unique<VirtualCameraStream>(stream);
+
+ const auto& [_, newlyInserted] = mStreams.emplace(
+ std::piecewise_construct, std::forward_as_tuple(stream.id),
+ std::forward_as_tuple(std::move(s)));
+ return newlyInserted;
+}
+
+void VirtualCameraSessionContext::closeAllStreams() {
+ std::lock_guard<std::mutex> lock(mLock);
+ mStreams.clear();
+}
+
+bool VirtualCameraSessionContext::importBuffersFromCaptureRequest(
+ const ::aidl::android::hardware::camera::device::CaptureRequest&
+ captureRequest) {
+ std::lock_guard<std::mutex> lock(mLock);
+
+ for (const StreamBuffer& buffer : captureRequest.outputBuffers) {
+ auto it = mStreams.find(buffer.streamId);
+ if (it == mStreams.end()) {
+ ALOGE("%s: Cannot import buffer for unknown stream with id %d", __func__,
+ buffer.streamId);
+ return false;
+ }
+ VirtualCameraStream& stream = *it->second;
+ if (stream.getHardwareBuffer(buffer.bufferId) != nullptr) {
+ // This buffer is already imported.
+ continue;
+ }
+
+ if (stream.importBuffer(buffer) == nullptr) {
+ ALOGE("%s: Failed to import buffer %" PRId64 " for streamId %d", __func__,
+ buffer.bufferId, buffer.streamId);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void VirtualCameraSessionContext::removeBufferCaches(
+ const std::vector<BufferCache>& cachesToRemove) {
+ std::lock_guard<std::mutex> lock(mLock);
+ for (const auto& bufferCache : cachesToRemove) {
+ auto it = mStreams.find(bufferCache.streamId);
+ if (it == mStreams.end()) {
+ ALOGE("%s: Ask to remove buffer %" PRId64 " from unknown stream %d",
+ __func__, bufferCache.bufferId, bufferCache.streamId);
+ continue;
+ }
+ if (it->second->removeBuffer(bufferCache.bufferId)) {
+ ALOGD("%s: Successfully removed buffer %" PRId64
+ " from cache of stream %d",
+ __func__, bufferCache.bufferId, bufferCache.streamId);
+ } else {
+ ALOGE("%s: Failed to remove buffer %" PRId64 " from cache of stream %d",
+ __func__, bufferCache.bufferId, bufferCache.streamId);
+ }
+ }
+}
+
+void VirtualCameraSessionContext::removeStreamsNotInStreamConfiguration(
+ const StreamConfiguration& streamConfiguration) {
+ std::unordered_set<int> newConfigurationStreamIds;
+ for (const Stream& stream : streamConfiguration.streams) {
+ newConfigurationStreamIds.insert(stream.id);
+ }
+
+ std::lock_guard<std::mutex> lock(mLock);
+ for (auto it = mStreams.begin(); it != mStreams.end();) {
+ if (newConfigurationStreamIds.find(it->first) ==
+ newConfigurationStreamIds.end()) {
+ ALOGV(
+ "Disposing of stream %d, since it is not referenced by new "
+ "configuration.",
+ it->first);
+ it = mStreams.erase(it);
+ } else {
+ ++it;
+ }
+ }
+}
+
+std::optional<Stream> VirtualCameraSessionContext::getStreamConfig(
+ int streamId) const {
+ std::lock_guard<std::mutex> lock(mLock);
+ auto it = mStreams.find(streamId);
+ if (it == mStreams.end()) {
+ ALOGE("%s: StreamBuffer references buffer of unknown streamId %d", __func__,
+ streamId);
+ return std::optional<Stream>();
+ }
+ return {it->second->getStreamConfig()};
+}
+
+std::shared_ptr<AHardwareBuffer> VirtualCameraSessionContext::fetchHardwareBuffer(
+ const int streamId, const int bufferId) const {
+ std::lock_guard<std::mutex> lock(mLock);
+ auto it = mStreams.find(streamId);
+ if (it == mStreams.end()) {
+ ALOGE("%s: StreamBuffer references buffer of unknown streamId %d", __func__,
+ streamId);
+ return nullptr;
+ }
+ return it->second->getHardwareBuffer(bufferId);
+}
+
+std::shared_ptr<EglFrameBuffer>
+VirtualCameraSessionContext::fetchOrCreateEglFramebuffer(
+ const EGLDisplay eglDisplay, const int streamId, const int bufferId) {
+ std::lock_guard<std::mutex> lock(mLock);
+ auto it = mStreams.find(streamId);
+ if (it == mStreams.end()) {
+ ALOGE("%s: StreamBuffer references buffer of unknown streamId %d", __func__,
+ streamId);
+ return nullptr;
+ }
+ return it->second->getEglFrameBuffer(eglDisplay, bufferId);
+}
+
+std::set<int> VirtualCameraSessionContext::getStreamIds() const {
+ std::set<int> result;
+ std::lock_guard<std::mutex> lock(mLock);
+ for (const auto& [streamId, _] : mStreams) {
+ result.insert(streamId);
+ }
+ return result;
+}
+
+} // namespace virtualcamera
+} // namespace companion
+} // namespace android
diff --git a/services/camera/virtualcamera/VirtualCameraSessionContext.h b/services/camera/virtualcamera/VirtualCameraSessionContext.h
new file mode 100644
index 0000000..e5cf5f4
--- /dev/null
+++ b/services/camera/virtualcamera/VirtualCameraSessionContext.h
@@ -0,0 +1,98 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_COMPANION_VIRTUALCAMERA_VIRTUALCAMERASESSIONCONTEXT_H
+#define ANDROID_COMPANION_VIRTUALCAMERA_VIRTUALCAMERASESSIONCONTEXT_H
+
+#include <map>
+#include <memory>
+#include <mutex>
+#include <set>
+
+#include "VirtualCameraStream.h"
+#include "aidl/android/hardware/camera/device/BufferCache.h"
+#include "aidl/android/hardware/camera/device/CaptureRequest.h"
+#include "aidl/android/hardware/camera/device/Stream.h"
+#include "aidl/android/hardware/camera/device/StreamConfiguration.h"
+
+namespace android {
+namespace companion {
+namespace virtualcamera {
+
+// Encapsulates set of streams belonging to the same camera session.
+class VirtualCameraSessionContext {
+ public:
+ // (Re)initialize the stream.
+ //
+ // Returns true if the stream is initialized for the first time.
+ bool initializeStream(
+ const ::aidl::android::hardware::camera::device::Stream& stream)
+ EXCLUDES(mLock);
+
+ // Close all streams and free all asociated buffers.
+ void closeAllStreams() EXCLUDES(mLock);
+
+ // Remove no longer needed buffers.
+ void removeBufferCaches(
+ const std::vector<::aidl::android::hardware::camera::device::BufferCache>&
+ cachesToRemove) EXCLUDES(mLock);
+
+ // Remove all streams not referenced by provided configuration.
+ void removeStreamsNotInStreamConfiguration(
+ const ::aidl::android::hardware::camera::device::StreamConfiguration&
+ streamConfiguration) EXCLUDES(mLock);
+
+ // Importored all not-yet imported buffers referenced by the capture request.
+ bool importBuffersFromCaptureRequest(
+ const ::aidl::android::hardware::camera::device::CaptureRequest&
+ captureRequest) EXCLUDES(mLock);
+
+ // Get stream configuration for provided stream id.
+ // Returns nullopt in case there's no stream with provided stream id.
+ std::optional<::aidl::android::hardware::camera::device::Stream>
+ getStreamConfig(int streamId) const EXCLUDES(mLock);
+
+ // Get hardware buffer for provided streamId & bufferId.
+ // Returns nullptr in case there's no such buffer.
+ std::shared_ptr<AHardwareBuffer> fetchHardwareBuffer(int streamId,
+ int bufferId) const
+ EXCLUDES(mLock);
+
+ // Get EGL framebuffer for provided EGL display, streamId & buffer id.
+ //
+ // This will also lazily create EglFrameBuffer for the provided EGLDisplay
+ // connection and will cache it (subsequent calls for same EGLDisplay and
+ // buffer will return same instance of EglFrameBuffer).
+ //
+ // Returns nullptr in case there's no such buffer or it was not possible
+ // to create EglFrameBuffer.
+ std::shared_ptr<EglFrameBuffer> fetchOrCreateEglFramebuffer(
+ const EGLDisplay eglDisplay, int streamId, int bufferId) EXCLUDES(mLock);
+
+ // Returns set of all stream ids managed by this instance.
+ std::set<int> getStreamIds() const EXCLUDES(mLock);
+
+ private:
+ mutable std::mutex mLock;
+ // streamId -> VirtualCameraStream mapping.
+ std::map<int, std::unique_ptr<VirtualCameraStream>> mStreams GUARDED_BY(mLock);
+};
+
+} // namespace virtualcamera
+} // namespace companion
+} // namespace android
+
+#endif // ANDROID_COMPANION_VIRTUALCAMERA_VIRTUALCAMERASESSIONCONTEXT_H
diff --git a/services/camera/virtualcamera/VirtualCameraStream.cc b/services/camera/virtualcamera/VirtualCameraStream.cc
new file mode 100644
index 0000000..03da171
--- /dev/null
+++ b/services/camera/virtualcamera/VirtualCameraStream.cc
@@ -0,0 +1,189 @@
+/*
+ * 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.
+ */
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "VirtualCameraStream"
+#include "VirtualCameraStream.h"
+
+#include <cstdint>
+#include <memory>
+#include <mutex>
+#include <tuple>
+#include <utility>
+
+#include "EGL/egl.h"
+#include "aidl/android/hardware/camera/device/Stream.h"
+#include "aidl/android/hardware/camera/device/StreamBuffer.h"
+#include "aidl/android/hardware/graphics/common/PixelFormat.h"
+#include "aidlcommonsupport/NativeHandle.h"
+#include "android/hardware_buffer.h"
+#include "cutils/native_handle.h"
+#include "ui/GraphicBuffer.h"
+#include "ui/GraphicBufferMapper.h"
+
+namespace android {
+namespace companion {
+namespace virtualcamera {
+
+using ::aidl::android::hardware::camera::device::Stream;
+using ::aidl::android::hardware::camera::device::StreamBuffer;
+using ::aidl::android::hardware::common::NativeHandle;
+using ::aidl::android::hardware::graphics::common::PixelFormat;
+
+namespace {
+
+sp<GraphicBuffer> createBlobGraphicBuffer(GraphicBufferMapper& mapper,
+ buffer_handle_t bufferHandle) {
+ uint64_t allocationSize;
+ uint64_t usage;
+ uint64_t layerCount;
+ if (mapper.getAllocationSize(bufferHandle, &allocationSize) != NO_ERROR ||
+ mapper.getUsage(bufferHandle, &usage) != NO_ERROR ||
+ mapper.getLayerCount(bufferHandle, &layerCount) != NO_ERROR) {
+ ALOGE("Error fetching metadata for the imported BLOB buffer handle.");
+ return nullptr;
+ }
+
+ return sp<GraphicBuffer>::make(
+ bufferHandle, GraphicBuffer::HandleWrapMethod::TAKE_HANDLE,
+ allocationSize, /*height=*/1, static_cast<int>(ui::PixelFormat::BLOB),
+ layerCount, usage, 0);
+}
+
+sp<GraphicBuffer> createYCbCr420GraphicBuffer(GraphicBufferMapper& mapper,
+ buffer_handle_t bufferHandle) {
+ uint64_t width;
+ uint64_t height;
+ uint64_t usage;
+ uint64_t layerCount;
+ if (mapper.getWidth(bufferHandle, &width) != NO_ERROR ||
+ mapper.getHeight(bufferHandle, &height) != NO_ERROR ||
+ mapper.getUsage(bufferHandle, &usage) != NO_ERROR ||
+ mapper.getLayerCount(bufferHandle, &layerCount) != NO_ERROR) {
+ ALOGE("Error fetching metadata for the imported YCbCr420 buffer handle.");
+ return nullptr;
+ }
+
+ return sp<GraphicBuffer>::make(
+ bufferHandle, GraphicBuffer::HandleWrapMethod::TAKE_HANDLE, width, height,
+ static_cast<int>(ui::PixelFormat::YCBCR_420_888), /*layers=*/1, usage,
+ width);
+}
+
+std::shared_ptr<AHardwareBuffer> importBufferInternal(
+ const NativeHandle& aidlHandle, const Stream& streamConfig) {
+ if (aidlHandle.fds.empty()) {
+ ALOGE("Empty handle - nothing to import");
+ return nullptr;
+ }
+ std::unique_ptr<native_handle_t, int (*)(native_handle_t*)> nativeHandle(
+ ::android::makeFromAidl(aidlHandle), native_handle_delete);
+
+ GraphicBufferMapper& mapper = GraphicBufferMapper::get();
+
+ buffer_handle_t bufferHandle;
+ // Use importBufferNoValidate to rely on ground-truth metadata passed along
+ // the buffer.
+ int ret = mapper.importBufferNoValidate(nativeHandle.get(), &bufferHandle);
+ if (ret != NO_ERROR) {
+ ALOGE("Failed to import buffer handle: %d", ret);
+ return nullptr;
+ }
+
+ sp<GraphicBuffer> buf =
+ streamConfig.format == PixelFormat::BLOB
+ ? createBlobGraphicBuffer(mapper, bufferHandle)
+ : createYCbCr420GraphicBuffer(mapper, bufferHandle);
+
+ if (buf->initCheck() != NO_ERROR) {
+ ALOGE("Imported graphic buffer is not correcly initialized.");
+ return nullptr;
+ }
+
+ AHardwareBuffer* rawPtr = buf->toAHardwareBuffer();
+ AHardwareBuffer_acquire(rawPtr);
+
+ return std::shared_ptr<AHardwareBuffer>(buf->toAHardwareBuffer(),
+ AHardwareBuffer_release);
+}
+
+} // namespace
+
+VirtualCameraStream::VirtualCameraStream(const Stream& stream)
+ : mStreamConfig(stream) {
+}
+
+std::shared_ptr<AHardwareBuffer> VirtualCameraStream::importBuffer(
+ const ::aidl::android::hardware::camera::device::StreamBuffer& buffer) {
+ auto hwBufferPtr = importBufferInternal(buffer.buffer, mStreamConfig);
+ if (hwBufferPtr != nullptr) {
+ std::lock_guard<std::mutex> lock(mLock);
+ mBuffers.emplace(std::piecewise_construct,
+ std::forward_as_tuple(buffer.bufferId),
+ std::forward_as_tuple(hwBufferPtr));
+ }
+ return hwBufferPtr;
+}
+
+std::shared_ptr<AHardwareBuffer> VirtualCameraStream::getHardwareBuffer(
+ const int bufferId) {
+ std::lock_guard<std::mutex> lock(mLock);
+ return getHardwareBufferLocked(bufferId);
+}
+
+std::shared_ptr<EglFrameBuffer> VirtualCameraStream::getEglFrameBuffer(
+ const EGLDisplay eglDisplay, const int bufferId) {
+ const FramebufferMapKey key(bufferId, eglDisplay);
+
+ std::lock_guard<std::mutex> lock(mLock);
+
+ auto it = mEglFramebuffers.find(key);
+ if (it != mEglFramebuffers.end()) {
+ return it->second;
+ }
+
+ std::shared_ptr<AHardwareBuffer> hwBufferPtr =
+ getHardwareBufferLocked(bufferId);
+ if (hwBufferPtr == nullptr) {
+ return nullptr;
+ }
+ std::shared_ptr<EglFrameBuffer> framebufferPtr =
+ std::make_shared<EglFrameBuffer>(eglDisplay, hwBufferPtr);
+ mEglFramebuffers.emplace(std::piecewise_construct, std::forward_as_tuple(key),
+ std::forward_as_tuple(framebufferPtr));
+
+ return framebufferPtr;
+}
+
+std::shared_ptr<AHardwareBuffer> VirtualCameraStream::getHardwareBufferLocked(
+ const int bufferId) {
+ auto it = mBuffers.find(bufferId);
+ return it != mBuffers.end() ? it->second : nullptr;
+}
+
+bool VirtualCameraStream::removeBuffer(int bufferId) {
+ std::lock_guard<std::mutex> lock(mLock);
+
+ return mBuffers.erase(bufferId) == 1;
+}
+
+Stream VirtualCameraStream::getStreamConfig() const {
+ return mStreamConfig;
+}
+
+} // namespace virtualcamera
+} // namespace companion
+} // namespace android
diff --git a/services/camera/virtualcamera/VirtualCameraStream.h b/services/camera/virtualcamera/VirtualCameraStream.h
new file mode 100644
index 0000000..d76d05c
--- /dev/null
+++ b/services/camera/virtualcamera/VirtualCameraStream.h
@@ -0,0 +1,93 @@
+/*
+ * 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.
+ */
+#ifndef ANDROID_COMPANION_VIRTUALCAMERA_VIRTUALCAMERASTREAM_H
+#define ANDROID_COMPANION_VIRTUALCAMERA_VIRTUALCAMERASTREAM_H
+
+#include <cstdint>
+#include <functional>
+#include <memory>
+#include <mutex>
+#include <tuple>
+#include <unordered_map>
+
+#include "EGL/egl.h"
+#include "aidl/android/hardware/camera/device/Stream.h"
+#include "aidl/android/hardware/camera/device/StreamBuffer.h"
+#include "android/hardware_buffer.h"
+#include "util/EglFramebuffer.h"
+#include "utils/Mutex.h"
+
+namespace android {
+namespace companion {
+namespace virtualcamera {
+
+// Encapsulates buffer management for the set of buffers belonging to the single
+// camera stream.
+class VirtualCameraStream {
+ public:
+ VirtualCameraStream(
+ const ::aidl::android::hardware::camera::device::Stream& stream);
+
+ std::shared_ptr<AHardwareBuffer> importBuffer(
+ const ::aidl::android::hardware::camera::device::StreamBuffer& streamBuffer);
+
+ // Get AHardwareBuffer instance corresponding to StreamBuffer from camera AIDL.
+ // In case this is the first occurrence of the buffer, this will perform mapping
+ // and stores hardware buffer in cache for further use.
+ //
+ // Returns nullptr in case buffer cannot be mapped or retrieved from the cache.
+ std::shared_ptr<AHardwareBuffer> getHardwareBuffer(int bufferId)
+ EXCLUDES(mLock);
+
+ std::shared_ptr<EglFrameBuffer> getEglFrameBuffer(const EGLDisplay eglDisplay,
+ int bufferId)
+ EXCLUDES(mLock);
+
+ // Un-maps the previously mapped buffer and removes it from the stream cache.
+ // Returns true if removal is successful, false otherwise.
+ bool removeBuffer(int bufferId) EXCLUDES(mLock);
+
+ // Returns AIDL Stream instance containing configuration of this stream.
+ ::aidl::android::hardware::camera::device::Stream getStreamConfig() const;
+
+ private:
+ std::shared_ptr<AHardwareBuffer> getHardwareBufferLocked(int bufferId)
+ REQUIRES(mLock);
+
+ const ::aidl::android::hardware::camera::device::Stream mStreamConfig;
+ std::mutex mLock;
+
+ // Cache for already mapped buffers, mapping bufferId -> AHardwareBuffer instance.
+ std::unordered_map<int, std::shared_ptr<AHardwareBuffer>> mBuffers
+ GUARDED_BY(mLock);
+
+ using FramebufferMapKey = std::pair<int, EGLDisplay>;
+ struct FramebufferMapKeyHash {
+ std::size_t operator()(const FramebufferMapKey& key) const {
+ return std::hash<int>{}(key.first) ^
+ (std::hash<void*>{}(reinterpret_cast<void*>(key.second)) << 1);
+ }
+ };
+ std::unordered_map<FramebufferMapKey, std::shared_ptr<EglFrameBuffer>,
+ FramebufferMapKeyHash>
+ mEglFramebuffers GUARDED_BY(mLock);
+};
+
+} // namespace virtualcamera
+} // namespace companion
+} // namespace android
+
+#endif // ANDROID_COMPANION_VIRTUALCAMERA_VIRTUALCAMERASTREAM_H
diff --git a/services/camera/virtualcamera/aidl/Android.bp b/services/camera/virtualcamera/aidl/Android.bp
new file mode 100644
index 0000000..9105b09
--- /dev/null
+++ b/services/camera/virtualcamera/aidl/Android.bp
@@ -0,0 +1,36 @@
+package {
+ // See: http://go/android-license-faq
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+aidl_interface {
+ name: "virtual_camera_service_aidl",
+ unstable: true,
+ srcs: [
+ "android/companion/virtualcamera/Format.aidl",
+ "android/companion/virtualcamera/IVirtualCameraCallback.aidl",
+ "android/companion/virtualcamera/IVirtualCameraService.aidl",
+ "android/companion/virtualcamera/VirtualCameraConfiguration.aidl",
+ "android/companion/virtualcamera/SupportedStreamConfiguration.aidl",
+ ],
+ local_include_dir: ".",
+ include_dirs: [
+ "frameworks/native/aidl/gui",
+ ],
+ backend: {
+ cpp: {
+ enabled: false,
+ },
+ ndk: {
+ enabled: true,
+ additional_shared_libraries: [
+ "libnativewindow",
+ ],
+ min_sdk_version: "34",
+ },
+ java: {
+ enabled: true,
+ platform_apis: true,
+ }
+ },
+}
diff --git a/services/camera/virtualcamera/aidl/android/companion/virtualcamera/Format.aidl b/services/camera/virtualcamera/aidl/android/companion/virtualcamera/Format.aidl
new file mode 100644
index 0000000..d9b90a6
--- /dev/null
+++ b/services/camera/virtualcamera/aidl/android/companion/virtualcamera/Format.aidl
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+
+package android.companion.virtualcamera;
+
+/**
+ * Pixel format supported by a virtual camera stream.
+ *
+ * @hide
+ */
+@Backing(type="int")
+enum Format {
+ UNKNOWN = 0,
+ YUV_420_888 = 0x23,
+}
diff --git a/services/camera/virtualcamera/aidl/android/companion/virtualcamera/IVirtualCameraCallback.aidl b/services/camera/virtualcamera/aidl/android/companion/virtualcamera/IVirtualCameraCallback.aidl
new file mode 100644
index 0000000..cbe03e9
--- /dev/null
+++ b/services/camera/virtualcamera/aidl/android/companion/virtualcamera/IVirtualCameraCallback.aidl
@@ -0,0 +1,58 @@
+/*
+ * 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.
+ */
+
+package android.companion.virtualcamera;
+
+import android.companion.virtualcamera.Format;
+import android.view.Surface;
+
+/**
+ * AIDL Interface to receive callbacks from virtual camera instance.
+ * @hide
+ */
+oneway interface IVirtualCameraCallback {
+
+ /**
+ * Called when there's new video stream. This callback is send after clients opens and
+ * configures camera. Implementation should hold onto the surface until corresponding
+ * terminateStream call is received.
+ *
+ * @param streamId - id of the video stream.
+ * @param surface - Surface representing the virtual camera sensor.
+ * @param width - width of the surface.
+ * @param height - height of the surface.
+ * @param pixelFormat - pixel format of the surface.
+ */
+ void onStreamConfigured(int streamId, in Surface surface, int width, int height, in Format pixelFormat);
+
+ /**
+ * Called when framework requests capture. This can be used by the client as a hint
+ * to render another frame into input surface.
+ *
+ * @param streamId - id of the stream corresponding to the Surface for which next
+ * frame is requested.
+ * @param frameId - id of the requested frame.
+ */
+ void onProcessCaptureRequest(int streamId, int frameId);
+
+ /**
+ * Called when the corresponding stream is no longer in use. Implementation should dispose of
+ * corresponding Surface upon receiving this call and no longer interact with it.
+ *
+ * @param streamId - id of the video stream to terminate.
+ */
+ void onStreamClosed(int streamId);
+}
diff --git a/services/camera/virtualcamera/aidl/android/companion/virtualcamera/IVirtualCameraService.aidl b/services/camera/virtualcamera/aidl/android/companion/virtualcamera/IVirtualCameraService.aidl
new file mode 100644
index 0000000..af8324a
--- /dev/null
+++ b/services/camera/virtualcamera/aidl/android/companion/virtualcamera/IVirtualCameraService.aidl
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+
+package android.companion.virtualcamera;
+
+import android.companion.virtualcamera.VirtualCameraConfiguration;
+
+/**
+ * AIDL Interface to communicate with the VirtualCamera HAL
+ * @hide
+ */
+interface IVirtualCameraService {
+
+ /**
+ * Registers a new camera with the virtual camera hal.
+ * @return true if the camera was successfully registered
+ */
+ boolean registerCamera(in IBinder token, in VirtualCameraConfiguration configuration);
+
+ /**
+ * Unregisters the camera from the virtual camera hal. After this call the virtual camera won't
+ * be visible to the camera framework anymore.
+ */
+ void unregisterCamera(in IBinder token);
+}
diff --git a/services/camera/virtualcamera/aidl/android/companion/virtualcamera/SupportedStreamConfiguration.aidl b/services/camera/virtualcamera/aidl/android/companion/virtualcamera/SupportedStreamConfiguration.aidl
new file mode 100644
index 0000000..7070cbd
--- /dev/null
+++ b/services/camera/virtualcamera/aidl/android/companion/virtualcamera/SupportedStreamConfiguration.aidl
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+package android.companion.virtualcamera;
+
+import android.companion.virtualcamera.Format;
+
+/**
+ * Configuration supported by virtual camera owner.
+ *
+ * @hide
+ */
+parcelable SupportedStreamConfiguration {
+ int width;
+ int height;
+ Format pixelFormat = Format.UNKNOWN;
+}
diff --git a/services/camera/virtualcamera/aidl/android/companion/virtualcamera/VirtualCameraConfiguration.aidl b/services/camera/virtualcamera/aidl/android/companion/virtualcamera/VirtualCameraConfiguration.aidl
new file mode 100644
index 0000000..c1a2f22
--- /dev/null
+++ b/services/camera/virtualcamera/aidl/android/companion/virtualcamera/VirtualCameraConfiguration.aidl
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+package android.companion.virtualcamera;
+
+import android.companion.virtualcamera.IVirtualCameraCallback;
+import android.companion.virtualcamera.SupportedStreamConfiguration;
+
+/**
+ * Configuration of virtual camera instance.
+ *
+ * @hide
+ */
+parcelable VirtualCameraConfiguration {
+ SupportedStreamConfiguration[] supportedStreamConfigs;
+ IVirtualCameraCallback virtualCameraCallback;
+}
diff --git a/services/camera/virtualcamera/fuzzer/Android.bp b/services/camera/virtualcamera/fuzzer/Android.bp
new file mode 100644
index 0000000..71e8f50
--- /dev/null
+++ b/services/camera/virtualcamera/fuzzer/Android.bp
@@ -0,0 +1,46 @@
+/******************************************************************************
+ *
+ * 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.
+ *
+ *****************************************************************************/
+ package {
+ // See: http://go/android-license-faq
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_fuzz {
+ name: "virtual_camera_fuzzer",
+ defaults: [
+ "libvirtualcamera_defaults",
+ "service_fuzzer_defaults",
+ ],
+ static_libs: [
+ "libvirtualcamera",
+ "libvirtualcamera_utils",
+ ],
+ srcs: [
+ "virtual_camera_fuzzer.cc",
+ ],
+ fuzz_config: {
+ cc: [
+ "if-xr+fuzzer@google.com",
+ ],
+ componentid: 1171888,
+ hotlists: [
+ "5426614",
+ ],
+ description: "The fuzzers target the APIs of virtual_camera binary",
+ },
+}
diff --git a/services/camera/virtualcamera/fuzzer/virtual_camera_fuzzer.cc b/services/camera/virtualcamera/fuzzer/virtual_camera_fuzzer.cc
new file mode 100644
index 0000000..ebd5e73
--- /dev/null
+++ b/services/camera/virtualcamera/fuzzer/virtual_camera_fuzzer.cc
@@ -0,0 +1,42 @@
+/*
+ * 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 <android-base/logging.h>
+#include <android/binder_interface_utils.h>
+#include <fuzzbinder/libbinder_ndk_driver.h>
+#include <fuzzer/FuzzedDataProvider.h>
+
+#include "VirtualCameraProvider.h"
+#include "VirtualCameraService.h"
+
+using android::fuzzService;
+using ::android::companion::virtualcamera::VirtualCameraProvider;
+using ::android::companion::virtualcamera::VirtualCameraService;
+using ndk::SharedRefBase;
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ std::shared_ptr<VirtualCameraProvider> defaultProvider =
+ SharedRefBase::make<VirtualCameraProvider>();
+
+ fuzzService(defaultProvider->asBinder().get(), FuzzedDataProvider(data, size));
+
+ const std::string serviceName =
+ std::string(VirtualCameraProvider::descriptor) + "/virtual/0";
+ auto binder = SharedRefBase::make<VirtualCameraService>(defaultProvider);
+
+ fuzzService(binder->asBinder().get(), FuzzedDataProvider(data, size));
+ return 0;
+}
diff --git a/services/camera/virtualcamera/main.cc b/services/camera/virtualcamera/main.cc
new file mode 100644
index 0000000..b7e9c38
--- /dev/null
+++ b/services/camera/virtualcamera/main.cc
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "VirtualCamera"
+
+#include <android/binder_stability.h>
+
+#include <cstddef>
+
+#include "VirtualCameraProvider.h"
+#include "VirtualCameraService.h"
+#include "android-base/logging.h"
+#include "android/binder_manager.h"
+#include "android/binder_process.h"
+#include "log/log.h"
+
+using ::android::companion::virtualcamera::VirtualCameraProvider;
+using ::android::companion::virtualcamera::VirtualCameraService;
+
+namespace {
+// Default recommended RPC thread count for camera provider implementations
+const int HWBINDER_THREAD_COUNT = 6;
+
+constexpr char kVirtualCameraServiceName[] = "virtual_camera";
+} // namespace
+
+int main() {
+ ALOGI("CameraProvider: virtual webcam service is starting.");
+
+ ABinderProcess_setThreadPoolMaxThreadCount(HWBINDER_THREAD_COUNT);
+
+ std::shared_ptr<VirtualCameraProvider> defaultProvider =
+ ndk::SharedRefBase::make<VirtualCameraProvider>();
+ const std::string serviceName =
+ std::string(VirtualCameraProvider::descriptor) + "/virtual/0";
+
+ auto aidlBinder = defaultProvider->asBinder();
+ AIBinder_forceDowngradeToLocalStability(aidlBinder.get());
+ binder_exception_t ret =
+ AServiceManager_addService(aidlBinder.get(), serviceName.c_str());
+ LOG_ALWAYS_FATAL_IF(
+ ret != EX_NONE,
+ "Error while registering virtual camera provider service: %d", ret);
+
+ std::shared_ptr<VirtualCameraService> virtualCameraService =
+ ndk::SharedRefBase::make<VirtualCameraService>(defaultProvider);
+ ret = AServiceManager_addService(virtualCameraService->asBinder().get(),
+ kVirtualCameraServiceName);
+ LOG_ALWAYS_FATAL_IF(ret != EX_NONE,
+ "Error while registering virtual camera service: %d", ret);
+
+ ABinderProcess_joinThreadPool();
+ return EXIT_FAILURE; // should not reach
+}
diff --git a/services/camera/virtualcamera/tests/Android.bp b/services/camera/virtualcamera/tests/Android.bp
new file mode 100644
index 0000000..c30779c
--- /dev/null
+++ b/services/camera/virtualcamera/tests/Android.bp
@@ -0,0 +1,23 @@
+package {
+ // See: http://go/android-license-faq
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_test {
+ name: "virtual_camera_tests",
+ defaults: [
+ "libvirtualcamera_defaults",
+ ],
+ static_libs: [
+ "libvirtualcamera",
+ "libvirtualcamera_utils",
+ "libgtest",
+ "libgmock",
+ ],
+ srcs: ["EglUtilTest.cc",
+ "VirtualCameraProviderTest.cc",
+ "VirtualCameraRenderThreadTest.cc",
+ "VirtualCameraServiceTest.cc",
+ "VirtualCameraSessionTest.cc"],
+ test_suites: ["device-tests"],
+}
diff --git a/services/camera/virtualcamera/tests/EglUtilTest.cc b/services/camera/virtualcamera/tests/EglUtilTest.cc
new file mode 100644
index 0000000..86fc50c
--- /dev/null
+++ b/services/camera/virtualcamera/tests/EglUtilTest.cc
@@ -0,0 +1,68 @@
+/*
+ * 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 "gtest/gtest.h"
+#include "util/EglDisplayContext.h"
+#include "util/EglProgram.h"
+#include "util/EglUtil.h"
+
+namespace android {
+namespace companion {
+namespace virtualcamera {
+namespace {
+
+constexpr char kGlExtYuvTarget[] = "GL_EXT_YUV_target";
+
+TEST(EglDisplayContextTest, SuccessfulInitialization) {
+ EglDisplayContext displayContext;
+
+ EXPECT_TRUE(displayContext.isInitialized());
+}
+
+class EglProgramTest : public ::testing::Test {
+ public:
+ void SetUp() override {
+ ASSERT_TRUE(mEglDisplayContext.isInitialized());
+ ASSERT_TRUE(mEglDisplayContext.makeCurrent());
+ }
+
+ private:
+ EglDisplayContext mEglDisplayContext;
+};
+
+TEST_F(EglProgramTest, EglTestPatternProgramSuccessfulInit) {
+ EglTestPatternProgram eglTestPatternProgram;
+
+ // Verify the shaders compiled and linked successfully.
+ EXPECT_TRUE(eglTestPatternProgram.isInitialized());
+}
+
+TEST_F(EglProgramTest, EglTextureProgramSuccessfulInit) {
+ if (!isGlExtensionSupported(kGlExtYuvTarget)) {
+ GTEST_SKIP() << "Skipping test because of missing required GL extension "
+ << kGlExtYuvTarget;
+ }
+
+ EglTextureProgram eglTextureProgram;
+
+ // Verify the shaders compiled and linked successfully.
+ EXPECT_TRUE(eglTextureProgram.isInitialized());
+}
+
+} // namespace
+} // namespace virtualcamera
+} // namespace companion
+} // namespace android
diff --git a/services/camera/virtualcamera/tests/VirtualCameraProviderTest.cc b/services/camera/virtualcamera/tests/VirtualCameraProviderTest.cc
new file mode 100644
index 0000000..03fc2c2
--- /dev/null
+++ b/services/camera/virtualcamera/tests/VirtualCameraProviderTest.cc
@@ -0,0 +1,162 @@
+/*
+ * 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 "VirtualCameraProvider.h"
+#include "aidl/android/hardware/camera/common/CameraDeviceStatus.h"
+#include "aidl/android/hardware/camera/common/Status.h"
+#include "aidl/android/hardware/camera/common/TorchModeStatus.h"
+#include "aidl/android/hardware/camera/provider/BnCameraProviderCallback.h"
+#include "android/binder_auto_utils.h"
+#include "android/binder_interface_utils.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "util/Util.h"
+
+namespace android {
+namespace companion {
+namespace virtualcamera {
+namespace {
+
+using ::aidl::android::hardware::camera::common::CameraDeviceStatus;
+using ::aidl::android::hardware::camera::common::Status;
+using ::aidl::android::hardware::camera::common::TorchModeStatus;
+using ::aidl::android::hardware::camera::provider::BnCameraProviderCallback;
+using ::testing::_;
+using ::testing::ElementsAre;
+using ::testing::Eq;
+using ::testing::IsEmpty;
+using ::testing::IsNull;
+using ::testing::MatchesRegex;
+using ::testing::Not;
+using ::testing::Return;
+
+constexpr char kVirtualCameraNameRegex[] =
+ "device@[0-9]+\\.[0-9]+/virtual/[0-9]+";
+
+class MockCameraProviderCallback : public BnCameraProviderCallback {
+ public:
+ MOCK_METHOD(ndk::ScopedAStatus, cameraDeviceStatusChange,
+ (const std::string&, CameraDeviceStatus), (override));
+ MOCK_METHOD(ndk::ScopedAStatus, torchModeStatusChange,
+ (const std::string&, TorchModeStatus), (override));
+ MOCK_METHOD(ndk::ScopedAStatus, physicalCameraDeviceStatusChange,
+ (const std::string&, const std::string&, CameraDeviceStatus),
+ (override));
+};
+
+class VirtualCameraProviderTest : public ::testing::Test {
+ public:
+ void SetUp() override {
+ mCameraProvider = ndk::SharedRefBase::make<VirtualCameraProvider>();
+ mMockCameraProviderCallback =
+ ndk::SharedRefBase::make<MockCameraProviderCallback>();
+ ON_CALL(*mMockCameraProviderCallback, cameraDeviceStatusChange)
+ .WillByDefault([](const std::string&, CameraDeviceStatus) {
+ return ndk::ScopedAStatus::ok();
+ });
+ }
+
+ protected:
+ std::shared_ptr<VirtualCameraProvider> mCameraProvider;
+ std::shared_ptr<MockCameraProviderCallback> mMockCameraProviderCallback =
+ ndk::SharedRefBase::make<MockCameraProviderCallback>();
+};
+
+TEST_F(VirtualCameraProviderTest, SetNullCameraCallbackFails) {
+ // Attempting to set callback to nullptr should fail.
+ EXPECT_FALSE(mCameraProvider->setCallback(nullptr).isOk());
+}
+
+TEST_F(VirtualCameraProviderTest, NoCamerasInitially) {
+ std::vector<std::string> cameras;
+
+ // Initially, the camera provider should return empty list
+ // of cameras.
+ ASSERT_TRUE(mCameraProvider->getCameraIdList(&cameras).isOk());
+ EXPECT_THAT(cameras, IsEmpty());
+}
+
+TEST_F(VirtualCameraProviderTest, CreateCamera) {
+ // When new camera is created, we expect
+ // cameraDeviceStatusChange to be called exactly once with
+ // PRESENT status.
+ EXPECT_CALL(*mMockCameraProviderCallback,
+ cameraDeviceStatusChange(_, CameraDeviceStatus::PRESENT))
+ .WillOnce(Return(ndk::ScopedAStatus::ok()));
+
+ ASSERT_TRUE(mCameraProvider->setCallback(mMockCameraProviderCallback).isOk());
+ std::shared_ptr<VirtualCameraDevice> camera = mCameraProvider->createCamera();
+ EXPECT_THAT(camera, Not(IsNull()));
+ EXPECT_THAT(camera->getCameraName(), MatchesRegex(kVirtualCameraNameRegex));
+
+ // Created camera should be in the list of cameras.
+ std::vector<std::string> cameraIds;
+ ASSERT_TRUE(mCameraProvider->getCameraIdList(&cameraIds).isOk());
+ EXPECT_THAT(cameraIds, ElementsAre(camera->getCameraName()));
+}
+
+TEST_F(VirtualCameraProviderTest, CreateCameraBeforeCallbackIsSet) {
+ // We expect cameraDeviceStatusChange to be invoked even when the
+ // setCallback configures the callback after camera is already created.
+ EXPECT_CALL(*mMockCameraProviderCallback,
+ cameraDeviceStatusChange(_, CameraDeviceStatus::PRESENT))
+ .WillOnce(Return(ndk::ScopedAStatus::ok()));
+
+ std::shared_ptr<VirtualCameraDevice> camera = mCameraProvider->createCamera();
+ ASSERT_TRUE(mCameraProvider->setCallback(mMockCameraProviderCallback).isOk());
+
+ // Created camera should be in the list of cameras.
+ std::vector<std::string> cameraIds;
+ EXPECT_TRUE(mCameraProvider->getCameraIdList(&cameraIds).isOk());
+ EXPECT_THAT(cameraIds, ElementsAre(camera->getCameraName()));
+}
+
+TEST_F(VirtualCameraProviderTest, RemoveCamera) {
+ ASSERT_TRUE(mCameraProvider->setCallback(mMockCameraProviderCallback).isOk());
+ std::shared_ptr<VirtualCameraDevice> camera = mCameraProvider->createCamera();
+
+ EXPECT_CALL(*mMockCameraProviderCallback,
+ cameraDeviceStatusChange(Eq(camera->getCameraName()),
+ CameraDeviceStatus::NOT_PRESENT))
+ .WillOnce(Return(ndk::ScopedAStatus::ok()));
+ EXPECT_TRUE(mCameraProvider->removeCamera(camera->getCameraName()));
+
+ // There are no cameras present after only camera is removed.
+ std::vector<std::string> cameraIds;
+ ASSERT_TRUE(mCameraProvider->getCameraIdList(&cameraIds).isOk());
+ EXPECT_THAT(cameraIds, IsEmpty());
+}
+
+TEST_F(VirtualCameraProviderTest, RemoveNonExistingCamera) {
+ ASSERT_TRUE(mCameraProvider->setCallback(mMockCameraProviderCallback).isOk());
+ std::shared_ptr<VirtualCameraDevice> camera = mCameraProvider->createCamera();
+
+ // Removing non-existing camera should fail.
+ const std::string cameraName = "DefinitelyNoTCamera";
+ EXPECT_FALSE(mCameraProvider->removeCamera(cameraName));
+
+ // Camera should be still present in the camera list.
+ std::vector<std::string> cameraIds;
+ ASSERT_TRUE(mCameraProvider->getCameraIdList(&cameraIds).isOk());
+ EXPECT_THAT(cameraIds, ElementsAre(camera->getCameraName()));
+}
+
+} // namespace
+} // namespace virtualcamera
+} // namespace companion
+} // namespace android
diff --git a/services/camera/virtualcamera/tests/VirtualCameraRenderThreadTest.cc b/services/camera/virtualcamera/tests/VirtualCameraRenderThreadTest.cc
new file mode 100644
index 0000000..2e40d16
--- /dev/null
+++ b/services/camera/virtualcamera/tests/VirtualCameraRenderThreadTest.cc
@@ -0,0 +1,146 @@
+/*
+ * 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 <sys/cdefs.h>
+
+#include <memory>
+
+#include "VirtualCameraRenderThread.h"
+#include "VirtualCameraSessionContext.h"
+#include "aidl/android/hardware/camera/common/CameraDeviceStatus.h"
+#include "aidl/android/hardware/camera/common/TorchModeStatus.h"
+#include "aidl/android/hardware/camera/device/BnCameraDeviceCallback.h"
+#include "aidl/android/hardware/camera/device/BufferRequest.h"
+#include "aidl/android/hardware/camera/device/BufferRequestStatus.h"
+#include "aidl/android/hardware/camera/device/BufferStatus.h"
+#include "aidl/android/hardware/camera/device/CaptureResult.h"
+#include "aidl/android/hardware/camera/device/NotifyMsg.h"
+#include "aidl/android/hardware/camera/device/StreamBuffer.h"
+#include "aidl/android/hardware/camera/device/StreamBufferRet.h"
+#include "android/binder_auto_utils.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace android {
+namespace companion {
+namespace virtualcamera {
+namespace {
+
+using ::aidl::android::hardware::camera::common::CameraDeviceStatus;
+using ::aidl::android::hardware::camera::common::TorchModeStatus;
+using ::aidl::android::hardware::camera::device::BnCameraDeviceCallback;
+using ::aidl::android::hardware::camera::device::BufferRequest;
+using ::aidl::android::hardware::camera::device::BufferRequestStatus;
+using ::aidl::android::hardware::camera::device::BufferStatus;
+using ::aidl::android::hardware::camera::device::CaptureResult;
+using ::aidl::android::hardware::camera::device::ErrorMsg;
+using ::aidl::android::hardware::camera::device::NotifyMsg;
+using ::aidl::android::hardware::camera::device::StreamBuffer;
+using ::aidl::android::hardware::camera::device::StreamBufferRet;
+using ::testing::AllOf;
+using ::testing::ElementsAre;
+using ::testing::Eq;
+using ::testing::Field;
+using ::testing::Matcher;
+using ::testing::Property;
+using ::testing::Return;
+using ::testing::SizeIs;
+
+constexpr int kInputWidth = 640;
+constexpr int kInputHeight = 480;
+
+Matcher<StreamBuffer> IsStreamBufferWithStatus(const int streamId,
+ const int bufferId,
+ const BufferStatus status) {
+ return AllOf(Field(&StreamBuffer::streamId, Eq(streamId)),
+ Field(&StreamBuffer::bufferId, Eq(bufferId)),
+ Field(&StreamBuffer::status, Eq(status)));
+}
+
+Matcher<NotifyMsg> IsRequestErrorNotifyMsg(const int frameId) {
+ return AllOf(Property(&NotifyMsg::getTag, Eq(NotifyMsg::error)),
+ Property(&NotifyMsg::get<NotifyMsg::error>,
+ Field(&ErrorMsg::frameNumber, Eq(frameId))));
+}
+
+class MockCameraDeviceCallback : public BnCameraDeviceCallback {
+ public:
+ MOCK_METHOD(ndk::ScopedAStatus, notify, (const std::vector<NotifyMsg>&),
+ (override));
+ MOCK_METHOD(ndk::ScopedAStatus, processCaptureResult,
+ (const std::vector<CaptureResult>&), (override));
+ MOCK_METHOD(ndk::ScopedAStatus, requestStreamBuffers,
+ (const std::vector<BufferRequest>&, std::vector<StreamBufferRet>*,
+ BufferRequestStatus*),
+ (override));
+ MOCK_METHOD(ndk::ScopedAStatus, returnStreamBuffers,
+ (const std::vector<StreamBuffer>&), (override));
+};
+
+class VirtualCameraRenderThreadTest : public ::testing::Test {
+ public:
+ void SetUp() override {
+ mSessionContext = std::make_unique<VirtualCameraSessionContext>();
+ mMockCameraDeviceCallback =
+ ndk::SharedRefBase::make<MockCameraDeviceCallback>();
+ mRenderThread = std::make_unique<VirtualCameraRenderThread>(
+ *mSessionContext, kInputWidth, kInputHeight, mMockCameraDeviceCallback);
+ }
+
+ protected:
+ std::unique_ptr<VirtualCameraSessionContext> mSessionContext;
+ std::unique_ptr<VirtualCameraRenderThread> mRenderThread;
+ std::shared_ptr<MockCameraDeviceCallback> mMockCameraDeviceCallback;
+};
+
+TEST_F(VirtualCameraRenderThreadTest, FlushReturnsErrorForInFlightRequests) {
+ const int frameNumber = 42;
+ const int firstStreamId = 1;
+ const int firstStreamBufferId = 1234;
+ const int secondStreamId = 7;
+ const int secondStreamBufferId = 4321;
+
+ // Notify should be called with the error set to corresponding frame.
+ EXPECT_CALL(*mMockCameraDeviceCallback,
+ notify(ElementsAre(IsRequestErrorNotifyMsg(frameNumber))))
+ .WillOnce(Return(ndk::ScopedAStatus::ok()));
+
+ // Process capture result should be called with all buffers in error state.
+ EXPECT_CALL(
+ *mMockCameraDeviceCallback,
+ processCaptureResult(ElementsAre(AllOf(
+ Field(&CaptureResult::frameNumber, frameNumber),
+ Field(&CaptureResult::outputBuffers,
+ testing::UnorderedElementsAre(
+ IsStreamBufferWithStatus(firstStreamId, firstStreamBufferId,
+ BufferStatus::ERROR),
+ IsStreamBufferWithStatus(secondStreamId, secondStreamBufferId,
+ BufferStatus::ERROR)))))))
+ .WillOnce([]() { return ndk::ScopedAStatus::ok(); });
+
+ mRenderThread->enqueueTask(std::make_unique<ProcessCaptureRequestTask>(
+ frameNumber,
+ std::vector<CaptureRequestBuffer>{
+ CaptureRequestBuffer(firstStreamId, firstStreamBufferId),
+ CaptureRequestBuffer(secondStreamId, secondStreamBufferId)}));
+
+ mRenderThread->flush();
+}
+
+} // namespace
+} // namespace virtualcamera
+} // namespace companion
+} // namespace android
\ No newline at end of file
diff --git a/services/camera/virtualcamera/tests/VirtualCameraServiceTest.cc b/services/camera/virtualcamera/tests/VirtualCameraServiceTest.cc
new file mode 100644
index 0000000..4fd0b3b
--- /dev/null
+++ b/services/camera/virtualcamera/tests/VirtualCameraServiceTest.cc
@@ -0,0 +1,206 @@
+/*
+ * 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 <cstdio>
+#include <memory>
+
+#include "VirtualCameraService.h"
+#include "aidl/android/companion/virtualcamera/BnVirtualCameraCallback.h"
+#include "aidl/android/companion/virtualcamera/VirtualCameraConfiguration.h"
+#include "aidl/android/hardware/camera/provider/BnCameraProviderCallback.h"
+#include "aidl/android/hardware/graphics/common/PixelFormat.h"
+#include "android/binder_auto_utils.h"
+#include "android/binder_interface_utils.h"
+#include "android/binder_libbinder.h"
+#include "android/binder_status.h"
+#include "binder/Binder.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "utils/Errors.h"
+
+namespace android {
+namespace companion {
+namespace virtualcamera {
+namespace {
+
+using ::aidl::android::companion::virtualcamera::BnVirtualCameraCallback;
+using ::aidl::android::companion::virtualcamera::VirtualCameraConfiguration;
+using ::aidl::android::hardware::camera::common::CameraDeviceStatus;
+using ::aidl::android::hardware::camera::common::TorchModeStatus;
+using ::aidl::android::hardware::camera::provider::BnCameraProviderCallback;
+using ::aidl::android::hardware::graphics::common::PixelFormat;
+using ::aidl::android::view::Surface;
+using ::testing::_;
+using ::testing::Eq;
+using ::testing::Ge;
+using ::testing::IsEmpty;
+using ::testing::IsNull;
+using ::testing::Not;
+using ::testing::SizeIs;
+
+const VirtualCameraConfiguration kEmptyVirtualCameraConfiguration;
+
+class MockCameraProviderCallback : public BnCameraProviderCallback {
+ public:
+ MOCK_METHOD(ndk::ScopedAStatus, cameraDeviceStatusChange,
+ (const std::string&, CameraDeviceStatus), (override));
+ MOCK_METHOD(ndk::ScopedAStatus, torchModeStatusChange,
+ (const std::string&, TorchModeStatus), (override));
+ MOCK_METHOD(ndk::ScopedAStatus, physicalCameraDeviceStatusChange,
+ (const std::string&, const std::string&, CameraDeviceStatus),
+ (override));
+};
+
+class VirtualCameraServiceTest : public ::testing::Test {
+ public:
+ void SetUp() override {
+ mCameraProvider = ndk::SharedRefBase::make<VirtualCameraProvider>();
+ mMockCameraProviderCallback =
+ ndk::SharedRefBase::make<MockCameraProviderCallback>();
+ ON_CALL(*mMockCameraProviderCallback, cameraDeviceStatusChange)
+ .WillByDefault([](const std::string&, CameraDeviceStatus) {
+ return ndk::ScopedAStatus::ok();
+ });
+ mCameraProvider->setCallback(mMockCameraProviderCallback);
+ mCameraService =
+ ndk::SharedRefBase::make<VirtualCameraService>(mCameraProvider);
+
+ mDevNullFd = open("/dev/null", O_RDWR);
+ ASSERT_THAT(mDevNullFd, Ge(0));
+ }
+
+ void createCamera() {
+ mOwnerToken = sp<BBinder>::make();
+ mNdkOwnerToken.set(AIBinder_fromPlatformBinder(mOwnerToken));
+ bool aidlRet;
+
+ ASSERT_TRUE(mCameraService
+ ->registerCamera(mNdkOwnerToken,
+ kEmptyVirtualCameraConfiguration, &aidlRet)
+ .isOk());
+ ASSERT_TRUE(aidlRet);
+ }
+
+ void TearDown() override {
+ close(mDevNullFd);
+ }
+
+ void execute_shell_command(const std::string cmd) {
+ std::array<const char*, 1> args{cmd.data()};
+ ASSERT_THAT(
+ mCameraService->handleShellCommand(mDevNullFd, mDevNullFd, mDevNullFd,
+ args.data(), args.size()),
+ Eq(NO_ERROR));
+ }
+
+ protected:
+ std::shared_ptr<VirtualCameraService> mCameraService;
+ std::shared_ptr<VirtualCameraProvider> mCameraProvider;
+ std::shared_ptr<MockCameraProviderCallback> mMockCameraProviderCallback =
+ ndk::SharedRefBase::make<MockCameraProviderCallback>();
+
+ sp<BBinder> mOwnerToken;
+ ndk::SpAIBinder mNdkOwnerToken;
+
+ int mDevNullFd;
+};
+
+TEST_F(VirtualCameraServiceTest, RegisterCameraSucceeds) {
+ sp<BBinder> token = sp<BBinder>::make();
+ ndk::SpAIBinder ndkToken(AIBinder_fromPlatformBinder(token));
+ bool aidlRet;
+
+ ASSERT_TRUE(
+ mCameraService
+ ->registerCamera(ndkToken, kEmptyVirtualCameraConfiguration, &aidlRet)
+ .isOk());
+
+ EXPECT_TRUE(aidlRet);
+}
+
+TEST_F(VirtualCameraServiceTest, RegisterCameraTwiceSecondReturnsFalse) {
+ createCamera();
+ bool aidlRet;
+
+ ASSERT_TRUE(mCameraService
+ ->registerCamera(mNdkOwnerToken,
+ kEmptyVirtualCameraConfiguration, &aidlRet)
+ .isOk());
+ EXPECT_FALSE(aidlRet);
+}
+
+TEST_F(VirtualCameraServiceTest, GetCamera) {
+ createCamera();
+
+ EXPECT_THAT(mCameraService->getCamera(mNdkOwnerToken), Not(IsNull()));
+
+ sp<BBinder> otherToken = sp<BBinder>::make();
+ EXPECT_THAT(mCameraService->getCamera(
+ ndk::SpAIBinder(AIBinder_fromPlatformBinder(otherToken))),
+ IsNull());
+}
+
+TEST_F(VirtualCameraServiceTest, UnregisterCamera) {
+ createCamera();
+
+ EXPECT_THAT(mCameraService->getCamera(mNdkOwnerToken), Not(IsNull()));
+
+ mCameraService->unregisterCamera(mNdkOwnerToken);
+
+ EXPECT_THAT(mCameraService->getCamera(mNdkOwnerToken), IsNull());
+}
+
+TEST_F(VirtualCameraServiceTest, UnregisterCameraWithUnknownToken) {
+ createCamera();
+
+ EXPECT_THAT(mCameraService->getCamera(mNdkOwnerToken), Not(IsNull()));
+
+ auto otherToken = sp<BBinder>::make();
+ ndk::SpAIBinder ndkOtherToken(AIBinder_fromPlatformBinder(otherToken));
+ mCameraService->unregisterCamera(ndkOtherToken);
+
+ EXPECT_THAT(mCameraService->getCamera(mNdkOwnerToken), Not(IsNull()));
+}
+
+TEST_F(VirtualCameraServiceTest, ShellCmdWithNullArgs) {
+ EXPECT_EQ(mCameraService->handleShellCommand(
+ /*in=*/mDevNullFd, /*out=*/mDevNullFd, /*err=*/mDevNullFd,
+ /*args=*/nullptr, /*numArgs=*/1),
+ STATUS_BAD_VALUE);
+
+ std::array<const char*, 1> args{nullptr};
+ EXPECT_EQ(mCameraService->handleShellCommand(
+ /*in=*/mDevNullFd, /*out=*/mDevNullFd, /*err=*/mDevNullFd,
+ args.data(), /*numArgs=*/1),
+ STATUS_BAD_VALUE);
+}
+
+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));
+
+ execute_shell_command("disable_test_camera");
+ EXPECT_TRUE(mCameraProvider->getCameraIdList(&cameraIds).isOk());
+ EXPECT_THAT(cameraIds, IsEmpty());
+}
+
+} // namespace
+} // namespace virtualcamera
+} // namespace companion
+} // namespace android
diff --git a/services/camera/virtualcamera/tests/VirtualCameraSessionTest.cc b/services/camera/virtualcamera/tests/VirtualCameraSessionTest.cc
new file mode 100644
index 0000000..5da080b
--- /dev/null
+++ b/services/camera/virtualcamera/tests/VirtualCameraSessionTest.cc
@@ -0,0 +1,195 @@
+/*
+ * 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 <cstdint>
+#include <memory>
+
+#include "VirtualCameraSession.h"
+#include "aidl/android/companion/virtualcamera/BnVirtualCameraCallback.h"
+#include "aidl/android/companion/virtualcamera/IVirtualCameraCallback.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"
+#include "android/binder_auto_utils.h"
+#include "android/binder_interface_utils.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "util/MetadataBuilder.h"
+
+namespace android {
+namespace companion {
+namespace virtualcamera {
+namespace {
+
+constexpr int kWidth = 640;
+constexpr int kHeight = 480;
+constexpr int kStreamId = 0;
+const std::string kCameraName = "virtual_camera";
+
+using ::aidl::android::companion::virtualcamera::BnVirtualCameraCallback;
+using ::aidl::android::companion::virtualcamera::Format;
+using ::aidl::android::hardware::camera::device::BnCameraDeviceCallback;
+using ::aidl::android::hardware::camera::device::BufferRequest;
+using ::aidl::android::hardware::camera::device::BufferRequestStatus;
+using ::aidl::android::hardware::camera::device::CaptureRequest;
+using ::aidl::android::hardware::camera::device::CaptureResult;
+using ::aidl::android::hardware::camera::device::HalStream;
+using ::aidl::android::hardware::camera::device::NotifyMsg;
+using ::aidl::android::hardware::camera::device::Stream;
+using ::aidl::android::hardware::camera::device::StreamBuffer;
+using ::aidl::android::hardware::camera::device::StreamBufferRet;
+using ::aidl::android::hardware::camera::device::StreamConfiguration;
+using ::aidl::android::hardware::graphics::common::PixelFormat;
+using ::aidl::android::view::Surface;
+using ::testing::_;
+using ::testing::ElementsAre;
+using ::testing::Eq;
+using ::testing::Return;
+using ::testing::SizeIs;
+
+Stream createStream(int streamId, int width, int height, PixelFormat format) {
+ Stream s;
+ s.id = streamId;
+ s.width = width;
+ s.height = height;
+ s.format = format;
+ return s;
+}
+
+class MockCameraDeviceCallback : public BnCameraDeviceCallback {
+ public:
+ MOCK_METHOD(ndk::ScopedAStatus, notify, (const std::vector<NotifyMsg>&),
+ (override));
+ MOCK_METHOD(ndk::ScopedAStatus, processCaptureResult,
+ (const std::vector<CaptureResult>&), (override));
+ MOCK_METHOD(ndk::ScopedAStatus, requestStreamBuffers,
+ (const std::vector<BufferRequest>&, std::vector<StreamBufferRet>*,
+ BufferRequestStatus*),
+ (override));
+ MOCK_METHOD(ndk::ScopedAStatus, returnStreamBuffers,
+ (const std::vector<StreamBuffer>&), (override));
+};
+
+class MockVirtualCameraCallback : public BnVirtualCameraCallback {
+ public:
+ MOCK_METHOD(ndk::ScopedAStatus, onStreamConfigured,
+ (int, const Surface&, int32_t, int32_t, Format), (override));
+ MOCK_METHOD(ndk::ScopedAStatus, onProcessCaptureRequest, (int, int),
+ (override));
+ MOCK_METHOD(ndk::ScopedAStatus, onStreamClosed, (int), (override));
+};
+
+class VirtualCameraSessionTest : public ::testing::Test {
+ public:
+ void SetUp() override {
+ mMockCameraDeviceCallback =
+ ndk::SharedRefBase::make<MockCameraDeviceCallback>();
+ mMockVirtualCameraClientCallback =
+ ndk::SharedRefBase::make<MockVirtualCameraCallback>();
+ mVirtualCameraSession = ndk::SharedRefBase::make<VirtualCameraSession>(
+ kCameraName, mMockCameraDeviceCallback,
+ mMockVirtualCameraClientCallback);
+
+ ON_CALL(*mMockVirtualCameraClientCallback, onStreamConfigured)
+ .WillByDefault(ndk::ScopedAStatus::ok);
+ ON_CALL(*mMockVirtualCameraClientCallback, onProcessCaptureRequest)
+ .WillByDefault(ndk::ScopedAStatus::ok);
+ }
+
+ protected:
+ std::shared_ptr<MockCameraDeviceCallback> mMockCameraDeviceCallback;
+ std::shared_ptr<MockVirtualCameraCallback> mMockVirtualCameraClientCallback;
+ std::shared_ptr<VirtualCameraSession> mVirtualCameraSession;
+};
+
+TEST_F(VirtualCameraSessionTest, ConfigureTriggersClientConfigureCallback) {
+ PixelFormat format = PixelFormat::YCBCR_420_888;
+ StreamConfiguration streamConfiguration;
+ streamConfiguration.streams = {
+ createStream(kStreamId, kWidth, kHeight, format)};
+ std::vector<HalStream> halStreams;
+ EXPECT_CALL(
+ *mMockVirtualCameraClientCallback,
+ onStreamConfigured(kStreamId, _, kWidth, kHeight, Format::YUV_420_888));
+
+ ASSERT_TRUE(
+ mVirtualCameraSession->configureStreams(streamConfiguration, &halStreams)
+ .isOk());
+
+ EXPECT_THAT(halStreams, SizeIs(streamConfiguration.streams.size()));
+ EXPECT_THAT(mVirtualCameraSession->getStreamIds(), ElementsAre(0));
+}
+
+TEST_F(VirtualCameraSessionTest, SecondConfigureDropsUnreferencedStreams) {
+ PixelFormat format = PixelFormat::YCBCR_420_888;
+ StreamConfiguration streamConfiguration;
+ std::vector<HalStream> halStreams;
+
+ streamConfiguration.streams = {createStream(0, kWidth, kHeight, format),
+ createStream(1, kWidth, kHeight, format),
+ createStream(2, kWidth, kHeight, format)};
+ ASSERT_TRUE(
+ mVirtualCameraSession->configureStreams(streamConfiguration, &halStreams)
+ .isOk());
+
+ EXPECT_THAT(mVirtualCameraSession->getStreamIds(), ElementsAre(0, 1, 2));
+
+ streamConfiguration.streams = {createStream(0, kWidth, kHeight, format),
+ createStream(2, kWidth, kHeight, format),
+ createStream(3, kWidth, kHeight, format)};
+ ASSERT_TRUE(
+ mVirtualCameraSession->configureStreams(streamConfiguration, &halStreams)
+ .isOk());
+
+ EXPECT_THAT(mVirtualCameraSession->getStreamIds(), ElementsAre(0, 2, 3));
+}
+
+TEST_F(VirtualCameraSessionTest, CloseTriggersClientTerminateCallback) {
+ EXPECT_CALL(*mMockVirtualCameraClientCallback, onStreamClosed(kStreamId))
+ .WillOnce(Return(ndk::ScopedAStatus::ok()));
+
+ ASSERT_TRUE(mVirtualCameraSession->close().isOk());
+}
+
+TEST_F(VirtualCameraSessionTest, onProcessCaptureRequestTriggersClientCallback) {
+ StreamConfiguration streamConfiguration;
+ streamConfiguration.streams = {
+ createStream(kStreamId, kWidth, kHeight, PixelFormat::YCBCR_420_888)};
+ std::vector<CaptureRequest> requests(1);
+ requests[0].frameNumber = 42;
+ requests[0].settings = *(
+ MetadataBuilder().setControlAfMode(ANDROID_CONTROL_AF_MODE_AUTO).build());
+
+ std::vector<HalStream> halStreams;
+ ASSERT_TRUE(
+ mVirtualCameraSession->configureStreams(streamConfiguration, &halStreams)
+ .isOk());
+
+ EXPECT_CALL(*mMockVirtualCameraClientCallback,
+ onProcessCaptureRequest(kStreamId, requests[0].frameNumber))
+ .WillOnce(Return(ndk::ScopedAStatus::ok()));
+ int32_t aidlReturn = 0;
+ ASSERT_TRUE(mVirtualCameraSession
+ ->processCaptureRequest(requests, /*in_cachesToRemove=*/{},
+ &aidlReturn)
+ .isOk());
+ EXPECT_THAT(aidlReturn, Eq(requests.size()));
+}
+
+} // namespace
+} // namespace virtualcamera
+} // namespace companion
+} // namespace android
diff --git a/services/camera/virtualcamera/util/EglDisplayContext.cc b/services/camera/virtualcamera/util/EglDisplayContext.cc
new file mode 100644
index 0000000..6d343a2
--- /dev/null
+++ b/services/camera/virtualcamera/util/EglDisplayContext.cc
@@ -0,0 +1,112 @@
+/*
+ * 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.
+ */
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "EglDisplayContext"
+#define EGL_EGLEXT_PROTOTYPES
+#define GL_GLEXT_PROTOTYPES
+
+#include "EglDisplayContext.h"
+
+#include "EGL/egl.h"
+#include "EglDisplayContext.h"
+#include "EglFramebuffer.h"
+#include "log/log.h"
+
+namespace android {
+namespace companion {
+namespace virtualcamera {
+
+EglDisplayContext::EglDisplayContext()
+ : mEglDisplay(EGL_NO_DISPLAY),
+ mEglContext(EGL_NO_CONTEXT),
+ mEglConfig(nullptr) {
+ EGLBoolean result;
+
+ mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+ if (mEglDisplay == EGL_NO_DISPLAY) {
+ ALOGE("eglGetDisplay failed: %#x", eglGetError());
+ return;
+ }
+
+ EGLint majorVersion, minorVersion;
+ result = eglInitialize(mEglDisplay, &majorVersion, &minorVersion);
+ if (result != EGL_TRUE) {
+ ALOGE("eglInitialize failed: %#x", eglGetError());
+ return;
+ }
+ ALOGV("Initialized EGL v%d.%d", majorVersion, minorVersion);
+
+ EGLint numConfigs = 0;
+ EGLint configAttribs[] = {
+ EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, EGL_RENDERABLE_TYPE,
+ EGL_OPENGL_ES2_BIT, EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8,
+ // no alpha
+ EGL_NONE};
+
+ result =
+ eglChooseConfig(mEglDisplay, configAttribs, &mEglConfig, 1, &numConfigs);
+ if (result != EGL_TRUE) {
+ ALOGE("eglChooseConfig error: %#x", eglGetError());
+ return;
+ }
+
+ EGLint contextAttribs[] = {EGL_CONTEXT_MAJOR_VERSION_KHR, 3, EGL_NONE};
+ mEglContext =
+ eglCreateContext(mEglDisplay, mEglConfig, EGL_NO_CONTEXT, contextAttribs);
+ if (mEglContext == EGL_NO_CONTEXT) {
+ ALOGE("eglCreateContext error: %#x", eglGetError());
+ return;
+ }
+
+ if (!makeCurrent()) {
+ ALOGE(
+ "Failed to set newly initialized EGLContext and EGLDisplay connection "
+ "as current.");
+ } else {
+ ALOGV("EGL successfully initialized.");
+ }
+}
+
+EglDisplayContext::~EglDisplayContext() {
+ if (mEglDisplay != EGL_NO_DISPLAY) {
+ eglTerminate(mEglDisplay);
+ }
+ if (mEglContext != EGL_NO_CONTEXT) {
+ eglDestroyContext(mEglDisplay, mEglContext);
+ }
+ eglReleaseThread();
+}
+
+EGLDisplay EglDisplayContext::getEglDisplay() const {
+ return mEglDisplay;
+}
+
+bool EglDisplayContext::isInitialized() const {
+ return mEglContext != EGL_NO_CONTEXT && mEglDisplay != EGL_NO_DISPLAY;
+}
+
+bool EglDisplayContext::makeCurrent() {
+ if (!eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, mEglContext)) {
+ ALOGE("eglMakeCurrent failed: %#x", eglGetError());
+ return false;
+ }
+ return true;
+}
+
+} // namespace virtualcamera
+} // namespace companion
+} // namespace android
diff --git a/services/camera/virtualcamera/util/EglDisplayContext.h b/services/camera/virtualcamera/util/EglDisplayContext.h
new file mode 100644
index 0000000..402ca3c
--- /dev/null
+++ b/services/camera/virtualcamera/util/EglDisplayContext.h
@@ -0,0 +1,57 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_COMPANION_VIRTUALCAMERA_EGLDISPLAYCONTEXT_H
+#define ANDROID_COMPANION_VIRTUALCAMERA_EGLDISPLAYCONTEXT_H
+
+#include "EGL/egl.h"
+
+namespace android {
+namespace companion {
+namespace virtualcamera {
+
+// Encapsulated EGLDisplay & EGLContext.
+//
+// Upon construction, this object will create and configure new
+// EGLDisplay & EGLContext and will destroy them once it goes
+// out of scope.
+class EglDisplayContext {
+ public:
+ EglDisplayContext();
+ ~EglDisplayContext();
+
+ // Sets EGLDisplay & EGLContext for current thread.
+ //
+ // Returns true on success, false otherwise.
+ bool makeCurrent();
+
+ EGLDisplay getEglDisplay() const;
+
+ // Returns true if this instance encapsulates successfully initialized
+ // EGLDisplay & EGLContext.
+ bool isInitialized() const;
+
+ private:
+ EGLDisplay mEglDisplay;
+ EGLContext mEglContext;
+ EGLConfig mEglConfig;
+};
+
+} // namespace virtualcamera
+} // namespace companion
+} // namespace android
+
+#endif // ANDROID_COMPANION_VIRTUALCAMERA_EGLDISPLAYCONTEXT_H
diff --git a/services/camera/virtualcamera/util/EglFramebuffer.cc b/services/camera/virtualcamera/util/EglFramebuffer.cc
new file mode 100644
index 0000000..acf0122
--- /dev/null
+++ b/services/camera/virtualcamera/util/EglFramebuffer.cc
@@ -0,0 +1,117 @@
+/*
+ * 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.
+ */
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "EglFramebuffer"
+#include "EglFramebuffer.h"
+
+#include "EGL/eglext.h"
+#include "EglUtil.h"
+#include "GLES/gl.h"
+#include "GLES2/gl2.h"
+#include "GLES2/gl2ext.h"
+#include "android/hardware_buffer.h"
+#include "log/log.h"
+
+namespace android {
+namespace companion {
+namespace virtualcamera {
+
+EglFrameBuffer::EglFrameBuffer(EGLDisplay display,
+ std::shared_ptr<AHardwareBuffer> hwBuffer)
+ : mHardwareBuffer(hwBuffer), mEglDisplay(display) {
+ if (hwBuffer == nullptr) {
+ ALOGE("Cannot construct EglFramebuffer from null hwBuffer");
+ return;
+ }
+
+ AHardwareBuffer_Desc hwBufferDesc;
+ AHardwareBuffer_describe(hwBuffer.get(), &hwBufferDesc);
+ mWidth = hwBufferDesc.width;
+ mHeight = hwBufferDesc.height;
+
+ EGLClientBuffer clientBuffer = eglGetNativeClientBufferANDROID(hwBuffer.get());
+ mEglImageKhr = eglCreateImageKHR(display, EGL_NO_CONTEXT,
+ EGL_NATIVE_BUFFER_ANDROID, clientBuffer, 0);
+ if (checkEglError("eglCreateImageKHR")) {
+ return;
+ }
+
+ // Create texture backed by the hardware buffer.
+ glGenTextures(1, &mTextureId);
+ glBindTexture(GL_TEXTURE_EXTERNAL_OES, mTextureId);
+ glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES,
+ (GLeglImageOES)mEglImageKhr);
+ if (checkEglError("configure external texture")) {
+ return;
+ }
+
+ // Create framebuffer backed by the texture.
+ glGenFramebuffers(1, &mFramebufferId);
+ glBindFramebuffer(GL_FRAMEBUFFER, mFramebufferId);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ GL_TEXTURE_EXTERNAL_OES, mTextureId, 0);
+ GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
+ if (status != GL_FRAMEBUFFER_COMPLETE) {
+ ALOGE("Failed to configure framebuffer for texture");
+ return; // false;
+ }
+ if (checkEglError("glCheckFramebufferStatus")) {
+ return; // false;
+ }
+}
+
+EglFrameBuffer::~EglFrameBuffer() {
+ if (mFramebufferId != 0) {
+ glDeleteFramebuffers(1, &mFramebufferId);
+ }
+ if (mTextureId != 0) {
+ glDeleteTextures(1, &mTextureId);
+ }
+ if (mEglImageKhr != EGL_NO_IMAGE_KHR) {
+ eglDestroyImageKHR(mEglDisplay, mEglDisplay);
+ }
+}
+
+bool EglFrameBuffer::beforeDraw() {
+ glBindFramebuffer(GL_FRAMEBUFFER, mFramebufferId);
+ if (checkEglError("glBindFramebuffer")) {
+ return false;
+ }
+
+ glViewport(0, 0, mWidth, mHeight);
+
+ return true;
+}
+
+bool EglFrameBuffer::afterDraw() {
+ glFinish();
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ glBindTexture(GL_TEXTURE_EXTERNAL_OES, 0);
+ return true;
+}
+
+int EglFrameBuffer::getWidth() const {
+ return mWidth;
+}
+
+int EglFrameBuffer::getHeight() const {
+ return mHeight;
+}
+
+} // namespace virtualcamera
+} // namespace companion
+} // namespace android
diff --git a/services/camera/virtualcamera/util/EglFramebuffer.h b/services/camera/virtualcamera/util/EglFramebuffer.h
new file mode 100644
index 0000000..35f85e2
--- /dev/null
+++ b/services/camera/virtualcamera/util/EglFramebuffer.h
@@ -0,0 +1,70 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_COMPANION_VIRTUALCAMERA_EGLFRAMEBUFFER_H
+#define ANDROID_COMPANION_VIRTUALCAMERA_EGLFRAMEBUFFER_H
+
+#define EGL_EGLEXT_PROTOTYPES
+#define GL_GLEXT_PROTOTYPES
+
+#include <memory>
+
+#include "EGL/egl.h"
+#include "EGL/eglext.h"
+#include "GLES/gl.h"
+
+namespace android {
+namespace companion {
+namespace virtualcamera {
+
+// Encapsulates EGL Framebuffer backed by AHardwareBuffer instance.
+//
+// Note that the framebuffer is tied to EGLDisplay connection.
+class EglFrameBuffer {
+ public:
+ EglFrameBuffer(EGLDisplay display, std::shared_ptr<AHardwareBuffer> hwBuffer);
+ virtual ~EglFrameBuffer();
+
+ // Prepare for rendering into the framebuffer.
+ bool beforeDraw();
+
+ // Finishes rendering into the framebuffer.
+ bool afterDraw();
+
+ // Return width of framebuffer (in pixels).
+ int getWidth() const;
+
+ // Return height of framebuffer (in pixels).
+ int getHeight() const;
+
+ private:
+ // Keeping shared_ptr to hardware buffer instance here prevents it from being
+ // freed while tied to EGL framebufer / EGL texture.
+ std::shared_ptr<AHardwareBuffer> mHardwareBuffer;
+ EGLDisplay mEglDisplay;
+ EGLImageKHR mEglImageKhr{EGL_NO_IMAGE_KHR};
+ GLuint mTextureId;
+ GLuint mFramebufferId;
+
+ int mWidth;
+ int mHeight;
+};
+
+} // namespace virtualcamera
+} // namespace companion
+} // namespace android
+
+#endif // ANDROID_COMPANION_VIRTUALCAMERA_EGLFRAMEBUFFER_H
diff --git a/services/camera/virtualcamera/util/EglProgram.cc b/services/camera/virtualcamera/util/EglProgram.cc
new file mode 100644
index 0000000..c468d39
--- /dev/null
+++ b/services/camera/virtualcamera/util/EglProgram.cc
@@ -0,0 +1,292 @@
+/*
+ * 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.
+ */
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "EglProgram"
+#include "EglProgram.h"
+
+#include <array>
+#include <complex>
+
+#include "EglUtil.h"
+#include "GLES/gl.h"
+#include "GLES2/gl2.h"
+#include "GLES2/gl2ext.h"
+#include "log/log.h"
+
+namespace android {
+namespace companion {
+namespace virtualcamera {
+
+namespace {
+
+constexpr char kGlExtYuvTarget[] = "GL_EXT_YUV_target";
+
+constexpr char kIdentityVertexShader[] = R"(
+ attribute vec4 vPosition;
+ void main() {
+ gl_Position = vPosition;
+ })";
+
+constexpr char kJuliaFractalFragmentShader[] = R"(
+ precision mediump float;
+ uniform vec2 uResolution;
+ uniform vec2 uC;
+ uniform vec2 uUV;
+ const float kIter = 64.0;
+
+ vec2 imSq(vec2 n){
+ return vec2(pow(n.x,2.0)-pow(n.y,2.0), 2.0*n.x*n.y);
+ }
+
+ float julia(vec2 n, vec2 c) {
+ vec2 z = n;
+ for (float i=0.0;i<kIter; i+=1.0) {
+ z = imSq(z) + c;
+ if (length(z) > 2.0) return i/kIter;
+ }
+ return kIter;
+ }
+
+ void main() {
+ vec2 uv = vec2(gl_FragCoord.x / uResolution.x - 0.5, gl_FragCoord.y / uResolution.y - 0.5);
+ float juliaVal = julia(uv * 4.0, uC);
+ gl_FragColor = vec4( juliaVal,uUV.x,uUV.y,0.0);
+ })";
+
+constexpr char kExternalTextureVertexShader[] = R"(#version 300 es
+ in vec4 aPosition;
+ in vec2 aTextureCoord;
+ out vec2 vTextureCoord;
+ void main() {
+ gl_Position = aPosition;
+ vTextureCoord = aTextureCoord;
+ })";
+
+constexpr char kExternalTextureFragmentShader[] = R"(#version 300 es
+ #extension GL_OES_EGL_image_external_essl3 : require
+ #extension GL_EXT_YUV_target : require
+ precision mediump float;
+ in vec2 vTextureCoord;
+ out vec4 fragColor;
+ uniform __samplerExternal2DY2YEXT uTexture;
+ void main() {
+ fragColor = texture(uTexture, vTextureCoord);
+ })";
+
+constexpr int kCoordsPerVertex = 3;
+constexpr std::array<float, 12> kSquareCoords{-1.f, 1.0f, 0.0f, // top left
+ -1.f, -1.f, 0.0f, // bottom left
+ 1.0f, -1.f, 0.0f, // bottom right
+ 1.0f, 1.0f, 0.0f}; // top right
+
+constexpr std::array<float, 8> kTextureCoords{0.0f, 1.0f, // top left
+ 0.0f, 0.0f, // bottom left
+ 1.0f, 0.0f, // bottom right
+ 1.0f, 1.0f}; // top right
+
+constexpr std::array<uint8_t, 6> kDrawOrder{0, 1, 2, 0, 2, 3};
+
+GLuint compileShader(GLenum shaderType, const char* src) {
+ GLuint shader = glCreateShader(shaderType);
+ if (shader == 0) {
+ ALOGE("glCreateShader(shaderType=%x) error: %#x",
+ static_cast<unsigned int>(shaderType), glGetError());
+ return 0;
+ }
+
+ glShaderSource(shader, 1, &src, NULL);
+ glCompileShader(shader);
+
+ GLint compiled = 0;
+ glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
+ if (!compiled) {
+ ALOGE("Compile of shader type %d failed", shaderType);
+ GLint infoLen = 0;
+ glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
+ if (infoLen) {
+ char* buf = new char[infoLen];
+ if (buf) {
+ glGetShaderInfoLog(shader, infoLen, NULL, buf);
+ ALOGE("Compile log: %s", buf);
+ delete[] buf;
+ }
+ }
+ glDeleteShader(shader);
+ return 0;
+ }
+ return shader;
+}
+
+} // namespace
+
+EglProgram::~EglProgram() {
+ if (mProgram) {
+ glDeleteProgram(mProgram);
+ }
+}
+
+bool EglProgram::initialize(const char* vertexShaderSrc,
+ const char* fragmentShaderSrc) {
+ GLuint vertexShaderId = compileShader(GL_VERTEX_SHADER, vertexShaderSrc);
+ if (checkEglError("compileShader(vertex)")) {
+ return false;
+ }
+ GLuint fragmentShaderId = compileShader(GL_FRAGMENT_SHADER, fragmentShaderSrc);
+ if (checkEglError("compileShader(fragment)")) {
+ return false;
+ }
+
+ GLuint programId = glCreateProgram();
+
+ glAttachShader(programId, vertexShaderId);
+ glAttachShader(programId, fragmentShaderId);
+ glLinkProgram(programId);
+
+ GLint linkStatus = GL_FALSE;
+ glGetProgramiv(programId, GL_LINK_STATUS, &linkStatus);
+ if (linkStatus != GL_TRUE) {
+ ALOGE("glLinkProgram failed");
+ GLint bufLength = 0;
+ glGetProgramiv(programId, GL_INFO_LOG_LENGTH, &bufLength);
+ if (bufLength) {
+ char* buf = new char[bufLength];
+ if (buf) {
+ glGetProgramInfoLog(programId, bufLength, NULL, buf);
+ ALOGE("Link log: %s", buf);
+ delete[] buf;
+ }
+ }
+ glDeleteProgram(programId);
+ return false;
+ }
+
+ mProgram = programId;
+
+ mIsInitialized = true;
+ return mIsInitialized;
+}
+
+bool EglProgram::isInitialized() const {
+ return mIsInitialized;
+}
+
+EglTestPatternProgram::EglTestPatternProgram() {
+ if (initialize(kIdentityVertexShader, kJuliaFractalFragmentShader)) {
+ ALOGV("Successfully initialized EGL shaders for test pattern program.");
+ } else {
+ ALOGE("Test pattern EGL shader program initialization failed.");
+ }
+}
+
+bool EglTestPatternProgram::draw(int width, int height, int frameNumber) {
+ glViewport(0, 0, static_cast<GLsizei>(width), static_cast<GLsizei>(height));
+ checkEglError("glViewport");
+
+ // Load compiled shader.
+ glUseProgram(mProgram);
+ checkEglError("glUseProgram");
+
+ // Compute point in complex plane corresponding to fractal for this frame number.
+ float time = float(frameNumber) / 120.0f;
+ const std::complex<float> c(std::sin(time) * 0.78f, std::cos(time) * 0.78f);
+
+ // Pass uniform values to the shader.
+ int resolutionHandle = glGetUniformLocation(mProgram, "uResolution");
+ checkEglError("glGetUniformLocation -> uResolution");
+ glUniform2f(resolutionHandle, static_cast<float>(width),
+ static_cast<float>(height));
+ checkEglError("glUniform2f -> uResolution");
+
+ // Pass "C" constant value determining the Julia set to the shader.
+ int cHandle = glGetUniformLocation(mProgram, "uC");
+ glUniform2f(cHandle, c.imag(), c.real());
+
+ // Pass chroma value to the shader.
+ int uvHandle = glGetUniformLocation(mProgram, "uUV");
+ glUniform2f(uvHandle, (c.imag() + 1.f) / 2.f, (c.real() + 1.f) / 2.f);
+
+ // Pass vertex array to draw.
+ int positionHandle = glGetAttribLocation(mProgram, "vPosition");
+ glEnableVertexAttribArray(positionHandle);
+
+ // Prepare the triangle coordinate data.
+ glVertexAttribPointer(positionHandle, kCoordsPerVertex, GL_FLOAT, false,
+ kSquareCoords.size(), kSquareCoords.data());
+
+ // Draw triangle strip forming a square filling the viewport.
+ glDrawElements(GL_TRIANGLES, kDrawOrder.size(), GL_UNSIGNED_BYTE,
+ kDrawOrder.data());
+ if (checkEglError("glDrawElements")) {
+ return false;
+ }
+
+ return true;
+}
+
+EglTextureProgram::EglTextureProgram() {
+ if (!isGlExtensionSupported(kGlExtYuvTarget)) {
+ ALOGE(
+ "Cannot initialize external texture program due to missing "
+ "GL_EXT_YUV_target extension");
+ return;
+ }
+
+ if (initialize(kExternalTextureVertexShader, kExternalTextureFragmentShader)) {
+ ALOGV("Successfully initialized EGL shaders for external texture program.");
+ } else {
+ ALOGE("External texture EGL shader program initialization failed.");
+ }
+}
+
+bool EglTextureProgram::draw(GLuint textureId) {
+ // Load compiled shader.
+ glUseProgram(mProgram);
+ if (checkEglError("glUseProgram")) {
+ return false;
+ }
+
+ // Pass vertex array to the shader.
+ int positionHandle = glGetAttribLocation(mProgram, "aPosition");
+ glEnableVertexAttribArray(positionHandle);
+ glVertexAttribPointer(positionHandle, kCoordsPerVertex, GL_FLOAT, false,
+ kSquareCoords.size(), kSquareCoords.data());
+
+ // Pass texture coordinates corresponding to vertex array to the shader.
+ int textureCoordHandle = glGetAttribLocation(mProgram, "aTextureCoord");
+ glEnableVertexAttribArray(textureCoordHandle);
+ glVertexAttribPointer(textureCoordHandle, 2, GL_FLOAT, false,
+ kTextureCoords.size(), kTextureCoords.data());
+
+ // Configure texture for the shader.
+ int textureHandle = glGetUniformLocation(mProgram, "uTexture");
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_EXTERNAL_OES, textureId);
+ glUniform1i(textureHandle, 0);
+
+ // Draw triangle strip forming a square filling the viewport.
+ glDrawElements(GL_TRIANGLES, kDrawOrder.size(), GL_UNSIGNED_BYTE,
+ kDrawOrder.data());
+ if (checkEglError("glDrawElements")) {
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace virtualcamera
+} // namespace companion
+} // namespace android
diff --git a/services/camera/virtualcamera/util/EglProgram.h b/services/camera/virtualcamera/util/EglProgram.h
new file mode 100644
index 0000000..8e394e7
--- /dev/null
+++ b/services/camera/virtualcamera/util/EglProgram.h
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_COMPANION_VIRTUALCAMERA_EGLPROGRAM_H
+#define ANDROID_COMPANION_VIRTUALCAMERA_EGLPROGRAM_H
+
+#include "GLES/gl.h"
+
+namespace android {
+namespace companion {
+namespace virtualcamera {
+
+// Base class for EGL Shader programs representation.
+class EglProgram {
+ public:
+ virtual ~EglProgram();
+
+ // Returns whether the EGL Program was successfully compiled and linked.
+ bool isInitialized() const;
+
+ protected:
+ // Compile & link program from the vertex & fragment shader source.
+ bool initialize(const char* vertexShaderSrc, const char* fragmentShaderSrc);
+ GLuint mProgram;
+ // Whether the EGL Program was successfully compiled and linked.
+ bool mIsInitialized = false;
+};
+
+// Shader program to draw Julia Set test pattern.
+class EglTestPatternProgram : public EglProgram {
+ public:
+ EglTestPatternProgram();
+
+ bool draw(int width, int height, int frameNumber);
+};
+
+// Shader program to draw texture.
+//
+// Shader stretches the texture over the viewport (if the texture is not same
+// aspect ratio as viewport, it will be deformed).
+//
+// TODO(b/301023410) Add support for translation / cropping.
+class EglTextureProgram : public EglProgram {
+ public:
+ EglTextureProgram();
+
+ bool draw(GLuint textureId);
+};
+
+} // namespace virtualcamera
+} // namespace companion
+} // namespace android
+
+#endif // ANDROID_COMPANION_VIRTUALCAMERA_EGLPROGRAM_H
diff --git a/services/camera/virtualcamera/util/EglSurfaceTexture.cc b/services/camera/virtualcamera/util/EglSurfaceTexture.cc
new file mode 100644
index 0000000..ac92bc4
--- /dev/null
+++ b/services/camera/virtualcamera/util/EglSurfaceTexture.cc
@@ -0,0 +1,82 @@
+/*
+ * 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.
+ */
+
+// #define LOG_NDEBUG 0
+#include <memory>
+#define LOG_TAG "EglSurfaceTexture"
+
+#include <cstdint>
+
+#include "EglSurfaceTexture.h"
+#include "EglUtil.h"
+#include "GLES/gl.h"
+#include "gui/BufferQueue.h"
+#include "gui/GLConsumer.h"
+#include "gui/IGraphicBufferProducer.h"
+#include "hardware/gralloc.h"
+
+namespace android {
+namespace companion {
+namespace virtualcamera {
+
+EglSurfaceTexture::EglSurfaceTexture(const uint32_t width, const uint32_t height)
+ : mWidth(width), mHeight(height) {
+ glGenTextures(1, &mTextureId);
+ if (checkEglError("EglSurfaceTexture(): glGenTextures")) {
+ ALOGE("Failed to generate texture");
+ return;
+ }
+ BufferQueue::createBufferQueue(&mBufferProducer, &mBufferConsumer);
+ mGlConsumer = sp<GLConsumer>::make(
+ mBufferConsumer, mTextureId, GLConsumer::TEXTURE_EXTERNAL, false, false);
+ mGlConsumer->setName(String8("VirtualCameraEglSurfaceTexture"));
+ mGlConsumer->setDefaultBufferSize(mWidth, mHeight);
+ mGlConsumer->setConsumerUsageBits(GRALLOC_USAGE_HW_TEXTURE);
+ mGlConsumer->setDefaultBufferFormat(AHARDWAREBUFFER_FORMAT_Y8Cb8Cr8_420);
+
+ mSurface = sp<Surface>::make(mBufferProducer);
+}
+
+EglSurfaceTexture::~EglSurfaceTexture() {
+ if (mTextureId != 0) {
+ glDeleteTextures(1, &mTextureId);
+ }
+}
+
+sp<Surface> EglSurfaceTexture::getSurface() {
+ return mSurface;
+}
+
+sp<GraphicBuffer> EglSurfaceTexture::getCurrentBuffer() {
+ return mGlConsumer->getCurrentBuffer();
+}
+
+GLuint EglSurfaceTexture::updateTexture() {
+ mGlConsumer->updateTexImage();
+ return mTextureId;
+}
+
+uint32_t EglSurfaceTexture::getWidth() const {
+ return mWidth;
+}
+
+uint32_t EglSurfaceTexture::getHeight() const {
+ return mHeight;
+}
+
+} // namespace virtualcamera
+} // namespace companion
+} // namespace android
diff --git a/services/camera/virtualcamera/util/EglSurfaceTexture.h b/services/camera/virtualcamera/util/EglSurfaceTexture.h
new file mode 100644
index 0000000..14dc7d6
--- /dev/null
+++ b/services/camera/virtualcamera/util/EglSurfaceTexture.h
@@ -0,0 +1,74 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_COMPANION_VIRTUALCAMERA_EGLSURFACETEXTURE_H
+#define ANDROID_COMPANION_VIRTUALCAMERA_EGLSURFACETEXTURE_H
+
+#include <cstdint>
+
+#include "GLES/gl.h"
+#include "gui/Surface.h"
+#include "utils/RefBase.h"
+
+namespace android {
+
+class IGraphicBufferProducer;
+class IGraphicBufferConsumer;
+class GLConsumer;
+
+namespace companion {
+namespace virtualcamera {
+
+// Encapsulates GLConsumer & Surface for rendering into EGL texture.
+class EglSurfaceTexture {
+ public:
+ // Create new EGL Texture with specified size.
+ EglSurfaceTexture(uint32_t width, uint32_t height);
+ ~EglSurfaceTexture();
+
+ // Get Surface backing up the texture.
+ sp<Surface> getSurface();
+
+ // Get GraphicBuffer backing the current texture.
+ sp<GraphicBuffer> getCurrentBuffer();
+
+ // Get width of surface / texture.
+ uint32_t getWidth() const;
+
+ // Get height of surface / texture.
+ uint32_t getHeight() const;
+
+ // Update the texture with the most recent submitted buffer.
+ // Most be called on thread with EGL context.
+ //
+ // Returns EGL texture id of the texture.
+ GLuint updateTexture();
+
+ private:
+ sp<IGraphicBufferProducer> mBufferProducer;
+ sp<IGraphicBufferConsumer> mBufferConsumer;
+ sp<GLConsumer> mGlConsumer;
+ sp<Surface> mSurface;
+ GLuint mTextureId;
+ const uint32_t mWidth;
+ const uint32_t mHeight;
+};
+
+} // namespace virtualcamera
+} // namespace companion
+} // namespace android
+
+#endif // ANDROID_COMPANION_VIRTUALCAMERA_EGLSURFACETEXTURE_H
diff --git a/services/camera/virtualcamera/util/EglUtil.cc b/services/camera/virtualcamera/util/EglUtil.cc
new file mode 100644
index 0000000..481d8f0
--- /dev/null
+++ b/services/camera/virtualcamera/util/EglUtil.cc
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "EglUtil"
+#include "EglUtil.h"
+
+#include <cstring>
+
+#include "GLES/gl.h"
+#include "log/log.h"
+
+namespace android {
+namespace companion {
+namespace virtualcamera {
+
+bool checkEglError(const char* operation) {
+ GLenum err = glGetError();
+ if (err == GL_NO_ERROR) {
+ return false;
+ }
+ ALOGE("%s failed: %d", operation, err);
+ return true;
+}
+
+bool isGlExtensionSupported(const char* extension) {
+ const char* extensions =
+ reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS));
+ if (extension == nullptr || extensions == nullptr) {
+ return false;
+ }
+ return strstr(extensions, extension) != nullptr;
+}
+
+} // namespace virtualcamera
+} // namespace companion
+} // namespace android
diff --git a/services/camera/virtualcamera/util/EglUtil.h b/services/camera/virtualcamera/util/EglUtil.h
new file mode 100644
index 0000000..71640e3
--- /dev/null
+++ b/services/camera/virtualcamera/util/EglUtil.h
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_COMPANION_VIRTUALCAMERA_EGLUTIL_H
+#define ANDROID_COMPANION_VIRTUALCAMERA_EGLUTIL_H
+
+namespace android {
+namespace companion {
+namespace virtualcamera {
+
+// Returns true if the EGL is in an error state and logs the error.
+bool checkEglError(const char* operation = "EGL operation");
+
+// Returns true if the GL extension is supported, false otherwise.
+bool isGlExtensionSupported(const char* extension);
+
+} // namespace virtualcamera
+} // namespace companion
+} // namespace android
+
+#endif // ANDROID_COMPANION_VIRTUALCAMERA_EGLUTIL_H
diff --git a/services/camera/virtualcamera/util/JpegUtil.cc b/services/camera/virtualcamera/util/JpegUtil.cc
new file mode 100644
index 0000000..6f10376
--- /dev/null
+++ b/services/camera/virtualcamera/util/JpegUtil.cc
@@ -0,0 +1,228 @@
+/*
+ * 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.
+ */
+// #define LOG_NDEBUG 0
+#define LOG_TAG "JpegUtil"
+#include "JpegUtil.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+
+#include "android/hardware_buffer.h"
+#include "jpeglib.h"
+#include "log/log.h"
+#include "ui/GraphicBuffer.h"
+#include "ui/GraphicBufferMapper.h"
+#include "utils/Errors.h"
+
+namespace android {
+namespace companion {
+namespace virtualcamera {
+namespace {
+
+constexpr int kJpegQuality = 80;
+
+class LibJpegContext {
+ public:
+ LibJpegContext(int width, int height, const android_ycbcr& ycbcr,
+ const size_t outBufferSize, void* outBuffer)
+ : mYCbCr(ycbcr),
+ mWidth(width),
+ mHeight(height),
+ mDstBufferSize(outBufferSize),
+ mDstBuffer(outBuffer) {
+ // Initialize error handling for libjpeg.
+ // We call jpeg_std_error to initialize standard error
+ // handling and then override:
+ // * output_message not to print to stderr, but use ALOG instead.
+ // * error_exit not to terminate the process, but failure flag instead.
+ mCompressStruct.err = jpeg_std_error(&mErrorMgr);
+ mCompressStruct.err->output_message = onOutputError;
+ mCompressStruct.err->error_exit = onErrorExit;
+ jpeg_create_compress(&mCompressStruct);
+
+ // Configure input image parameters.
+ mCompressStruct.image_width = width;
+ mCompressStruct.image_height = height;
+ mCompressStruct.input_components = 3;
+ mCompressStruct.in_color_space = JCS_YCbCr;
+ // We pass pointer to this instance as a client data so we can
+ // access this object from the static callbacks invoked by
+ // libjpeg.
+ mCompressStruct.client_data = this;
+
+ // Configure destination manager for libjpeg.
+ mCompressStruct.dest = &mDestinationMgr;
+ mDestinationMgr.init_destination = onInitDestination;
+ mDestinationMgr.empty_output_buffer = onEmptyOutputBuffer;
+ mDestinationMgr.term_destination = onTermDestination;
+ mDestinationMgr.next_output_byte = reinterpret_cast<JOCTET*>(mDstBuffer);
+ mDestinationMgr.free_in_buffer = mDstBufferSize;
+
+ // Configure everything else based on input configuration above.
+ jpeg_set_defaults(&mCompressStruct);
+
+ // Set quality and colorspace.
+ jpeg_set_quality(&mCompressStruct, kJpegQuality, 1);
+ jpeg_set_colorspace(&mCompressStruct, JCS_YCbCr);
+
+ // Configure RAW input mode - this let's libjpeg know we're providing raw,
+ // subsampled YCbCr data.
+ mCompressStruct.raw_data_in = 1;
+ mCompressStruct.dct_method = JDCT_IFAST;
+
+ // Configure sampling factors - this states that every 2 Y
+ // samples share 1 Cb & 1 Cr component vertically & horizontally (YUV420).
+ mCompressStruct.comp_info[0].h_samp_factor = 2;
+ mCompressStruct.comp_info[0].v_samp_factor = 2;
+ mCompressStruct.comp_info[1].h_samp_factor = 1;
+ mCompressStruct.comp_info[1].v_samp_factor = 1;
+ mCompressStruct.comp_info[2].h_samp_factor = 1;
+ mCompressStruct.comp_info[2].v_samp_factor = 1;
+ }
+
+ bool compress() {
+ // Prepare arrays of pointers to scanlines of each plane.
+ std::vector<JSAMPROW> yLines(mHeight);
+ std::vector<JSAMPROW> cbLines(mHeight / 2);
+ std::vector<JSAMPROW> crLines(mHeight / 2);
+
+ uint8_t* y = static_cast<uint8_t*>(mYCbCr.y);
+ uint8_t* cb = static_cast<uint8_t*>(mYCbCr.cb);
+ uint8_t* cr = static_cast<uint8_t*>(mYCbCr.cr);
+
+ // Since UV samples might be interleaved (semiplanar) we need to copy
+ // them to separate planes, since libjpeg doesn't directly
+ // support processing semiplanar YUV.
+ const int c_samples = (mWidth / 2) * (mHeight / 2);
+ std::vector<uint8_t> cb_plane(c_samples);
+ std::vector<uint8_t> cr_plane(c_samples);
+
+ // TODO(b/301023410) - Use libyuv or ARM SIMD for "unzipping" the data.
+ for (int i = 0; i < c_samples; ++i) {
+ cb_plane[i] = *cb;
+ cr_plane[i] = *cr;
+ cb += mYCbCr.chroma_step;
+ cr += mYCbCr.chroma_step;
+ }
+
+ // Collect pointers to individual scanline of each plane.
+ for (int i = 0; i < mHeight; ++i) {
+ yLines[i] = y + i * mYCbCr.ystride;
+ }
+ for (int i = 0; i < (mHeight / 2); ++i) {
+ cbLines[i] = cb_plane.data() + i * (mWidth / 2);
+ crLines[i] = cr_plane.data() + i * (mWidth / 2);
+ }
+
+ // Perform actual compression.
+ jpeg_start_compress(&mCompressStruct, TRUE);
+
+ while (mCompressStruct.next_scanline < mCompressStruct.image_height) {
+ const uint32_t batchSize = DCTSIZE * 2;
+ const uint32_t nl = mCompressStruct.next_scanline;
+ JSAMPARRAY planes[3]{&yLines[nl], &cbLines[nl / 2], &crLines[nl / 2]};
+
+ uint32_t done = jpeg_write_raw_data(&mCompressStruct, planes, batchSize);
+
+ if (done != batchSize) {
+ ALOGE("%s: compressed %u lines, expected %u (total %u/%u)",
+ __FUNCTION__, done, batchSize, mCompressStruct.next_scanline,
+ mCompressStruct.image_height);
+ return false;
+ }
+ }
+ jpeg_finish_compress(&mCompressStruct);
+ return mSuccess;
+ }
+
+ private:
+ void setSuccess(const boolean success) {
+ mSuccess = success;
+ }
+
+ void initDestination() {
+ mDestinationMgr.next_output_byte = reinterpret_cast<JOCTET*>(mDstBuffer);
+ mDestinationMgr.free_in_buffer = mDstBufferSize;
+ ALOGV("%s:%d jpeg start: %p [%zu]", __FUNCTION__, __LINE__, mDstBuffer,
+ mDstBufferSize);
+ }
+
+ void termDestination() {
+ mEncodedSize = mDstBufferSize - mDestinationMgr.free_in_buffer;
+ ALOGV("%s:%d Done with jpeg: %zu", __FUNCTION__, __LINE__, mEncodedSize);
+ }
+
+ // === libjpeg callbacks below ===
+
+ static void onOutputError(j_common_ptr cinfo) {
+ char buffer[JMSG_LENGTH_MAX];
+ (*cinfo->err->format_message)(cinfo, buffer);
+ ALOGE("libjpeg error: %s", buffer);
+ };
+
+ static void onErrorExit(j_common_ptr cinfo) {
+ static_cast<LibJpegContext*>(cinfo->client_data)->setSuccess(false);
+ };
+
+ static void onInitDestination(j_compress_ptr cinfo) {
+ static_cast<LibJpegContext*>(cinfo->client_data)->initDestination();
+ }
+
+ static int onEmptyOutputBuffer(j_compress_ptr cinfo __unused) {
+ ALOGV("%s:%d Out of buffer", __FUNCTION__, __LINE__);
+ return 0;
+ }
+
+ static void onTermDestination(j_compress_ptr cinfo) {
+ static_cast<LibJpegContext*>(cinfo->client_data)->termDestination();
+ }
+
+ jpeg_compress_struct mCompressStruct;
+ jpeg_error_mgr mErrorMgr;
+ jpeg_destination_mgr mDestinationMgr;
+
+ // Layout of the input image.
+ android_ycbcr mYCbCr;
+
+ // Dimensions of the input image.
+ int mWidth;
+ int mHeight;
+
+ // Destination buffer and it's capacity.
+ size_t mDstBufferSize;
+ void* mDstBuffer;
+
+ // This will be set to size of encoded data
+ // written to the outputBuffer when encoding finishes.
+ size_t mEncodedSize;
+ // Set to true/false based on whether the encoding
+ // was successful.
+ boolean mSuccess = true;
+};
+
+} // namespace
+
+// Returns true if the EGL is in an error state and logs the error.
+bool compressJpeg(int width, int height, const android_ycbcr& ycbcr,
+ size_t outBufferSize, void* outBuffer) {
+ return LibJpegContext(width, height, ycbcr, outBufferSize, outBuffer)
+ .compress();
+}
+
+} // namespace virtualcamera
+} // namespace companion
+} // namespace android
diff --git a/services/camera/virtualcamera/util/JpegUtil.h b/services/camera/virtualcamera/util/JpegUtil.h
new file mode 100644
index 0000000..8bff008
--- /dev/null
+++ b/services/camera/virtualcamera/util/JpegUtil.h
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_COMPANION_VIRTUALCAMERA_JPEGUTIL_H
+#define ANDROID_COMPANION_VIRTUALCAMERA_JPEGUTIL_H
+
+#include <memory>
+
+#include "android/hardware_buffer.h"
+#include "system/graphics.h"
+
+namespace android {
+namespace companion {
+namespace virtualcamera {
+
+// Jpeg-compress image into the output buffer.
+bool compressJpeg(int width, int height, const android_ycbcr& ycbcr,
+ size_t outBufferSize, void* outBuffer);
+
+} // namespace virtualcamera
+} // namespace companion
+} // namespace android
+
+#endif // ANDROID_COMPANION_VIRTUALCAMERA_JPEGUTIL_H
diff --git a/services/camera/virtualcamera/util/MetadataBuilder.cc b/services/camera/virtualcamera/util/MetadataBuilder.cc
new file mode 100644
index 0000000..fb06e31
--- /dev/null
+++ b/services/camera/virtualcamera/util/MetadataBuilder.cc
@@ -0,0 +1,325 @@
+/*
+ * 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.
+ */
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "MetadataBuilder"
+
+#include "MetadataBuilder.h"
+
+#include <algorithm>
+#include <cstdint>
+#include <iterator>
+#include <memory>
+#include <utility>
+#include <variant>
+#include <vector>
+
+#include "CameraMetadata.h"
+#include "aidl/android/hardware/camera/device/CameraMetadata.h"
+#include "log/log.h"
+#include "system/camera_metadata.h"
+#include "utils/Errors.h"
+
+namespace android {
+namespace companion {
+namespace virtualcamera {
+
+namespace {
+
+using ::android::hardware::camera::common::helper::CameraMetadata;
+
+template <typename To, typename From>
+std::vector<To> convertTo(const std::vector<From>& from) {
+ std::vector<To> to;
+ to.reserve(from.size());
+ std::transform(from.begin(), from.end(), std::back_inserter(to),
+ [](const From& x) { return static_cast<To>(x); });
+ return to;
+}
+
+} // namespace
+
+MetadataBuilder& MetadataBuilder::setSupportedHardwareLevel(
+ camera_metadata_enum_android_info_supported_hardware_level_t hwLevel) {
+ mEntryMap[ANDROID_INFO_SUPPORTED_HARDWARE_LEVEL] =
+ std::vector<uint8_t>({static_cast<uint8_t>(hwLevel)});
+ return *this;
+}
+
+MetadataBuilder& MetadataBuilder::setFlashAvailable(bool flashAvailable) {
+ const uint8_t metadataVal = flashAvailable
+ ? ANDROID_FLASH_INFO_AVAILABLE_TRUE
+ : ANDROID_FLASH_INFO_AVAILABLE_FALSE;
+ mEntryMap[ANDROID_FLASH_INFO_AVAILABLE] = std::vector<uint8_t>({metadataVal});
+ return *this;
+}
+
+MetadataBuilder& MetadataBuilder::setLensFacing(
+ camera_metadata_enum_android_lens_facing lensFacing) {
+ mEntryMap[ANDROID_LENS_FACING] =
+ std::vector<uint8_t>({static_cast<uint8_t>(lensFacing)});
+ return *this;
+}
+
+MetadataBuilder& MetadataBuilder::setSensorOrientation(int32_t sensorOrientation) {
+ mEntryMap[ANDROID_SENSOR_ORIENTATION] =
+ std::vector<int32_t>({sensorOrientation});
+ return *this;
+}
+
+MetadataBuilder& MetadataBuilder::setSensorTimestamp(
+ std::chrono::nanoseconds timestamp) {
+ mEntryMap[ANDROID_SENSOR_TIMESTAMP] =
+ std::vector<int64_t>({timestamp.count()});
+ return *this;
+}
+
+MetadataBuilder& MetadataBuilder::setAvailableFaceDetectModes(
+ const std::vector<camera_metadata_enum_android_statistics_face_detect_mode_t>&
+ faceDetectModes) {
+ mEntryMap[ANDROID_STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES] =
+ convertTo<uint8_t>(faceDetectModes);
+ return *this;
+}
+
+MetadataBuilder& MetadataBuilder::setControlAfAvailableModes(
+ const std::vector<camera_metadata_enum_android_control_af_mode_t>&
+ availableModes) {
+ mEntryMap[ANDROID_CONTROL_AF_AVAILABLE_MODES] =
+ convertTo<uint8_t>(availableModes);
+ return *this;
+}
+
+MetadataBuilder& MetadataBuilder::setControlAfMode(
+ const camera_metadata_enum_android_control_af_mode_t mode) {
+ mEntryMap[ANDROID_CONTROL_AF_MODE] =
+ std::vector<uint8_t>({static_cast<uint8_t>(mode)});
+ return *this;
+}
+
+MetadataBuilder& MetadataBuilder::setControlAeAvailableFpsRange(
+ const int32_t minFps, const int32_t maxFps) {
+ mEntryMap[ANDROID_CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES] =
+ std::vector<int32_t>({minFps, maxFps});
+ return *this;
+}
+
+MetadataBuilder& MetadataBuilder::setControlMaxRegions(int32_t maxAeRegions,
+ int32_t maxAwbRegions,
+ int32_t maxAfRegions) {
+ mEntryMap[ANDROID_CONTROL_MAX_REGIONS] =
+ std::vector<int32_t>({maxAeRegions, maxAwbRegions, maxAfRegions});
+ return *this;
+}
+
+MetadataBuilder& MetadataBuilder::setControlAeRegions(
+ const std::vector<ControlRegion>& aeRegions) {
+ std::vector<int32_t> regions;
+ regions.reserve(5 * aeRegions.size());
+ for (const ControlRegion& region : aeRegions) {
+ regions.push_back(region.x0);
+ regions.push_back(region.y0);
+ regions.push_back(region.x1);
+ regions.push_back(region.y1);
+ regions.push_back(region.weight);
+ }
+ mEntryMap[ANDROID_CONTROL_AE_REGIONS] = std::move(regions);
+ return *this;
+}
+
+MetadataBuilder& MetadataBuilder::setControlAfRegions(
+ const std::vector<ControlRegion>& afRegions) {
+ std::vector<int32_t> regions;
+ regions.reserve(5 * afRegions.size());
+ for (const ControlRegion& region : afRegions) {
+ regions.push_back(region.x0);
+ regions.push_back(region.y0);
+ regions.push_back(region.x1);
+ regions.push_back(region.y1);
+ regions.push_back(region.weight);
+ }
+ mEntryMap[ANDROID_CONTROL_AF_REGIONS] = std::move(regions);
+ return *this;
+}
+
+MetadataBuilder& MetadataBuilder::setControlAwbRegions(
+ const std::vector<ControlRegion>& awbRegions) {
+ std::vector<int32_t> regions;
+ regions.reserve(5 * awbRegions.size());
+ for (const ControlRegion& region : awbRegions) {
+ regions.push_back(region.x0);
+ regions.push_back(region.y0);
+ regions.push_back(region.x1);
+ regions.push_back(region.y1);
+ regions.push_back(region.weight);
+ }
+ mEntryMap[ANDROID_CONTROL_AWB_REGIONS] = std::move(regions);
+ return *this;
+}
+
+MetadataBuilder& MetadataBuilder::setControlCaptureIntent(
+ const camera_metadata_enum_android_control_capture_intent_t intent) {
+ mEntryMap[ANDROID_CONTROL_CAPTURE_INTENT] =
+ std::vector<uint8_t>({static_cast<uint8_t>(intent)});
+ return *this;
+}
+
+MetadataBuilder& MetadataBuilder::setMaxJpegSize(const int32_t size) {
+ mEntryMap[ANDROID_JPEG_MAX_SIZE] = std::vector<int32_t>({size});
+ return *this;
+}
+
+MetadataBuilder& MetadataBuilder::setAvailableOutputStreamConfigurations(
+ const std::vector<StreamConfiguration>& streamConfigurations) {
+ std::vector<int32_t> metadataStreamConfigs;
+ std::vector<int64_t> metadataMinFrameDurations;
+ std::vector<int64_t> metadataStallDurations;
+ metadataStreamConfigs.reserve(streamConfigurations.size());
+ metadataMinFrameDurations.reserve(streamConfigurations.size());
+ metadataStallDurations.reserve(streamConfigurations.size());
+
+ for (const auto& config : streamConfigurations) {
+ metadataStreamConfigs.push_back(config.format);
+ metadataStreamConfigs.push_back(config.width);
+ metadataStreamConfigs.push_back(config.height);
+ metadataStreamConfigs.push_back(
+ ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT);
+
+ metadataMinFrameDurations.push_back(config.format);
+ metadataMinFrameDurations.push_back(config.width);
+ metadataMinFrameDurations.push_back(config.height);
+ metadataMinFrameDurations.push_back(config.minFrameDuration.count());
+
+ metadataStallDurations.push_back(config.format);
+ metadataStallDurations.push_back(config.width);
+ metadataStallDurations.push_back(config.height);
+ metadataStallDurations.push_back(config.minStallDuration.count());
+ }
+
+ mEntryMap[ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS] =
+ metadataStreamConfigs;
+ mEntryMap[ANDROID_SCALER_AVAILABLE_MIN_FRAME_DURATIONS] =
+ metadataMinFrameDurations;
+ mEntryMap[ANDROID_SCALER_AVAILABLE_STALL_DURATIONS] =
+ metadataMinFrameDurations;
+
+ return *this;
+}
+
+MetadataBuilder& MetadataBuilder::setAvailableMaxDigitalZoom(const float maxZoom) {
+ mEntryMap[ANDROID_SCALER_AVAILABLE_MAX_DIGITAL_ZOOM] =
+ std::vector<float>(maxZoom);
+ return *this;
+}
+
+MetadataBuilder& MetadataBuilder::setSensorActiveArraySize(int x0, int y0,
+ int x1, int y1) {
+ mEntryMap[ANDROID_SENSOR_INFO_ACTIVE_ARRAY_SIZE] =
+ std::vector<int32_t>({x0, y0, x1, y1});
+ return *this;
+}
+
+MetadataBuilder& MetadataBuilder::setControlAeCompensationRange(int32_t min,
+ int32_t max) {
+ mEntryMap[ANDROID_CONTROL_AE_COMPENSATION_RANGE] =
+ std::vector<int32_t>({min, max});
+ return *this;
+}
+
+MetadataBuilder& MetadataBuilder::setControlAeCompensationStep(
+ const camera_metadata_rational step) {
+ mEntryMap[ANDROID_CONTROL_AE_COMPENSATION_STEP] =
+ std::vector<camera_metadata_rational>({step});
+ return *this;
+}
+
+MetadataBuilder& MetadataBuilder::setAvailableRequestKeys(
+ const std::vector<int32_t>& keys) {
+ mEntryMap[ANDROID_REQUEST_AVAILABLE_REQUEST_KEYS] = keys;
+ return *this;
+}
+
+MetadataBuilder& MetadataBuilder::setAvailableResultKeys(
+ const std::vector<int32_t>& keys) {
+ mEntryMap[ANDROID_REQUEST_AVAILABLE_RESULT_KEYS] = keys;
+ return *this;
+}
+
+MetadataBuilder& MetadataBuilder::setAvailableCapabilities(
+ const std::vector<camera_metadata_enum_android_request_available_capabilities_t>&
+ capabilities) {
+ mEntryMap[ANDROID_REQUEST_AVAILABLE_CAPABILITIES] =
+ convertTo<uint8_t>(capabilities);
+ return *this;
+}
+
+MetadataBuilder& MetadataBuilder::setAvailableCharacteristicKeys(
+ const std::vector<camera_metadata_tag_t>& keys) {
+ mEntryMap[ANDROID_REQUEST_AVAILABLE_CHARACTERISTICS_KEYS] =
+ convertTo<int32_t>(keys);
+ return *this;
+}
+
+MetadataBuilder& MetadataBuilder::setAvailableCharacteristicKeys() {
+ std::vector<camera_metadata_tag_t> availableKeys;
+ availableKeys.reserve(mEntryMap.size());
+ for (const auto& [key, _] : mEntryMap) {
+ if (key != ANDROID_REQUEST_AVAILABLE_CHARACTERISTICS_KEYS) {
+ availableKeys.push_back(key);
+ }
+ }
+ setAvailableCharacteristicKeys(availableKeys);
+ return *this;
+}
+
+std::unique_ptr<aidl::android::hardware::camera::device::CameraMetadata>
+MetadataBuilder::build() const {
+ CameraMetadata metadataHelper;
+ for (const auto& entry : mEntryMap) {
+ status_t ret = std::visit(
+ [&](auto&& arg) {
+ return metadataHelper.update(entry.first, arg.data(), arg.size());
+ },
+ entry.second);
+ if (ret != NO_ERROR) {
+ ALOGE("Failed to update metadata with key %d - %s: %s", entry.first,
+ get_camera_metadata_tag_name(entry.first),
+ ::android::statusToString(ret).c_str());
+ return nullptr;
+ }
+ }
+
+ const camera_metadata_t* metadata = metadataHelper.getAndLock();
+ if (metadata == nullptr) {
+ ALOGE(
+ "Failure when constructing metadata -> CameraMetadata helper returned "
+ "nullptr");
+ return nullptr;
+ }
+
+ auto aidlMetadata =
+ std::make_unique<aidl::android::hardware::camera::device::CameraMetadata>();
+ const uint8_t* data_ptr = reinterpret_cast<const uint8_t*>(metadata);
+ aidlMetadata->metadata.assign(data_ptr,
+ data_ptr + get_camera_metadata_size(metadata));
+ metadataHelper.unlock(metadata);
+
+ return aidlMetadata;
+}
+
+} // namespace virtualcamera
+} // namespace companion
+} // namespace android
diff --git a/services/camera/virtualcamera/util/MetadataBuilder.h b/services/camera/virtualcamera/util/MetadataBuilder.h
new file mode 100644
index 0000000..25c931c
--- /dev/null
+++ b/services/camera/virtualcamera/util/MetadataBuilder.h
@@ -0,0 +1,196 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_COMPANION_VIRTUALCAMERA_METADATABUILDER_H
+#define ANDROID_COMPANION_VIRTUALCAMERA_METADATABUILDER_H
+
+#include <chrono>
+#include <cstdint>
+#include <map>
+#include <memory>
+#include <variant>
+#include <vector>
+
+#include "aidl/android/hardware/camera/device/CameraMetadata.h"
+#include "system/camera_metadata.h"
+
+namespace android {
+namespace companion {
+namespace virtualcamera {
+
+// Convenience builder for the
+// aidl::android::hardware::camera::device::CameraMetadata.
+//
+// Calling the same builder setter multiple will overwrite the value.
+// This class is not thread-safe.
+class MetadataBuilder {
+ public:
+ struct StreamConfiguration {
+ int32_t width = 0;
+ int32_t height = 0;
+ int32_t format = 0;
+ // Minimal frame duration - corresponds to maximal FPS for given format.
+ // See ANDROID_SCALER_AVAILABLE_MIN_FRAME_DURATIONS in CameraMetadataTag.aidl.
+ std::chrono::nanoseconds minFrameDuration{std::chrono::seconds(1) / 30};
+ // Minimal stall duration.
+ // See ANDROID_SCALER_AVAILABLE_STALL_DURATIONS in CameraMetadataTag.aidl.
+ std::chrono::nanoseconds minStallDuration{0};
+ };
+
+ struct ControlRegion {
+ int32_t x0 = 0;
+ int32_t y0 = 0;
+ int32_t x1 = 0;
+ int32_t y1 = 0;
+ int32_t weight = 0;
+ };
+
+ MetadataBuilder() = default;
+ ~MetadataBuilder() = default;
+
+ // See ANDROID_INFO_SUPPORTED_HARDWARE_LEVEL in CameraMetadataTag.aidl.
+ MetadataBuilder& setSupportedHardwareLevel(
+ camera_metadata_enum_android_info_supported_hardware_level_t hwLevel);
+
+ // Whether this camera device has a flash unit
+ // See ANDROID_FLASH_INFO_AVAILABLE in CameraMetadataTag.aidl.
+ MetadataBuilder& setFlashAvailable(bool flashAvailable);
+
+ // See ANDROID_LENS_FACING in CameraMetadataTag.aidl.
+ MetadataBuilder& setLensFacing(
+ camera_metadata_enum_android_lens_facing lensFacing);
+
+ // See ANDROID_SENSOR_ORIENTATION in CameraMetadataTag.aidl.
+ MetadataBuilder& setSensorOrientation(int32_t sensorOrientation);
+
+ // Time at start of exposure of first row of the image
+ // sensor active array, in nanoseconds.
+ //
+ // See ANDROID_SENSOR_TIMESTAMP in CameraMetadataTag.aidl.
+ MetadataBuilder& setSensorTimestamp(std::chrono::nanoseconds timestamp);
+
+ // See ANDROID_SENSOR_INFO_ACTIVE_ARRAY_SIZE in CameraMetadataTag.aidl.
+ MetadataBuilder& setSensorActiveArraySize(int x0, int y0, int x1, int y1);
+
+ // See ANDROID_STATISTICS_FACE_DETECT_MODE in CameraMetadataTag.aidl.
+ MetadataBuilder& setAvailableFaceDetectModes(
+ const std::vector<camera_metadata_enum_android_statistics_face_detect_mode_t>&
+ faceDetectMode);
+
+ // Sets available stream configurations along with corresponding minimal frame
+ // durations (corresponding to max fps) and stall durations.
+ //
+ // See ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS,
+ // ANDROID_SCALER_AVAILABLE_MIN_FRAME_DURATIONS and
+ // ANDROID_SCALER_AVAILABLE_STALL_DURATIONS in CameraMetadataTag.aidl.
+ MetadataBuilder& setAvailableOutputStreamConfigurations(
+ const std::vector<StreamConfiguration>& streamConfigurations);
+
+ // See ANDROID_CONTROL_AE_COMPENSATION_RANGE in CameraMetadataTag.aidl.
+ MetadataBuilder& setControlAeCompensationRange(int32_t min, int32_t max);
+
+ // See ANDROID_CONTROL_AE_COMPENSATION_STEP in CameraMetadataTag.aidl.
+ MetadataBuilder& setControlAeCompensationStep(camera_metadata_rational step);
+
+ // See ANDROID_CONTROL_AF_AVAILABLE_MODES in CameraMetadataTag.aidl.
+ MetadataBuilder& setControlAfAvailableModes(
+ const std::vector<camera_metadata_enum_android_control_af_mode_t>&
+ availableModes);
+
+ // See ANDROID_CONTROL_AF_MODE in CameraMetadataTag.aidl.
+ MetadataBuilder& setControlAfMode(
+ const camera_metadata_enum_android_control_af_mode_t mode);
+
+ // See ANDROID_CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES in CameraMetadataTag.aidl.
+ MetadataBuilder& setControlAeAvailableFpsRange(int32_t min, int32_t max);
+
+ // See ANDROID_CONTROL_CAPTURE_INTENT in CameraMetadataTag.aidl.
+ MetadataBuilder& setControlCaptureIntent(
+ camera_metadata_enum_android_control_capture_intent_t intent);
+
+ // See ANDROID_CONTROL_MAX_REGIONS in CameraMetadataTag.aidl.
+ MetadataBuilder& setControlMaxRegions(int32_t maxAeRegions,
+ int32_t maxAwbRegions,
+ int32_t maxAfRegions);
+
+ // See ANDROID_CONTROL_AE_REGIONS in CameraMetadataTag.aidl.
+ MetadataBuilder& setControlAeRegions(
+ const std::vector<ControlRegion>& aeRegions);
+
+ // See ANDROID_CONTROL_AWB_REGIONS in CameraMetadataTag.aidl.
+ MetadataBuilder& setControlAwbRegions(
+ const std::vector<ControlRegion>& awbRegions);
+
+ // See ANDROID_CONTROL_AF_REGIONS in CameraMetadataTag.aidl.
+ MetadataBuilder& setControlAfRegions(
+ const std::vector<ControlRegion>& afRegions);
+
+ // The size of the compressed JPEG image, in bytes.
+ //
+ // See ANDROID_JPEG_SIZE in CameraMetadataTag.aidl.
+ MetadataBuilder& setMaxJpegSize(int32_t size);
+
+ // See ANDROID_SCALER_AVAILABLE_MAX_DIGITAL_ZOOM in CameraMetadataTag.aidl.
+ MetadataBuilder& setAvailableMaxDigitalZoom(const float maxZoom);
+
+ // A list of all keys that the camera device has available to use with
+ // CaptureRequest.
+ //
+ // See ANDROID_REQUEST_AVAILABLE_REQUEST_KEYS in CameraMetadataTag.aidl.
+ MetadataBuilder& setAvailableRequestKeys(const std::vector<int32_t>& keys);
+
+ // A list of all keys that the camera device has available to use with
+ // CaptureResult.
+ //
+ // See ANDROID_RESULT_AVAILABLE_REQUEST_KEYS in CameraMetadataTag.aidl.
+ MetadataBuilder& setAvailableResultKeys(const std::vector<int32_t>& keys);
+
+ // See ANDROID_REQUEST_AVAILABLE_CAPABILITIES in CameraMetadataTag.aidl.
+ MetadataBuilder& setAvailableCapabilities(
+ const std::vector<
+ camera_metadata_enum_android_request_available_capabilities_t>&
+ capabilities);
+
+ // A list of all keys that the camera device has available to use.
+ //
+ // See ANDROID_REQUEST_AVAILABLE_CHARACTERISTICS_KEYS in CameraMetadataTag.aidl.
+ MetadataBuilder& setAvailableCharacteristicKeys(
+ const std::vector<camera_metadata_tag_t>& keys);
+
+ // Extends metadata with ANDROID_REQUEST_AVAILABLE_CHARACTERISTICS_KEYS
+ // containing all previously set tags.
+ MetadataBuilder& setAvailableCharacteristicKeys();
+
+ // Build CameraMetadata instance.
+ //
+ // Returns nullptr in case something went wrong.
+ std::unique_ptr<::aidl::android::hardware::camera::device::CameraMetadata>
+ build() const;
+
+ private:
+ // Maps metadata tags to vectors of values for the given tag.
+ std::map<camera_metadata_tag_t,
+ std::variant<std::vector<int64_t>, std::vector<int32_t>,
+ std::vector<uint8_t>, std::vector<float>,
+ std::vector<camera_metadata_rational_t>>>
+ mEntryMap;
+};
+
+} // namespace virtualcamera
+} // namespace companion
+} // namespace android
+
+#endif // ANDROID_COMPANION_VIRTUALCAMERA_METADATABUILDER_H
diff --git a/services/camera/virtualcamera/util/TestPatternHelper.cc b/services/camera/virtualcamera/util/TestPatternHelper.cc
new file mode 100644
index 0000000..a00a1b8
--- /dev/null
+++ b/services/camera/virtualcamera/util/TestPatternHelper.cc
@@ -0,0 +1,113 @@
+/*
+ * 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.
+ */
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "TestPatternHelper"
+
+#include "TestPatternHelper.h"
+
+#include <complex>
+#include <cstdint>
+
+#include "log/log.h"
+#include "utils/Errors.h"
+
+namespace android {
+namespace companion {
+namespace virtualcamera {
+
+namespace {
+
+uint8_t julia(const std::complex<float> n, const std::complex<float> c) {
+ std::complex<float> z = n;
+ for (int i = 0; i < 64; i++) {
+ z = z * z + c;
+ if (std::abs(z) > 2.0) return i * 4;
+ }
+ return 0xff;
+}
+
+uint8_t pixelToFractal(const int x, const int y, const std::complex<float> c) {
+ std::complex<float> n(float(x) / 640.0f - 0.5, float(y) / 480.0f - 0.5);
+ return julia(n * 5.f, c);
+}
+
+void renderTestPatternYcbCr420(uint8_t* data_ptr, const int width,
+ const int height, const int frameNumber) {
+ float time = float(frameNumber) / 120.0f;
+ const std::complex<float> c(std::sin(time), std::cos(time));
+
+ uint8_t* y_data = data_ptr;
+ uint8_t* uv_data = static_cast<uint8_t*>(y_data + width * height);
+
+ for (int i = 0; i < width; ++i) {
+ for (int j = 0; j < height; ++j) {
+ y_data[j * width + i] = pixelToFractal(i, j, c * 0.78f);
+ if ((i & 1) && (j & 1)) {
+ uv_data[((j / 2) * (width / 2) + i / 2) * 2] =
+ static_cast<uint8_t>((float(i) / float(width)) * 255.f);
+ uv_data[((j / 2) * (width / 2) + i / 2) * 2 + 1] =
+ static_cast<uint8_t>((float(j) / float(height)) * 255.f);
+ }
+ }
+ }
+}
+
+} // namespace
+
+// This is just to see some meaningfull image in the buffer for testing, only
+// works with YcbCr420.
+void renderTestPatternYCbCr420(const std::shared_ptr<AHardwareBuffer> buffer,
+ const int frameNumber, const int fence) {
+ AHardwareBuffer_Planes planes_info;
+
+ AHardwareBuffer_Desc hwBufferDesc;
+ AHardwareBuffer_describe(buffer.get(), &hwBufferDesc);
+
+ const int width = hwBufferDesc.width;
+ const int height = hwBufferDesc.height;
+
+ int result = AHardwareBuffer_lockPlanes(buffer.get(),
+ AHARDWAREBUFFER_USAGE_CPU_READ_RARELY,
+ fence, nullptr, &planes_info);
+ if (result != OK) {
+ ALOGE("%s: Failed to lock planes: %d", __func__, result);
+ return;
+ }
+
+ renderTestPatternYcbCr420(
+ reinterpret_cast<uint8_t*>(planes_info.planes[0].data), width, height,
+ frameNumber);
+
+ AHardwareBuffer_unlock(buffer.get(), nullptr);
+}
+
+void renderTestPatternYCbCr420(sp<Surface> surface, int frameNumber) {
+ ANativeWindow_Buffer buffer;
+ surface->lock(&buffer, nullptr);
+
+ ALOGV("buffer: %dx%d stride %d, pixfmt %d", buffer.width, buffer.height,
+ buffer.stride, buffer.format);
+
+ renderTestPatternYcbCr420(reinterpret_cast<uint8_t*>(buffer.bits),
+ buffer.width, buffer.height, frameNumber);
+
+ surface->unlockAndPost();
+}
+
+} // namespace virtualcamera
+} // namespace companion
+} // namespace android
diff --git a/services/camera/virtualcamera/util/TestPatternHelper.h b/services/camera/virtualcamera/util/TestPatternHelper.h
new file mode 100644
index 0000000..aca1cdd
--- /dev/null
+++ b/services/camera/virtualcamera/util/TestPatternHelper.h
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_COMPANION_VIRTUALCAMERA_TESTPATTERNHELPER_H
+#define ANDROID_COMPANION_VIRTUALCAMERA_TESTPATTERNHELPER_H
+
+#include <memory>
+
+#include "android/hardware_buffer.h"
+#include "gui/Surface.h"
+
+namespace android {
+namespace companion {
+namespace virtualcamera {
+
+// Helper function filling hardware buffer with test pattern for debugging /
+// testing purposes.
+void renderTestPatternYCbCr420(std::shared_ptr<AHardwareBuffer> buffer,
+ int frameNumber, int fence = -1);
+
+// Helper function for rendering test pattern into Surface.
+void renderTestPatternYCbCr420(sp<Surface> surface, int frameNumber);
+
+} // namespace virtualcamera
+} // namespace companion
+} // namespace android
+
+#endif // ANDROID_COMPANION_VIRTUALCAMERA_TESTPATTERNHELPER_H
diff --git a/services/camera/virtualcamera/util/Util.cc b/services/camera/virtualcamera/util/Util.cc
new file mode 100644
index 0000000..90f5916
--- /dev/null
+++ b/services/camera/virtualcamera/util/Util.cc
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+
+// #define LOG_NDEBUG 0
+#define LOG_TAG "VirtualCameraUtil"
+#include "Util.h"
+
+#include <unistd.h>
+
+#include "log/log.h"
+
+namespace android {
+namespace companion {
+namespace virtualcamera {
+
+using ::aidl::android::hardware::common::NativeHandle;
+
+sp<Fence> importFence(const NativeHandle& aidlHandle) {
+ if (aidlHandle.fds.size() != 1) {
+ ALOGE(
+ "%s: Cannot import fence from aidlHandle containing %d file "
+ "descriptors.",
+ __func__, static_cast<int>(aidlHandle.fds.size()));
+ return sp<Fence>::make();
+ }
+
+ return sp<Fence>::make(::dup(aidlHandle.fds[0].get()));
+}
+
+} // namespace virtualcamera
+} // namespace companion
+} // namespace android
diff --git a/services/camera/virtualcamera/util/Util.h b/services/camera/virtualcamera/util/Util.h
new file mode 100644
index 0000000..1a0a458
--- /dev/null
+++ b/services/camera/virtualcamera/util/Util.h
@@ -0,0 +1,49 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_COMPANION_VIRTUALCAMERA_UTIL_H
+#define ANDROID_COMPANION_VIRTUALCAMERA_UTIL_H
+
+#include <cstdint>
+
+#include "aidl/android/hardware/camera/common/Status.h"
+#include "aidl/android/hardware/camera/device/StreamBuffer.h"
+#include "android/binder_auto_utils.h"
+#include "ui/Fence.h"
+
+namespace android {
+namespace companion {
+namespace virtualcamera {
+
+// Converts camera AIDL status to ndk::ScopedAStatus
+inline ndk::ScopedAStatus cameraStatus(
+ const ::aidl::android::hardware::camera::common::Status status) {
+ return ndk::ScopedAStatus::fromServiceSpecificError(
+ static_cast<int32_t>(status));
+}
+
+// Import Fence from AIDL NativeHandle.
+//
+// If the handle can't be used to construct Fence (is empty or doesn't contain
+// only single fd) this function will return Fence instance in invalid state.
+sp<Fence> importFence(
+ const ::aidl::android::hardware::common::NativeHandle& handle);
+
+} // namespace virtualcamera
+} // namespace companion
+} // namespace android
+
+#endif // ANDROID_COMPANION_VIRTUALCAMERA_UTIL_H
diff --git a/services/camera/virtualcamera/virtual_camera.hal.rc b/services/camera/virtualcamera/virtual_camera.hal.rc
new file mode 100644
index 0000000..fd86965
--- /dev/null
+++ b/services/camera/virtualcamera/virtual_camera.hal.rc
@@ -0,0 +1,7 @@
+service virtual_camera /system/bin/virtual_camera
+ class core
+ user system
+ group system
+ capabilities SYS_NICE
+ rlimit rtprio 10 10
+ task_profiles CameraServiceCapacity CameraServicePerformance