blob: 7466089e896a385b29fae86ddc8f7959b1de20f1 [file] [log] [blame]
/*
* Copyright 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 "VirtualCameraService"
#include "VirtualCameraService.h"
#include <algorithm>
#include <array>
#include <cinttypes>
#include <cstdint>
#include <memory>
#include <mutex>
#include <optional>
#include <regex>
#include <variant>
#include "VirtualCameraDevice.h"
#include "VirtualCameraProvider.h"
#include "VirtualCameraTestInstance.h"
#include "aidl/android/companion/virtualcamera/Format.h"
#include "aidl/android/companion/virtualcamera/LensFacing.h"
#include "aidl/android/companion/virtualcamera/VirtualCameraConfiguration.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/Status.h"
#include "fmt/format.h"
#include "util/EglDisplayContext.h"
#include "util/EglUtil.h"
#include "util/Permissions.h"
#include "util/Util.h"
using ::android::binder::Status;
namespace android {
namespace companion {
namespace virtualcamera {
using ::aidl::android::companion::virtualcamera::Format;
using ::aidl::android::companion::virtualcamera::LensFacing;
using ::aidl::android::companion::virtualcamera::SensorOrientation;
using ::aidl::android::companion::virtualcamera::SupportedStreamConfiguration;
using ::aidl::android::companion::virtualcamera::VirtualCameraConfiguration;
namespace {
constexpr char kCameraIdPrefix[] = "v";
constexpr int kVgaWidth = 640;
constexpr int kVgaHeight = 480;
constexpr int kMaxFps = 60;
constexpr int kTestCameraDefaultInputFps = 30;
constexpr char kEnableTestCameraCmd[] = "enable_test_camera";
constexpr char kDisableTestCameraCmd[] = "disable_test_camera";
constexpr char kHelp[] = "help";
constexpr char kShellCmdHelp[] = R"(
Usage:
cmd virtual_camera command [--option=value]
Available commands:
* enable_test_camera
Options:
--camera_id=(ID) - override numerical ID for test camera instance
--lens_facing=(front|back|external) - specifies lens facing for test camera instance
--input_fps=(fps) - specify input fps for test camera, valid values are from 1 to 1000
--sensor_orientation=(0|90|180|270) - Clockwise angle through which the output image
needs to be rotated to be upright on the device screen in its native orientation
* disable_test_camera
)";
constexpr char kCreateVirtualDevicePermission[] =
"android.permission.CREATE_VIRTUAL_DEVICE";
constexpr std::array<const char*, 3> kRequiredEglExtensions = {
"GL_OES_EGL_image_external",
"GL_OES_EGL_image_external_essl3",
"GL_EXT_YUV_target",
};
// Numerical portion for id to assign to next created camera.
static std::atomic_int sNextIdNumericalPortion{1000};
ndk::ScopedAStatus validateConfiguration(
const VirtualCameraConfiguration& configuration) {
if (configuration.supportedStreamConfigs.empty()) {
ALOGE("%s: No supported input configuration specified", __func__);
return ndk::ScopedAStatus::fromServiceSpecificError(
Status::EX_ILLEGAL_ARGUMENT);
}
if (configuration.virtualCameraCallback == nullptr) {
ALOGE("%s: Input configuration is missing virtual camera callback",
__func__);
return ndk::ScopedAStatus::fromServiceSpecificError(
Status::EX_ILLEGAL_ARGUMENT);
}
for (const SupportedStreamConfiguration& config :
configuration.supportedStreamConfigs) {
if (!isFormatSupportedForInput(config.width, config.height,
config.pixelFormat, config.maxFps)) {
ALOGE("%s: Requested unsupported input format: %d x %d (%d)", __func__,
config.width, config.height, static_cast<int>(config.pixelFormat));
return ndk::ScopedAStatus::fromServiceSpecificError(
Status::EX_ILLEGAL_ARGUMENT);
}
}
if (configuration.sensorOrientation != SensorOrientation::ORIENTATION_0 &&
configuration.sensorOrientation != SensorOrientation::ORIENTATION_90 &&
configuration.sensorOrientation != SensorOrientation::ORIENTATION_180 &&
configuration.sensorOrientation != SensorOrientation::ORIENTATION_270) {
return ndk::ScopedAStatus::fromServiceSpecificError(
Status::EX_ILLEGAL_ARGUMENT);
}
if (configuration.lensFacing != LensFacing::FRONT &&
configuration.lensFacing != LensFacing::BACK &&
configuration.lensFacing != LensFacing::EXTERNAL) {
return ndk::ScopedAStatus::fromServiceSpecificError(
Status::EX_ILLEGAL_ARGUMENT);
}
return ndk::ScopedAStatus::ok();
}
enum class Command {
ENABLE_TEST_CAMERA,
DISABLE_TEST_CAMERA,
HELP,
};
struct CommandWithOptions {
Command command;
std::map<std::string, std::string> optionToValueMap;
};
std::optional<int> parseInt(const std::string& s) {
if (!std::all_of(s.begin(), s.end(), [](char c) { return std::isdigit(c); })) {
return std::nullopt;
}
int ret = atoi(s.c_str());
return ret > 0 ? std::optional(ret) : std::nullopt;
}
std::optional<LensFacing> parseLensFacing(const std::string& s) {
static const std::map<std::string, LensFacing> strToLensFacing{
{"front", LensFacing::FRONT},
{"back", LensFacing::BACK},
{"external", LensFacing::EXTERNAL}};
auto it = strToLensFacing.find(s);
return it == strToLensFacing.end() ? std::nullopt : std::optional(it->second);
}
std::variant<CommandWithOptions, std::string> parseCommand(
const char** args, const uint32_t numArgs) {
static const std::regex optionRegex("^--(\\w+)(?:=(.+))?$");
static const std::map<std::string, Command> strToCommand{
{kHelp, Command::HELP},
{kEnableTestCameraCmd, Command::ENABLE_TEST_CAMERA},
{kDisableTestCameraCmd, Command::DISABLE_TEST_CAMERA}};
if (numArgs < 1) {
return CommandWithOptions{.command = Command::HELP};
}
// We interpret the first argument as command;
auto it = strToCommand.find(args[0]);
if (it == strToCommand.end()) {
return "Unknown command: " + std::string(args[0]);
}
CommandWithOptions cmd{.command = it->second};
for (int i = 1; i < numArgs; i++) {
std::cmatch cm;
if (!std::regex_match(args[i], cm, optionRegex)) {
return "Not an option: " + std::string(args[i]);
}
cmd.optionToValueMap[cm[1]] = cm[2];
}
return cmd;
}
ndk::ScopedAStatus verifyRequiredEglExtensions() {
EglDisplayContext context;
for (const char* eglExtension : kRequiredEglExtensions) {
if (!isGlExtensionSupported(eglExtension)) {
ALOGE("%s not supported", eglExtension);
return ndk::ScopedAStatus::fromExceptionCodeWithMessage(
EX_UNSUPPORTED_OPERATION,
fmt::format(
"Cannot create virtual camera, because required EGL extension {} "
"is not supported on this system",
eglExtension)
.c_str());
}
}
return ndk::ScopedAStatus::ok();
}
std::string createCameraId(const int32_t deviceId) {
return kCameraIdPrefix + std::to_string(deviceId) + "_" +
std::to_string(sNextIdNumericalPortion++);
}
} // namespace
VirtualCameraService::VirtualCameraService(
std::shared_ptr<VirtualCameraProvider> virtualCameraProvider,
const PermissionsProxy& permissionProxy)
: mVirtualCameraProvider(virtualCameraProvider),
mPermissionProxy(permissionProxy) {
}
ndk::ScopedAStatus VirtualCameraService::registerCamera(
const ::ndk::SpAIBinder& token,
const VirtualCameraConfiguration& configuration, const int32_t deviceId,
bool* _aidl_return) {
return registerCamera(token, configuration, createCameraId(deviceId),
deviceId, _aidl_return);
}
ndk::ScopedAStatus VirtualCameraService::registerCamera(
const ::ndk::SpAIBinder& token,
const VirtualCameraConfiguration& configuration,
const std::string& cameraId, const int32_t deviceId, bool* _aidl_return) {
if (!mPermissionProxy.checkCallingPermission(kCreateVirtualDevicePermission)) {
ALOGE("%s: caller (pid %d, uid %d) doesn't hold %s permission", __func__,
getpid(), getuid(), kCreateVirtualDevicePermission);
return ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY);
}
if (_aidl_return == nullptr) {
return ndk::ScopedAStatus::fromServiceSpecificError(
Status::EX_ILLEGAL_ARGUMENT);
}
if (mVerifyEglExtensions) {
auto status = verifyRequiredEglExtensions();
if (!status.isOk()) {
*_aidl_return = false;
return status;
}
}
auto status = validateConfiguration(configuration);
if (!status.isOk()) {
*_aidl_return = false;
return status;
}
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;
return ndk::ScopedAStatus::ok();
}
std::shared_ptr<VirtualCameraDevice> camera =
mVirtualCameraProvider->createCamera(configuration, cameraId, deviceId);
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();
*_aidl_return = true;
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus VirtualCameraService::unregisterCamera(
const ::ndk::SpAIBinder& token) {
if (!mPermissionProxy.checkCallingPermission(kCreateVirtualDevicePermission)) {
ALOGE("%s: caller (pid %d, uid %d) doesn't hold %s permission", __func__,
getpid(), getuid(), kCreateVirtualDevicePermission);
return ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY);
}
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);
mTokenToCameraName.erase(it);
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus VirtualCameraService::getCameraId(
const ::ndk::SpAIBinder& token, std::string* _aidl_return) {
if (!mPermissionProxy.checkCallingPermission(kCreateVirtualDevicePermission)) {
ALOGE("%s: caller (pid %d, uid %d) doesn't hold %s permission", __func__,
getpid(), getuid(), kCreateVirtualDevicePermission);
return ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY);
}
if (_aidl_return == nullptr) {
return ndk::ScopedAStatus::fromServiceSpecificError(
Status::EX_ILLEGAL_ARGUMENT);
}
auto camera = getCamera(token);
if (camera == nullptr) {
ALOGE(
"Attempt to get camera id corresponding to unknown binder token: "
"0x%" PRIxPTR,
reinterpret_cast<uintptr_t>(token.get()));
return ndk::ScopedAStatus::ok();
}
*_aidl_return = camera->getCameraId();
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, int out, int err,
const char** args,
uint32_t numArgs) {
if (numArgs <= 0) {
dprintf(out, kShellCmdHelp);
fsync(out);
return STATUS_OK;
}
auto isNullptr = [](const char* ptr) { return ptr == nullptr; };
if (args == nullptr || std::any_of(args, args + numArgs, isNullptr)) {
return STATUS_BAD_VALUE;
}
std::variant<CommandWithOptions, std::string> cmdOrErrorMessage =
parseCommand(args, numArgs);
if (std::holds_alternative<std::string>(cmdOrErrorMessage)) {
dprintf(err, "Error: %s\n",
std::get<std::string>(cmdOrErrorMessage).c_str());
return STATUS_BAD_VALUE;
}
const CommandWithOptions& cmd =
std::get<CommandWithOptions>(cmdOrErrorMessage);
binder_status_t status = STATUS_OK;
switch (cmd.command) {
case Command::HELP:
dprintf(out, kShellCmdHelp);
break;
case Command::ENABLE_TEST_CAMERA:
status = enableTestCameraCmd(out, err, cmd.optionToValueMap);
break;
case Command::DISABLE_TEST_CAMERA:
disableTestCameraCmd(out);
break;
}
fsync(err);
fsync(out);
return status;
}
binder_status_t VirtualCameraService::enableTestCameraCmd(
const int out, const int err,
const std::map<std::string, std::string>& options) {
if (mTestCameraToken != nullptr) {
dprintf(out, "Test camera is already enabled (%s).\n",
getCamera(mTestCameraToken)->getCameraName().c_str());
return STATUS_OK;
}
std::optional<std::string> cameraId;
auto it = options.find("camera_id");
if (it != options.end()) {
cameraId = it->second;
if (!cameraId.has_value()) {
dprintf(err, "Invalid camera_id: %s", it->second.c_str());
return STATUS_BAD_VALUE;
}
}
std::optional<LensFacing> lensFacing;
it = options.find("lens_facing");
if (it != options.end()) {
lensFacing = parseLensFacing(it->second);
if (!lensFacing.has_value()) {
dprintf(err, "Invalid lens_facing: %s\n, must be front|back|external",
it->second.c_str());
return STATUS_BAD_VALUE;
}
}
std::optional<int> inputFps;
it = options.find("input_fps");
if (it != options.end()) {
inputFps = parseInt(it->second);
if (!inputFps.has_value() || inputFps.value() < 1 ||
inputFps.value() > 1000) {
dprintf(err, "Invalid input fps: %s\n, must be integer in <1,1000> range.",
it->second.c_str());
return STATUS_BAD_VALUE;
}
}
std::optional<SensorOrientation> sensorOrientation;
std::optional<int> sensorOrientationInt;
it = options.find("sensor_orientation");
if (it != options.end()) {
sensorOrientationInt = parseInt(it->second);
switch (sensorOrientationInt.value_or(0)) {
case 0:
sensorOrientation = SensorOrientation::ORIENTATION_0;
break;
case 90:
sensorOrientation = SensorOrientation::ORIENTATION_90;
break;
case 180:
sensorOrientation = SensorOrientation::ORIENTATION_180;
break;
case 270:
sensorOrientation = SensorOrientation::ORIENTATION_270;
break;
default:
dprintf(err, "Invalid sensor rotation: %s\n, must be 0, 90, 180 or 270.",
it->second.c_str());
return STATUS_BAD_VALUE;
}
}
sp<BBinder> token = sp<BBinder>::make();
mTestCameraToken.set(AIBinder_fromPlatformBinder(token));
bool ret;
VirtualCameraConfiguration configuration;
configuration.supportedStreamConfigs.push_back({.width = kVgaWidth,
.height = kVgaHeight,
Format::RGBA_8888,
.maxFps = kMaxFps});
configuration.lensFacing = lensFacing.value_or(LensFacing::EXTERNAL);
configuration.sensorOrientation =
sensorOrientation.value_or(SensorOrientation::ORIENTATION_0);
configuration.virtualCameraCallback =
ndk::SharedRefBase::make<VirtualCameraTestInstance>(
inputFps.value_or(kTestCameraDefaultInputFps));
registerCamera(mTestCameraToken, configuration,
cameraId.value_or(std::to_string(sNextIdNumericalPortion++)),
kDefaultDeviceId, &ret);
if (ret) {
dprintf(out, "Successfully registered test camera %s\n",
getCamera(mTestCameraToken)->getCameraName().c_str());
} else {
dprintf(err, "Failed to create test camera\n");
}
return STATUS_OK;
}
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