Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3b478c49 authored by Jan Sebechlebsky's avatar Jan Sebechlebsky
Browse files

Allow to specify list of supported input configurations.

... and populate corresponding metadata entries / perform
validation based on these.

Bug: 301023410
Test: atest virtual_camera_tests
Change-Id: I66f3cf2b013d5845b6fa7429294a1ed2157318f8
parent 7c7244bf
Loading
Loading
Loading
Loading
+162 −41
Original line number Diff line number Diff line
@@ -18,13 +18,19 @@
#define LOG_TAG "VirtualCameraDevice"
#include "VirtualCameraDevice.h"

#include <algorithm>
#include <array>
#include <chrono>
#include <cstdint>
#include <iterator>
#include <optional>
#include <string>

#include "VirtualCameraSession.h"
#include "aidl/android/companion/virtualcamera/SupportedStreamConfiguration.h"
#include "aidl/android/hardware/camera/common/Status.h"
#include "aidl/android/hardware/camera/device/CameraMetadata.h"
#include "aidl/android/hardware/camera/device/StreamConfiguration.h"
#include "android/binder_auto_utils.h"
#include "android/binder_status.h"
#include "log/log.h"
@@ -36,13 +42,16 @@ namespace android {
namespace companion {
namespace virtualcamera {

using ::aidl::android::companion::virtualcamera::Format;
using ::aidl::android::companion::virtualcamera::IVirtualCameraCallback;
using ::aidl::android::companion::virtualcamera::SupportedStreamConfiguration;
using ::aidl::android::hardware::camera::common::CameraResourceCost;
using ::aidl::android::hardware::camera::common::Status;
using ::aidl::android::hardware::camera::device::CameraMetadata;
using ::aidl::android::hardware::camera::device::ICameraDeviceCallback;
using ::aidl::android::hardware::camera::device::ICameraDeviceSession;
using ::aidl::android::hardware::camera::device::ICameraInjectionSession;
using ::aidl::android::hardware::camera::device::Stream;
using ::aidl::android::hardware::camera::device::StreamConfiguration;
using ::aidl::android::hardware::camera::device::StreamRotation;
using ::aidl::android::hardware::camera::device::StreamType;
@@ -55,16 +64,68 @@ 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{};

struct Resolution {
  Resolution(const int w, const int h) : width(w), height(h) {
  }

  bool operator<(const Resolution& other) const {
    return width * height < other.width * other.height;
  }

  bool operator==(const Resolution& other) const {
    return width == other.width && height == other.height;
  }

  const int width;
  const int height;
};

std::optional<Resolution> getMaxResolution(
    const std::vector<SupportedStreamConfiguration>& configs) {
  auto itMax = std::max_element(configs.begin(), configs.end(),
                                [](const SupportedStreamConfiguration& a,
                                   const SupportedStreamConfiguration& b) {
                                  return a.width * b.height < a.width * b.height;
                                });
  if (itMax == configs.end()) {
    ALOGE(
        "%s: empty vector of supported configurations, cannot find largest "
        "resolution.",
        __func__);
    return std::nullopt;
  }

  return Resolution(itMax->width, itMax->height);
}

std::set<Resolution> getUniqueResolutions(
    const std::vector<SupportedStreamConfiguration>& configs) {
  std::set<Resolution> uniqueResolutions;
  std::transform(configs.begin(), configs.end(),
                 std::inserter(uniqueResolutions, uniqueResolutions.begin()),
                 [](const SupportedStreamConfiguration& config) {
                   return Resolution(config.width, config.height);
                 });
  return uniqueResolutions;
}

// TODO(b/301023410) - Populate camera characteristics according to camera configuration.
CameraMetadata initCameraCharacteristics() {
  auto metadata =
std::optional<CameraMetadata> initCameraCharacteristics(
    const std::vector<SupportedStreamConfiguration>& supportedInputConfig) {
  if (!std::all_of(supportedInputConfig.begin(), supportedInputConfig.end(),
                   [](const SupportedStreamConfiguration& config) {
                     return config.pixelFormat == Format::YUV_420_888;
                   })) {
    ALOGE("%s: input configuration contains unsupported pixel format", __func__);
    return std::nullopt;
  }

  MetadataBuilder builder =
      MetadataBuilder()
          .setSupportedHardwareLevel(
              ANDROID_INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL)
@@ -73,29 +134,8 @@ CameraMetadata initCameraCharacteristics() {
          .setSensorOrientation(0)
          .setAvailableFaceDetectModes({ANDROID_STATISTICS_FACE_DETECT_MODE_OFF})
          .setControlAfAvailableModes({ANDROID_CONTROL_AF_MODE_OFF})
          .setAvailableOutputStreamConfigurations(
              {MetadataBuilder::StreamConfiguration{
                   .width = kVgaWidth,
                   .height = kVgaHeight,
                   .format =
                       ANDROID_SCALER_AVAILABLE_FORMATS_IMPLEMENTATION_DEFINED,
                   .minFrameDuration = kMinFrameDuration30Fps,
                   .minStallDuration = 0s},
               MetadataBuilder::StreamConfiguration{
                   .width = kVgaWidth,
                   .height = kVgaHeight,
                   .format = ANDROID_SCALER_AVAILABLE_FORMATS_YCbCr_420_888,
                   .minFrameDuration = kMinFrameDuration30Fps,
                   .minStallDuration = 0s},
               {MetadataBuilder::StreamConfiguration{
                   .width = kVgaWidth,
                   .height = kVgaHeight,
                   .format = ANDROID_SCALER_AVAILABLE_FORMATS_BLOB,
                   .minFrameDuration = kMinFrameDuration30Fps,
                   .minStallDuration = 0s}}})
          .setControlAeAvailableFpsRange(10, 30)
          .setControlMaxRegions(0, 0, 0)
          .setSensorActiveArraySize(0, 0, kVgaWidth, kVgaHeight)
          .setControlAfRegions({kDefaultEmptyControlRegion})
          .setControlAeRegions({kDefaultEmptyControlRegion})
          .setControlAwbRegions({kDefaultEmptyControlRegion})
@@ -106,9 +146,66 @@ CameraMetadata initCameraCharacteristics() {
          .setAvailableResultKeys({ANDROID_CONTROL_AF_MODE})
          .setAvailableCapabilities(
              {ANDROID_REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE})
          .setAvailableCharacteristicKeys()
          .build();
          .setAvailableCharacteristicKeys();

  // Active array size must correspond to largest supported input resolution.
  std::optional<Resolution> maxResolution =
      getMaxResolution(supportedInputConfig);
  if (!maxResolution.has_value()) {
    return std::nullopt;
  }
  builder.setSensorActiveArraySize(0, 0, maxResolution->width,
                                   maxResolution->height);

  std::vector<MetadataBuilder::StreamConfiguration> outputConfigurations;

  // TODO(b/301023410) Add also all "standard" resolutions we can rescale the
  // streams to (all standard resolutions with same aspect ratio).

  // Add IMPLEMENTATION_DEFINED format for all supported input resolutions.
  std::set<Resolution> uniqueResolutions =
      getUniqueResolutions(supportedInputConfig);
  std::transform(
      uniqueResolutions.begin(), uniqueResolutions.end(),
      std::back_inserter(outputConfigurations),
      [](const Resolution& resolution) {
        return MetadataBuilder::StreamConfiguration{
            .width = resolution.width,
            .height = resolution.height,
            .format = ANDROID_SCALER_AVAILABLE_FORMATS_IMPLEMENTATION_DEFINED,
            .minFrameDuration = kMinFrameDuration30Fps,
            .minStallDuration = 0s};
      });

  // Add all supported configuration with explicit pixel format.
  std::transform(supportedInputConfig.begin(), supportedInputConfig.end(),
                 std::back_inserter(outputConfigurations),
                 [](const SupportedStreamConfiguration& config) {
                   return MetadataBuilder::StreamConfiguration{
                       .width = config.width,
                       .height = config.height,
                       .format = static_cast<int>(config.pixelFormat),
                       .minFrameDuration = kMinFrameDuration30Fps,
                       .minStallDuration = 0s};
                 });

  // TODO(b/301023410) We currently don't support rescaling for still capture,
  // so only announce BLOB support for formats exactly matching the input.
  std::transform(uniqueResolutions.begin(), uniqueResolutions.end(),
                 std::back_inserter(outputConfigurations),
                 [](const Resolution& resolution) {
                   return MetadataBuilder::StreamConfiguration{
                       .width = resolution.width,
                       .height = resolution.height,
                       .format = ANDROID_SCALER_AVAILABLE_FORMATS_BLOB,
                       .minFrameDuration = kMinFrameDuration30Fps,
                       .minStallDuration = 0s};
                 });

  ALOGV("Adding %zu output configurations", outputConfigurations.size());
  builder.setAvailableOutputStreamConfigurations(outputConfigurations);

  auto metadata = builder.build();
  if (metadata == nullptr) {
    ALOGE("Failed to build metadata!");
    return CameraMetadata();
@@ -121,10 +218,21 @@ CameraMetadata initCameraCharacteristics() {

VirtualCameraDevice::VirtualCameraDevice(
    const uint32_t cameraId,
    const std::vector<SupportedStreamConfiguration>& supportedInputConfig,
    std::shared_ptr<IVirtualCameraCallback> virtualCameraClientCallback)
    : mCameraId(cameraId),
      mVirtualCameraClientCallback(virtualCameraClientCallback) {
  mCameraCharacteristics = initCameraCharacteristics();
      mVirtualCameraClientCallback(virtualCameraClientCallback),
      mSupportedInputConfigurations(supportedInputConfig) {
  std::optional<CameraMetadata> metadata =
      initCameraCharacteristics(mSupportedInputConfigurations);
  if (metadata.has_value()) {
    mCameraCharacteristics = *metadata;
  } else {
    ALOGE(
        "%s: Failed to initialize camera characteristic based on provided "
        "configuration.",
        __func__);
  }
}

ndk::ScopedAStatus VirtualCameraDevice::getCameraCharacteristics(
@@ -168,29 +276,42 @@ ndk::ScopedAStatus VirtualCameraDevice::isStreamCombinationSupported(
    return cameraStatus(Status::ILLEGAL_ARGUMENT);
  }

  for (const auto& stream : in_streams.streams) {
  *_aidl_return = isStreamCombinationSupported(in_streams);
  return ndk::ScopedAStatus::ok();
};

bool VirtualCameraDevice::isStreamCombinationSupported(
    const StreamConfiguration& streamConfiguration) const {
  for (const Stream& stream : streamConfiguration.streams) {
    ALOGV("%s: Configuration queried: %s", __func__, stream.toString().c_str());

    if (stream.streamType == StreamType::INPUT) {
      ALOGW("%s: Input stream type is not supported", __func__);
      *_aidl_return = false;
      return ndk::ScopedAStatus::ok();
      return false;
    }

    // TODO(b/301023410) remove hardcoded format checks, verify against configuration.
    if (stream.width != 640 || stream.height != 480 ||
        stream.rotation != StreamRotation::ROTATION_0 ||
    if (stream.rotation != StreamRotation::ROTATION_0 ||
        (stream.format != PixelFormat::IMPLEMENTATION_DEFINED &&
         stream.format != PixelFormat::YCBCR_420_888 &&
         stream.format != PixelFormat::BLOB)) {
      *_aidl_return = false;
      return ndk::ScopedAStatus::ok();
    }
      ALOGV("Unsupported output stream type");
      return false;
    }

  *_aidl_return = true;
  return ndk::ScopedAStatus::ok();
    auto matchesSupportedInputConfig =
        [&stream](const SupportedStreamConfiguration& config) {
          return stream.width == config.width && stream.height == config.height;
        };
    if (std::none_of(mSupportedInputConfigurations.begin(),
                     mSupportedInputConfigurations.end(),
                     matchesSupportedInputConfig)) {
      ALOGV("Requested config doesn't match any supported input config");
      return false;
    }
  }
  return true;
}

ndk::ScopedAStatus VirtualCameraDevice::open(
    const std::shared_ptr<ICameraDeviceCallback>& in_callback,
@@ -198,7 +319,7 @@ ndk::ScopedAStatus VirtualCameraDevice::open(
  ALOGV("%s", __func__);

  *_aidl_return = ndk::SharedRefBase::make<VirtualCameraSession>(
      std::to_string(mCameraId), in_callback, mVirtualCameraClientCallback);
      *this, in_callback, mVirtualCameraClientCallback);

  return ndk::ScopedAStatus::ok();
};
+12 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@
#include <memory>

#include "aidl/android/companion/virtualcamera/IVirtualCameraCallback.h"
#include "aidl/android/companion/virtualcamera/SupportedStreamConfiguration.h"
#include "aidl/android/hardware/camera/device/BnCameraDevice.h"

namespace android {
@@ -34,6 +35,9 @@ class VirtualCameraDevice
 public:
  explicit VirtualCameraDevice(
      uint32_t cameraId,
      const std::vector<
          aidl::android::companion::virtualcamera::SupportedStreamConfiguration>&
          supportedInputConfig,
      std::shared_ptr<
          ::aidl::android::companion::virtualcamera::IVirtualCameraCallback>
          virtualCameraClientCallback = nullptr);
@@ -58,6 +62,10 @@ class VirtualCameraDevice
          in_streams,
      bool* _aidl_return) override;

  bool isStreamCombinationSupported(
      const ::aidl::android::hardware::camera::device::StreamConfiguration&
          in_streams) const;

  ndk::ScopedAStatus open(
      const std::shared_ptr<
          ::aidl::android::hardware::camera::device::ICameraDeviceCallback>&
@@ -94,6 +102,10 @@ class VirtualCameraDevice
      mVirtualCameraClientCallback;

  ::aidl::android::hardware::camera::device::CameraMetadata mCameraCharacteristics;

  const std::vector<
      aidl::android::companion::virtualcamera::SupportedStreamConfiguration>
      mSupportedInputConfigurations;
};

}  // namespace virtualcamera
+3 −1
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ namespace companion {
namespace virtualcamera {

using ::aidl::android::companion::virtualcamera::IVirtualCameraCallback;
using ::aidl::android::companion::virtualcamera::SupportedStreamConfiguration;
using ::aidl::android::hardware::camera::common::CameraDeviceStatus;
using ::aidl::android::hardware::camera::common::Status;
using ::aidl::android::hardware::camera::common::VendorTagSection;
@@ -154,9 +155,10 @@ ndk::ScopedAStatus VirtualCameraProvider::isConcurrentStreamCombinationSupported
}

std::shared_ptr<VirtualCameraDevice> VirtualCameraProvider::createCamera(
    const std::vector<SupportedStreamConfiguration>& supportedInputConfig,
    std::shared_ptr<IVirtualCameraCallback> virtualCameraClientCallback) {
  auto camera = ndk::SharedRefBase::make<VirtualCameraDevice>(
      sNextId++, virtualCameraClientCallback);
      sNextId++, supportedInputConfig, virtualCameraClientCallback);
  std::shared_ptr<ICameraProviderCallback> callback;
  {
    const std::lock_guard<std::mutex> lock(mLock);
+3 −0
Original line number Diff line number Diff line
@@ -77,6 +77,9 @@ class VirtualCameraProvider
  //
  // TODO(b/301023410) - Add camera configuration.
  std::shared_ptr<VirtualCameraDevice> createCamera(
      const std::vector<
          aidl::android::companion::virtualcamera::SupportedStreamConfiguration>&
          supportedInputConfig,
      std::shared_ptr<aidl::android::companion::virtualcamera::IVirtualCameraCallback>
          virtualCameraClientCallback = nullptr);

+41 −4
Original line number Diff line number Diff line
@@ -15,7 +15,6 @@
 */

// #define LOG_NDEBUG 0
#include "android/binder_status.h"
#define LOG_TAG "VirtualCameraService"
#include "VirtualCameraService.h"

@@ -27,9 +26,12 @@

#include "VirtualCameraDevice.h"
#include "VirtualCameraProvider.h"
#include "aidl/android/companion/virtualcamera/Format.h"
#include "aidl/android/companion/virtualcamera/VirtualCameraConfiguration.h"
#include "android/binder_auto_utils.h"
#include "android/binder_libbinder.h"
#include "binder/Status.h"
#include "util/Util.h"

using ::android::binder::Status;

@@ -37,10 +39,14 @@ namespace android {
namespace companion {
namespace virtualcamera {

using ::aidl::android::companion::virtualcamera::Format;
using ::aidl::android::companion::virtualcamera::SupportedStreamConfiguration;
using ::aidl::android::companion::virtualcamera::VirtualCameraConfiguration;

namespace {

constexpr int kVgaWidth = 640;
constexpr int kVgaHeight = 480;
constexpr char kEnableTestCameraCmd[] = "enable_test_camera";
constexpr char kDisableTestCameraCmd[] = "disable_test_camera";
constexpr char kShellCmdHelp[] = R"(
@@ -49,6 +55,27 @@ Available commands:
 * disable_test_camera
)";

ndk::ScopedAStatus validateConfiguration(
    const VirtualCameraConfiguration& configuration) {
  if (configuration.supportedStreamConfigs.empty()) {
    ALOGE("%s: No supported input configuration specified", __func__);
    return ndk::ScopedAStatus::fromServiceSpecificError(
        Status::EX_ILLEGAL_ARGUMENT);
  }

  for (const SupportedStreamConfiguration& config :
       configuration.supportedStreamConfigs) {
    if (!isFormatSupportedForInput(config.width, config.height,
                                   config.pixelFormat)) {
      ALOGE("%s: Requested unsupported input format: %d x %d (%d)", __func__,
            config.width, config.height, static_cast<int>(config.pixelFormat));
      return ndk::ScopedAStatus::fromServiceSpecificError(
          Status::EX_ILLEGAL_ARGUMENT);
    }
  }
  return ndk::ScopedAStatus::ok();
}

}  // namespace

VirtualCameraService::VirtualCameraService(
@@ -59,13 +86,18 @@ VirtualCameraService::VirtualCameraService(
ndk::ScopedAStatus VirtualCameraService::registerCamera(
    const ::ndk::SpAIBinder& token,
    const VirtualCameraConfiguration& configuration, bool* _aidl_return) {
  (void)configuration;
  if (_aidl_return == nullptr) {
    return ndk::ScopedAStatus::fromServiceSpecificError(
        Status::EX_ILLEGAL_ARGUMENT);
  }
  *_aidl_return = true;

  auto status = validateConfiguration(configuration);
  if (!status.isOk()) {
    *_aidl_return = false;
    return status;
  }

  std::lock_guard lock(mLock);
  if (mTokenToCameraName.find(token) != mTokenToCameraName.end()) {
    ALOGE(
@@ -74,11 +106,13 @@ ndk::ScopedAStatus VirtualCameraService::registerCamera(
        "0x%" PRIxPTR,
        reinterpret_cast<uintptr_t>(token.get()));
    *_aidl_return = false;
    return ndk::ScopedAStatus::ok();
  }

  // TODO(b/301023410) Validate configuration and pass it to the camera.
  std::shared_ptr<VirtualCameraDevice> camera =
      mVirtualCameraProvider->createCamera(configuration.virtualCameraCallback);
      mVirtualCameraProvider->createCamera(configuration.supportedStreamConfigs,
                                           configuration.virtualCameraCallback);
  if (camera == nullptr) {
    ALOGE("Failed to create camera for binder token 0x%" PRIxPTR,
          reinterpret_cast<uintptr_t>(token.get()));
@@ -159,7 +193,10 @@ void VirtualCameraService::enableTestCameraCmd(const int out, const int err) {
  mTestCameraToken.set(AIBinder_fromPlatformBinder(token));

  bool ret;
  registerCamera(mTestCameraToken, VirtualCameraConfiguration(), &ret);
  VirtualCameraConfiguration configuration;
  configuration.supportedStreamConfigs.push_back(
      {.width = kVgaWidth, .height = kVgaHeight, Format::YUV_420_888});
  registerCamera(mTestCameraToken, configuration, &ret);
  if (ret) {
    dprintf(out, "Successfully registered test camera %s",
            getCamera(mTestCameraToken)->getCameraName().c_str());
Loading