// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "media/capture/video/chromeos/camera_device_delegate.h"
#include <algorithm>
#include <cmath>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "ash/constants/ash_features.h"
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/no_destructor.h"
#include "base/posix/safe_strerror.h"
#include "base/ranges/algorithm.h"
#include "base/strings/string_number_conversions.h"
#include "base/task/bind_post_task.h"
#include "base/task/single_thread_task_runner.h"
#include "base/trace_event/typed_macros.h"
#include "components/device_event_log/device_event_log.h"
#include "media/capture/mojom/image_capture_types.h"
#include "media/capture/video/blob_utils.h"
#include "media/capture/video/chromeos/camera_3a_controller.h"
#include "media/capture/video/chromeos/camera_app_device_bridge_impl.h"
#include "media/capture/video/chromeos/camera_buffer_factory.h"
#include "media/capture/video/chromeos/camera_hal_delegate.h"
#include "media/capture/video/chromeos/camera_metadata_utils.h"
#include "media/capture/video/chromeos/camera_trace_utils.h"
#include "media/capture/video/chromeos/request_manager.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
namespace media {
namespace {
constexpr char kBrightness[] = "com.google.control.brightness";
constexpr char kBrightnessRange[] = "com.google.control.brightnessRange";
constexpr char kContrast[] = "com.google.control.contrast";
constexpr char kContrastRange[] = "com.google.control.contrastRange";
constexpr char kPan[] = "com.google.control.pan";
constexpr char kPanRange[] = "com.google.control.panRange";
constexpr char kSaturation[] = "com.google.control.saturation";
constexpr char kSaturationRange[] = "com.google.control.saturationRange";
constexpr char kSharpness[] = "com.google.control.sharpness";
constexpr char kSharpnessRange[] = "com.google.control.sharpnessRange";
constexpr char kTilt[] = "com.google.control.tilt";
constexpr char kTiltRange[] = "com.google.control.tiltRange";
constexpr char kZoom[] = "com.google.control.zoom";
constexpr char kZoomRange[] = "com.google.control.zoomRange";
constexpr int32_t kColorTemperatureStep = 100;
constexpr int32_t kMicroToNano = 1000;
constexpr char kIntelPowerMode[] = "intel.vendorCamera.powerMode";
constexpr char kLibcameraStillCaptureMFNR[] = "org.libcamera.stillCaptureMFNR";
constexpr uint8_t kIntelPowerModeLowPower = 0;
constexpr uint8_t kIntelPowerModeHighQuality = 1;
using AwbModeTemperatureMap = std::map<uint8_t, int32_t>;
const AwbModeTemperatureMap& GetAwbModeTemperatureMap() {
// https://source.android.com/devices/camera/camera3_3Amodes#auto-wb
static const base::NoDestructor<AwbModeTemperatureMap> kAwbModeTemperatureMap(
{
{static_cast<uint8_t>(cros::mojom::AndroidControlAwbMode::
ANDROID_CONTROL_AWB_MODE_INCANDESCENT),
2700},
{static_cast<uint8_t>(cros::mojom::AndroidControlAwbMode::
ANDROID_CONTROL_AWB_MODE_FLUORESCENT),
5000},
{static_cast<uint8_t>(cros::mojom::AndroidControlAwbMode::
ANDROID_CONTROL_AWB_MODE_WARM_FLUORESCENT),
3000},
{static_cast<uint8_t>(cros::mojom::AndroidControlAwbMode::
ANDROID_CONTROL_AWB_MODE_DAYLIGHT),
5500},
{static_cast<uint8_t>(cros::mojom::AndroidControlAwbMode::
ANDROID_CONTROL_AWB_MODE_CLOUDY_DAYLIGHT),
6500},
{static_cast<uint8_t>(cros::mojom::AndroidControlAwbMode::
ANDROID_CONTROL_AWB_MODE_TWILIGHT),
15000},
{static_cast<uint8_t>(cros::mojom::AndroidControlAwbMode::
ANDROID_CONTROL_AWB_MODE_SHADE),
7500},
});
return *kAwbModeTemperatureMap;
}
std::pair<int32_t, int32_t> GetTargetFrameRateRange(
const cros::mojom::CameraMetadataPtr& static_metadata,
int target_frame_rate,
bool prefer_constant_frame_rate) {
int32_t result_min = 0;
int32_t result_max = 0;
auto available_frame_rates = GetMetadataEntryAsSpan<int32_t>(
static_metadata, cros::mojom::CameraMetadataTag::
ANDROID_CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES);
for (size_t idx = 0; idx < available_frame_rates.size(); idx += 2) {
int32_t min = available_frame_rates[idx];
int32_t max = available_frame_rates[idx + 1];
if (max != target_frame_rate) {
continue;
}
if (result_min != 0) {
if (prefer_constant_frame_rate && min <= result_min) {
// If we prefer constant frame rate, we prefer a smaller range.
continue;
} else if (!prefer_constant_frame_rate && min >= result_min) {
// Othwewise, we prefer a larger range.
continue;
}
}
result_min = min;
result_max = max;
if (prefer_constant_frame_rate && min == max) {
break;
}
}
return std::make_pair(result_min, result_max);
}
// The result of max_width and max_height could be zero if the stream
// is not in the pre-defined configuration.
void GetStreamResolutions(const cros::mojom::CameraMetadataPtr& static_metadata,
cros::mojom::Camera3StreamType stream_type,
cros::mojom::HalPixelFormat stream_format,
std::vector<gfx::Size>* resolutions) {
const cros::mojom::CameraMetadataEntryPtr* stream_configurations =
GetMetadataEntry(static_metadata,
cros::mojom::CameraMetadataTag::
ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS);
DCHECK(stream_configurations);
// The available stream configurations are stored as tuples of four int32s:
// (hal_pixel_format, width, height, type) x n
const size_t kStreamFormatOffset = 0;
const size_t kStreamWidthOffset = 1;
const size_t kStreamHeightOffset = 2;
const size_t kStreamTypeOffset = 3;
const size_t kStreamConfigurationSize = 4;
int32_t* iter =
reinterpret_cast<int32_t*>((*stream_configurations)->data.data());
for (size_t i = 0; i < (*stream_configurations)->count;
i += kStreamConfigurationSize) {
auto format =
static_cast<cros::mojom::HalPixelFormat>(iter[kStreamFormatOffset]);
int32_t width = iter[kStreamWidthOffset];
int32_t height = iter[kStreamHeightOffset];
auto type =
static_cast<cros::mojom::Camera3StreamType>(iter[kStreamTypeOffset]);
iter += kStreamConfigurationSize;
if (type != stream_type || format != stream_format) {
continue;
}
resolutions->emplace_back(width, height);
}
std::sort(resolutions->begin(), resolutions->end(),
[](const gfx::Size& a, const gfx::Size& b) -> bool {
return a.width() * a.height() < b.width() * b.height();
});
}
// VideoCaptureDevice::TakePhotoCallback is given by the application and is used
// to return the captured JPEG blob buffer. The second base::OnceClosure is
// created locally by the caller of TakePhoto(), and can be used to, for
// example, restore some settings to the values before TakePhoto() is called to
// facilitate the switch between photo and non-photo modes.
void TakePhotoCallbackBundle(VideoCaptureDevice::TakePhotoCallback callback,
base::OnceClosure on_photo_taken_callback,
mojom::BlobPtr blob) {
std::move(callback).Run(std::move(blob));
std::move(on_photo_taken_callback).Run();
}
void TakeNormalPhotoCallbackBundle(TakePhotoCallback callback,
base::OnceClosure on_photo_taken_callback,
int32_t status,
mojom::BlobPtr blob) {
std::move(callback).Run(status, std::move(blob));
std::move(on_photo_taken_callback).Run();
}
void SetFpsRangeInMetadata(cros::mojom::CameraMetadataPtr* settings,
int32_t min_frame_rate,
int32_t max_frame_rate) {
const int32_t entry_length = 2;
// CameraMetadata is represented as an uint8 array. According to the
// definition of the FPS metadata tag, its data type is int32, so we
// reinterpret_cast here.
std::vector<uint8_t> fps_range(sizeof(int32_t) * entry_length);
auto* fps_ptr = reinterpret_cast<int32_t*>(fps_range.data());
fps_ptr[0] = min_frame_rate;
fps_ptr[1] = max_frame_rate;
cros::mojom::CameraMetadataEntryPtr e =
cros::mojom::CameraMetadataEntry::New();
e->tag = cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AE_TARGET_FPS_RANGE;
e->type = cros::mojom::EntryType::TYPE_INT32;
e->count = entry_length;
e->data = std::move(fps_range);
AddOrUpdateMetadataEntry(settings, std::move(e));
}
} // namespace
PortraitModeCallbacks::PortraitModeCallbacks() = default;
PortraitModeCallbacks::PortraitModeCallbacks(PortraitModeCallbacks&& other)
: normal_photo_callback(std::move(other.normal_photo_callback)),
portrait_photo_callback(std::move(other.portrait_photo_callback)) {}
PortraitModeCallbacks& PortraitModeCallbacks::operator=(
PortraitModeCallbacks&& other) {
normal_photo_callback = std::move(other.normal_photo_callback);
portrait_photo_callback = std::move(other.portrait_photo_callback);
return *this;
}
PortraitModeCallbacks::~PortraitModeCallbacks() = default;
ResultMetadata::ResultMetadata() = default;
ResultMetadata::~ResultMetadata() = default;
StreamType StreamIdToStreamType(uint64_t stream_id) {
switch (static_cast<StreamType>(stream_id)) {
case StreamType::kPreviewOutput:
case StreamType::kJpegOutput:
case StreamType::kPortraitJpegOutput:
case StreamType::kRecordingOutput:
return static_cast<StreamType>(stream_id);
default:
return StreamType::kUnknown;
}
}
std::string StreamTypeToString(StreamType stream_type) {
switch (stream_type) {
case StreamType::kPreviewOutput:
return std::string("StreamType::kPreviewOutput");
case StreamType::kJpegOutput:
return std::string("StreamType::kJpegOutput");
case StreamType::kPortraitJpegOutput:
return std::string("StreamType::kPortraitJpegOutput");
case StreamType::kRecordingOutput:
return std::string("StreamType::kRecordingOutput");
default:
return std::string("Unknown StreamType value: ") +
base::NumberToString(static_cast<int32_t>(stream_type));
}
}
std::ostream& operator<<(std::ostream& os, StreamType stream_type) {
return os << StreamTypeToString(stream_type);
}
StreamCaptureInterface::Plane::Plane() = default;
StreamCaptureInterface::Plane::~Plane() = default;
class CameraDeviceDelegate::StreamCaptureInterfaceImpl final
: public StreamCaptureInterface {
public:
StreamCaptureInterfaceImpl(
base::WeakPtr<CameraDeviceDelegate> camera_device_delegate)
: camera_device_delegate_(std::move(camera_device_delegate)) {}
void ProcessCaptureRequest(cros::mojom::Camera3CaptureRequestPtr request,
base::OnceCallback<void(int32_t)> callback) final {
if (camera_device_delegate_) {
camera_device_delegate_->ProcessCaptureRequest(std::move(request),
std::move(callback));
}
}
void OnNewBuffer(ClientType client_type,
cros::mojom::CameraBufferHandlePtr buffer) final {
if (camera_device_delegate_) {
camera_device_delegate_->OnNewBuffer(client_type, std::move(buffer));
}
}
void OnBufferRetired(ClientType client_type, uint64_t buffer_id) final {
if (camera_device_delegate_) {
camera_device_delegate_->OnBufferRetired(client_type, buffer_id);
}
}
void Flush(base::OnceCallback<void(int32_t)> callback) final {
if (camera_device_delegate_) {
camera_device_delegate_->Flush(std::move(callback));
}
}
private:
const base::WeakPtr<CameraDeviceDelegate> camera_device_delegate_;
};
CameraDeviceDelegate::CameraDeviceDelegate(
VideoCaptureDeviceDescriptor device_descriptor,
CameraHalDelegate* camera_hal_delegate,
scoped_refptr<base::SingleThreadTaskRunner> ipc_task_runner,
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner)
: device_descriptor_(device_descriptor),
camera_hal_delegate_(camera_hal_delegate),
ipc_task_runner_(std::move(ipc_task_runner)) {
if (!ash::features::IsVcWebApiEnabled()) {
return;
}
camera_effects_observer_ = base::SequenceBound<CrosCameraEffectsObserver>(
ui_task_runner,
base::BindPostTask(
ipc_task_runner_,
base::BindRepeating(&CameraDeviceDelegate::OnCameraEffectsChanged,
GetWeakPtr())));
auto_framing_state_observer_ =
base::SequenceBound<CrosCameraAutoFramingStateObserver>(
ui_task_runner,
base::BindPostTask(
ipc_task_runner_,
base::BindRepeating(
&CameraDeviceDelegate::OnAutoFramingStateChanged,
GetWeakPtr())));
}
CameraDeviceDelegate::~CameraDeviceDelegate() = default;
void CameraDeviceDelegate::AllocateAndStart(
const base::flat_map<ClientType, VideoCaptureParams>& params,
CameraDeviceContext* device_context) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
if (device_context->GetState() != CameraDeviceContext::State::kStopped) {
// kError indicates that the device is in use but with some errors, or the
// device failed to open, or the mojo channel terminated unexpectedly.
// Therefore, the device does not need to be opened again when the
// state == kError either.
return;
}
result_metadata_frame_number_for_photo_state_ = 0;
result_metadata_frame_number_ = 0;
is_set_awb_mode_ = false;
is_set_brightness_ = false;
is_set_contrast_ = false;
is_set_exposure_compensation_ = false;
is_set_exposure_time_ = false;
is_set_focus_distance_ = false;
is_set_iso_ = false;
is_set_pan_ = false;
is_set_saturation_ = false;
is_set_sharpness_ = false;
is_set_tilt_ = false;
is_set_zoom_ = false;
chrome_capture_params_ = params;
device_context_ = device_context;
device_context_->SetState(CameraDeviceContext::State::kStarting);
auto camera_app_device =
CameraAppDeviceBridgeImpl::GetInstance()->GetWeakCameraAppDevice(
device_descriptor_.device_id);
if (camera_app_device) {
camera_app_device->SetCameraDeviceContext(device_context_);
}
CameraAppDeviceBridgeImpl::GetInstance()->SetDeviceInUse(
device_descriptor_.device_id, true);
auto camera_info = camera_hal_delegate_->GetCameraInfoFromDeviceId(
device_descriptor_.device_id);
if (camera_info.is_null()) {
device_context_->SetErrorState(
media::VideoCaptureError::kCrosHalV3DeviceDelegateFailedToGetCameraInfo,
FROM_HERE, "Failed to get camera info");
return;
}
device_api_version_ = camera_info->device_version;
SortCameraMetadata(&camera_info->static_camera_characteristics);
static_metadata_ = std::move(camera_info->static_camera_characteristics);
auto sensor_orientation = GetMetadataEntryAsSpan<int32_t>(
static_metadata_,
cros::mojom::CameraMetadataTag::ANDROID_SENSOR_ORIENTATION);
if (sensor_orientation.empty()) {
device_context_->SetErrorState(
media::VideoCaptureError::
kCrosHalV3DeviceDelegateMissingSensorOrientationInfo,
FROM_HERE, "Camera is missing required sensor orientation info");
return;
}
auto rect = GetMetadataEntryAsSpan<int32_t>(
static_metadata_,
cros::mojom::CameraMetadataTag::ANDROID_SENSOR_INFO_ACTIVE_ARRAY_SIZE);
active_array_size_ = gfx::Rect(rect[0], rect[1], rect[2], rect[3]);
device_context_->SetSensorOrientation(sensor_orientation[0]);
// |device_ops_| is bound after the BindNewPipeAndPassReceiver call.
camera_hal_delegate_->OpenDevice(
camera_hal_delegate_->GetCameraIdFromDeviceId(
device_descriptor_.device_id),
device_descriptor_.model_id, device_ops_.BindNewPipeAndPassReceiver(),
base::BindPostTaskToCurrentDefault(
base::BindOnce(&CameraDeviceDelegate::OnOpenedDevice, GetWeakPtr())));
device_ops_.set_disconnect_handler(base::BindOnce(
&CameraDeviceDelegate::OnMojoConnectionError, GetWeakPtr()));
}
void CameraDeviceDelegate::StopAndDeAllocate(
base::OnceClosure device_close_callback) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
CameraAppDeviceBridgeImpl::GetInstance()->SetDeviceInUse(
device_descriptor_.device_id, false);
if (!device_context_ ||
device_context_->GetState() == CameraDeviceContext::State::kStopped ||
(device_context_->GetState() == CameraDeviceContext::State::kError &&
!request_manager_)) {
// In case of Mojo connection error the device may be stopped before
// StopAndDeAllocate is called; in case of device open failure, the state
// is set to kError and |request_manager_| is uninitialized.
std::move(device_close_callback).Run();
return;
}
// StopAndDeAllocate may be called at any state except
// CameraDeviceContext::State::kStopping.
DCHECK_NE(device_context_->GetState(), CameraDeviceContext::State::kStopping);
auto camera_app_device =
CameraAppDeviceBridgeImpl::GetInstance()->GetWeakCameraAppDevice(
device_descriptor_.device_id);
if (camera_app_device) {
camera_app_device->SetCameraDeviceContext(nullptr);
}
device_close_callback_ = std::move(device_close_callback);
device_context_->SetState(CameraDeviceContext::State::kStopping);
if (device_ops_.is_bound()) {
device_ops_->Close(
base::BindOnce(&CameraDeviceDelegate::OnClosed, GetWeakPtr()));
}
// |request_manager_| might be still uninitialized if StopAndDeAllocate() is
// called before the device is opened.
if (request_manager_) {
request_manager_->StopPreview(base::NullCallback());
}
}
void CameraDeviceDelegate::TakePhoto(
VideoCaptureDevice::TakePhotoCallback callback) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
take_photo_callbacks_.push(std::move(callback));
if (!device_context_ ||
(device_context_->GetState() !=
CameraDeviceContext::State::kStreamConfigured &&
device_context_->GetState() != CameraDeviceContext::State::kCapturing)) {
return;
}
TakePhotoImpl(cros::mojom::Effect::kNoEffect);
}
void CameraDeviceDelegate::GetPhotoState(
VideoCaptureDevice::GetPhotoStateCallback callback) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
if (result_metadata_frame_number_ <=
result_metadata_frame_number_for_photo_state_) {
get_photo_state_queue_.push_back(
base::BindOnce(&CameraDeviceDelegate::DoGetPhotoState,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
return;
}
DoGetPhotoState(std::move(callback));
}
// On success, invokes |callback| with value |true|. On failure, drops
// callback without invoking it.
//
// https://www.w3.org/TR/mediacapture-streams/#dfn-applyconstraints-algorithm
// In a single operation, remove the existing constraints from object, apply
// newConstraints, and apply successfulSettings as the current settings.
void CameraDeviceDelegate::SetPhotoOptions(
mojom::PhotoSettingsPtr settings,
VideoCaptureDevice::SetPhotoOptionsCallback callback) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
if (device_context_->GetState() != CameraDeviceContext::State::kCapturing) {
return;
}
bool ret = SetPointsOfInterest(settings->points_of_interest);
if (!ret) {
LOG(ERROR) << "Failed to set points of interest";
return;
}
// Abort if background blur does not have already the desired value.
if (settings->has_background_blur_mode &&
(!ash::features::IsVcWebApiEnabled() || current_effects_.is_null() ||
settings->background_blur_mode !=
(current_effects_->blur_enabled ? mojom::BackgroundBlurMode::BLUR
: mojom::BackgroundBlurMode::OFF))) {
return;
}
// Set the vendor tag into with given |name| and |value|. Returns true if
// the vendor tag is set and false otherwise.
auto to_uint8_vector = [](int32_t value) {
std::vector<uint8_t> temp(sizeof(int32_t));
auto* temp_ptr = reinterpret_cast<int32_t*>(temp.data());
*temp_ptr = value;
return temp;
};
auto set_vendor_int = [&](const std::string& name, bool has_field,
double value, bool is_set) {
const VendorTagInfo* info =
camera_hal_delegate_->GetVendorTagInfoByName(name);
if (info == nullptr || !has_field) {
if (is_set) {
request_manager_->UnsetRepeatingCaptureMetadata(info->tag);
}
return false;
}
request_manager_->SetRepeatingCaptureMetadata(info->tag, info->type, 1,
to_uint8_vector(value));
return true;
};
is_set_brightness_ = set_vendor_int(kBrightness, settings->has_brightness,
settings->brightness, is_set_brightness_);
is_set_contrast_ = set_vendor_int(kContrast, settings->has_contrast,
settings->contrast, is_set_contrast_);
is_set_pan_ =
set_vendor_int(kPan, settings->has_pan, settings->pan, is_set_pan_);
is_set_saturation_ = set_vendor_int(kSaturation, settings->has_saturation,
settings->saturation, is_set_saturation_);
is_set_sharpness_ = set_vendor_int(kSharpness, settings->has_sharpness,
settings->sharpness, is_set_sharpness_);
is_set_tilt_ =
set_vendor_int(kTilt, settings->has_tilt, settings->tilt, is_set_tilt_);
if (use_digital_zoom_) {
if (settings->has_zoom && settings->zoom != 1) {
double zoom_factor = sqrt(settings->zoom);
int32_t crop_width = std::round(active_array_size_.width() / zoom_factor);
int32_t crop_height =
std::round(active_array_size_.height() / zoom_factor);
// crop from center
int32_t region[4] = {(active_array_size_.width() - crop_width) / 2,
(active_array_size_.height() - crop_height) / 2,
crop_width, crop_height};
request_manager_->SetRepeatingCaptureMetadata(
cros::mojom::CameraMetadataTag::ANDROID_SCALER_CROP_REGION,
cros::mojom::EntryType::TYPE_INT32, std::size(region),
SerializeMetadataValueFromSpan<int32_t>(region));
VLOG(1) << "zoom ratio:" << settings->zoom << " scaler.crop.region("
<< region[0] << "," << region[1] << "," << region[2] << ","
<< region[3] << ")";
is_set_zoom_ = true;
} else if (is_set_zoom_) {
request_manager_->UnsetRepeatingCaptureMetadata(
cros::mojom::CameraMetadataTag::ANDROID_SCALER_CROP_REGION);
is_set_zoom_ = false;
}
} else {
is_set_zoom_ =
set_vendor_int(kZoom, settings->has_zoom, settings->zoom, is_set_zoom_);
}
if (settings->has_white_balance_mode &&
settings->white_balance_mode == mojom::MeteringMode::MANUAL &&
settings->has_color_temperature) {
const AwbModeTemperatureMap& map = GetAwbModeTemperatureMap();
cros::mojom::AndroidControlAwbMode awb_mode =
cros::mojom::AndroidControlAwbMode::ANDROID_CONTROL_AWB_MODE_AUTO;
auto awb_available_modes = GetMetadataEntryAsSpan<uint8_t>(
static_metadata_,
cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AWB_AVAILABLE_MODES);
int32_t min_diff = std::numeric_limits<int32_t>::max();
for (const auto& mode : awb_available_modes) {
auto it = map.find(mode);
// Find the nearest awb mode
int32_t diff = std::abs(settings->color_temperature - it->second);
if (diff < min_diff) {
awb_mode = static_cast<cros::mojom::AndroidControlAwbMode>(mode);
min_diff = diff;
}
}
camera_3a_controller_->SetAutoWhiteBalanceMode(awb_mode);
is_set_awb_mode_ = true;
} else if (is_set_awb_mode_) {
camera_3a_controller_->SetAutoWhiteBalanceMode(
cros::mojom::AndroidControlAwbMode::ANDROID_CONTROL_AWB_MODE_AUTO);
is_set_awb_mode_ = false;
}
if (settings->has_exposure_mode &&
settings->exposure_mode == mojom::MeteringMode::MANUAL &&
settings->has_exposure_time) {
int64_t exposure_time_nanoseconds_ =
settings->exposure_time * 100 * kMicroToNano;
camera_3a_controller_->SetExposureTime(false, exposure_time_nanoseconds_);
is_set_exposure_time_ = true;
} else if (is_set_exposure_time_) {
camera_3a_controller_->SetExposureTime(true, 0);
is_set_exposure_time_ = false;
}
if (settings->has_focus_mode &&
settings->focus_mode == mojom::MeteringMode::MANUAL &&
settings->has_focus_distance) {
// The unit of settings is meter but it is diopter of android metadata.
float focus_distance_diopters_ = 1.0 / settings->focus_distance;
camera_3a_controller_->SetFocusDistance(false, focus_distance_diopters_);
is_set_focus_distance_ = true;
} else if (is_set_focus_distance_) {
camera_3a_controller_->SetFocusDistance(true, 0);
is_set_focus_distance_ = false;
}
if (settings->has_iso) {
request_manager_->SetRepeatingCaptureMetadata(
cros::mojom::CameraMetadataTag::ANDROID_SENSOR_SENSITIVITY,
cros::mojom::EntryType::TYPE_INT32, 1, to_uint8_vector(settings->iso));
is_set_iso_ = true;
if (!is_set_exposure_time_) {
LOG(WARNING) << "set iso doesn't work due to auto exposure time";
}
} else if (is_set_iso_) {
request_manager_->UnsetRepeatingCaptureMetadata(
cros::mojom::CameraMetadataTag::ANDROID_SENSOR_SENSITIVITY);
is_set_iso_ = false;
}
if (settings->has_exposure_compensation) {
int metadata_exposure_compensation =
std::round(settings->exposure_compensation / ae_compensation_step_);
request_manager_->SetRepeatingCaptureMetadata(
cros::mojom::CameraMetadataTag::
ANDROID_CONTROL_AE_EXPOSURE_COMPENSATION,
cros::mojom::EntryType::TYPE_INT32, 1,
to_uint8_vector(metadata_exposure_compensation));
is_set_exposure_compensation_ = true;
} else if (is_set_exposure_compensation_) {
request_manager_->UnsetRepeatingCaptureMetadata(
cros::mojom::CameraMetadataTag::
ANDROID_CONTROL_AE_EXPOSURE_COMPENSATION);
is_set_exposure_compensation_ = false;
}
// If there is callback of SetPhotoOptions(), the streams might being
// reconfigured and we should notify them once the reconfiguration is done.
auto on_reconfigured_callback = base::BindOnce(
[](VideoCaptureDevice::SetPhotoOptionsCallback callback) {
std::move(callback).Run(true);
},
std::move(callback));
if (!on_reconfigured_callbacks_.empty()) {
on_reconfigured_callbacks_.push(std::move(on_reconfigured_callback));
} else {
if (MaybeReconfigureForPhotoStream(std::move(settings))) {
on_reconfigured_callbacks_.push(std::move(on_reconfigured_callback));
} else {
std::move(on_reconfigured_callback).Run();
}
}
result_metadata_frame_number_for_photo_state_ = current_request_frame_number_;
}
void CameraDeviceDelegate::ReconfigureStreams(
const base::flat_map<ClientType, VideoCaptureParams>& params) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
chrome_capture_params_ = params;
if (request_manager_) {
request_manager_->StopPreview(
base::BindOnce(&CameraDeviceDelegate::OnFlushed, GetWeakPtr(),
ShouldUseBlobVideoSnapshot(), std::nullopt));
}
}
void CameraDeviceDelegate::SetRotation(int rotation) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
DCHECK(rotation >= 0 && rotation < 360 && rotation % 90 == 0);
if (!device_context_) {
return;
}
device_context_->SetScreenRotation(rotation);
}
base::WeakPtr<CameraDeviceDelegate> CameraDeviceDelegate::GetWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
bool CameraDeviceDelegate::MaybeReconfigureForPhotoStream(
mojom::PhotoSettingsPtr settings) {
bool is_resolution_specified = settings->has_width && settings->has_height;
bool should_reconfigure_streams =
(is_resolution_specified && (current_blob_resolution_.IsEmpty() ||
current_blob_resolution_.width() !=
static_cast<int32_t>(settings->width) ||
current_blob_resolution_.height() !=
static_cast<int32_t>(settings->height)));
if (!should_reconfigure_streams) {
return false;
}
if (is_resolution_specified) {
gfx::Size new_blob_resolution(static_cast<int32_t>(settings->width),
static_cast<int32_t>(settings->height));
request_manager_->StopPreview(
base::BindOnce(&CameraDeviceDelegate::OnFlushed, GetWeakPtr(), true,
std::move(new_blob_resolution)));
} else {
request_manager_->StopPreview(base::BindOnce(
&CameraDeviceDelegate::OnFlushed, GetWeakPtr(), true, std::nullopt));
}
return true;
}
void CameraDeviceDelegate::TakePhotoImpl(cros::mojom::Effect effect) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
StreamType request_stream_type =
(effect == cros::mojom::Effect::kPortraitMode)
? StreamType::kPortraitJpegOutput
: StreamType::kJpegOutput;
auto construct_request_cb =
base::BindOnce(&CameraDeviceDelegate::ConstructDefaultRequestSettings,
GetWeakPtr(), request_stream_type);
if (request_manager_->HasStreamsConfiguredForTakePhoto()) {
auto camera_app_device =
CameraAppDeviceBridgeImpl::GetInstance()->GetWeakCameraAppDevice(
device_descriptor_.device_id);
// Skip 3A stabilization when taking video snapshot to avoid disrupting the
// video recording.
bool should_skip_3a = ShouldUseBlobVideoSnapshot() && camera_app_device &&
camera_app_device->GetCaptureIntent() ==
cros::mojom::CaptureIntent::kVideoRecord;
if (should_skip_3a) {
std::move(construct_request_cb).Run();
} else {
camera_3a_controller_->Stabilize3AForStillCapture(
std::move(construct_request_cb));
}
return;
}
// Trigger the reconfigure process if it is required and is not yet triggered.
if (current_blob_resolution_.IsEmpty() &&
on_reconfigured_callbacks_.empty()) {
request_manager_->StopPreview(base::BindOnce(
&CameraDeviceDelegate::OnFlushed, GetWeakPtr(), true, std::nullopt));
}
auto on_reconfigured_callback = base::BindOnce(
[](base::WeakPtr<Camera3AController> controller,
base::OnceClosure callback) {
controller->Stabilize3AForStillCapture(std::move(callback));
},
camera_3a_controller_->GetWeakPtr(), std::move(construct_request_cb));
on_reconfigured_callbacks_.push(std::move(on_reconfigured_callback));
}
void CameraDeviceDelegate::OnMojoConnectionError() {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
if (device_context_->GetState() == CameraDeviceContext::State::kStopping) {
// When in stopping state the camera HAL adapter may terminate the Mojo
// channel before we do, in which case the OnClosed callback will not be
// called.
OnClosed(0);
} else {
// The Mojo channel terminated unexpectedly.
if (request_manager_) {
request_manager_->StopPreview(base::NullCallback());
}
device_context_->SetState(CameraDeviceContext::State::kStopped);
device_context_->SetErrorState(
media::VideoCaptureError::kCrosHalV3DeviceDelegateMojoConnectionError,
FROM_HERE, "Mojo connection error");
ResetMojoInterface();
// We cannot reset |device_context_| here because
// |device_context_->SetErrorState| above will call StopAndDeAllocate later
// to handle the class destruction.
}
}
void CameraDeviceDelegate::OnFlushed(
bool require_photo,
std::optional<gfx::Size> new_blob_resolution,
int32_t result) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
if (result) {
device_context_->SetErrorState(
media::VideoCaptureError::kCrosHalV3DeviceDelegateFailedToFlush,
FROM_HERE,
std::string("Flush failed: ") + base::safe_strerror(-result));
return;
}
device_context_->SetState(CameraDeviceContext::State::kInitialized);
ConfigureStreams(require_photo, std::move(new_blob_resolution));
}
void CameraDeviceDelegate::OnClosed(int32_t result) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
DCHECK_EQ(device_context_->GetState(), CameraDeviceContext::State::kStopping);
device_context_->SetState(CameraDeviceContext::State::kStopped);
if (result) {
device_context_->LogToClient(std::string("Failed to close device: ") +
base::safe_strerror(-result));
}
if (request_manager_) {
request_manager_->RemoveResultMetadataObserver(this);
}
ResetMojoInterface();
device_context_ = nullptr;
current_blob_resolution_.SetSize(0, 0);
std::move(device_close_callback_).Run();
}
void CameraDeviceDelegate::ResetMojoInterface() {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
device_ops_.reset();
camera_3a_controller_.reset();
request_manager_.reset();
}
void CameraDeviceDelegate::OnOpenedDevice(int32_t result) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
if (device_context_ == nullptr) {
// The OnOpenedDevice() might be called after the camera device already
// closed by StopAndDeAllocate(), because |camera_hal_delegate_| and
// |device_ops_| are on different IPC channels. In such case,
// OnMojoConnectionError() might call OnClosed() and the
// |device_context_| is cleared, so we just return here.
return;
}
if (device_context_->GetState() != CameraDeviceContext::State::kStarting) {
if (device_context_->GetState() == CameraDeviceContext::State::kError) {
// In case of camera open failed, the HAL can terminate the Mojo channel
// before we do and set the state to kError in OnMojoConnectionError.
return;
}
DCHECK_EQ(device_context_->GetState(),
CameraDeviceContext::State::kStopping);
OnClosed(0);
return;
}
if (result) {
device_context_->SetErrorState(
media::VideoCaptureError::
kCrosHalV3DeviceDelegateFailedToOpenCameraDevice,
FROM_HERE, "Failed to open camera device");
return;
}
Initialize();
}
void CameraDeviceDelegate::Initialize() {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
DCHECK_EQ(device_context_->GetState(), CameraDeviceContext::State::kStarting);
bool use_buffer_management_apis = false;
if (device_api_version_ >= cros::mojom::CAMERA_DEVICE_API_VERSION_3_6) {
auto version = GetMetadataEntryAsSpan<uint8_t>(
static_metadata_, cros::mojom::CameraMetadataTag::
ANDROID_INFO_SUPPORTED_BUFFER_MANAGEMENT_VERSION);
use_buffer_management_apis =
version.size() == 1 &&
version[0] ==
static_cast<uint8_t>(
cros::mojom::AndroidInfoSupportedBufferManagementVersion::
ANDROID_INFO_SUPPORTED_BUFFER_MANAGEMENT_VERSION_HIDL_DEVICE_3_5);
}
mojo::PendingRemote<cros::mojom::Camera3CallbackOps> callback_ops;
// Assumes the buffer_type will be the same for all |chrome_capture_params|.
request_manager_ = std::make_unique<RequestManager>(
device_descriptor_.device_id,
callback_ops.InitWithNewPipeAndPassReceiver(),
std::make_unique<StreamCaptureInterfaceImpl>(GetWeakPtr()),
device_context_,
chrome_capture_params_[ClientType::kPreviewClient].buffer_type,
std::make_unique<CameraBufferFactory>(),
base::BindRepeating(&RotateAndBlobify), ipc_task_runner_,
device_api_version_, use_buffer_management_apis);
camera_3a_controller_ = std::make_unique<Camera3AController>(
static_metadata_, request_manager_.get(), ipc_task_runner_);
device_ops_->Initialize(
std::move(callback_ops),
base::BindOnce(&CameraDeviceDelegate::OnInitialized, GetWeakPtr()));
request_manager_->AddResultMetadataObserver(this);
// For Intel IPU6 platform, set power mode to high quality for CCA and low
// power mode for others.
const VendorTagInfo* info =
camera_hal_delegate_->GetVendorTagInfoByName(kIntelPowerMode);
if (info != nullptr) {
// TODO(b/284403009): Currently we cannot check which device_id opens CCA
bool is_cca_open =
CameraAppDeviceBridgeImpl::GetInstance()->GetWeakCameraAppDevice(
device_descriptor_.device_id) != nullptr;
uint8_t power_mode =
is_cca_open ? kIntelPowerModeHighQuality : kIntelPowerModeLowPower;
request_manager_->SetRepeatingCaptureMetadata(
info->tag, info->type, 1, std::vector<uint8_t>{power_mode});
}
}
void CameraDeviceDelegate::OnInitialized(int32_t result) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
if (device_context_->GetState() != CameraDeviceContext::State::kStarting) {
DCHECK_EQ(device_context_->GetState(),
CameraDeviceContext::State::kStopping);
return;
}
if (result) {
device_context_->SetErrorState(
media::VideoCaptureError::
kCrosHalV3DeviceDelegateFailedToInitializeCameraDevice,
FROM_HERE,
std::string("Failed to initialize camera device: ") +
base::safe_strerror(-result));
return;
}
device_context_->SetState(CameraDeviceContext::State::kInitialized);
bool require_photo = [&] {
auto camera_app_device =
CameraAppDeviceBridgeImpl::GetInstance()->GetWeakCameraAppDevice(
device_descriptor_.device_id);
if (!camera_app_device) {
return false;
}
auto capture_intent = camera_app_device->GetCaptureIntent();
switch (capture_intent) {
case cros::mojom::CaptureIntent::kDefault:
return false;
case cros::mojom::CaptureIntent::kStillCapture:
case cros::mojom::CaptureIntent::kPortraitCapture:
return true;
case cros::mojom::CaptureIntent::kVideoRecord:
return ShouldUseBlobVideoSnapshot();
default:
NOTREACHED_IN_MIGRATION()
<< "Unknown capture intent: " << capture_intent;
return false;
}
}();
ConfigureStreams(require_photo, std::nullopt);
}
void CameraDeviceDelegate::ConfigureStreams(
bool require_photo,
std::optional<gfx::Size> new_blob_resolution) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
DCHECK_EQ(device_context_->GetState(),
CameraDeviceContext::State::kInitialized);
TRACE_EVENT_BEGIN("camera", "ConfigureStreams",
GetTraceTrack(CameraTraceEvent::kConfigureStreams));
cros::mojom::Camera3StreamConfigurationPtr stream_config =
cros::mojom::Camera3StreamConfiguration::New();
auto camera_app_device =
CameraAppDeviceBridgeImpl::GetInstance()->GetWeakCameraAppDevice(
device_descriptor_.device_id);
for (const auto& param : chrome_capture_params_) {
// Set up context for preview stream and record stream.
cros::mojom::Camera3StreamPtr stream = cros::mojom::Camera3Stream::New();
StreamType stream_type = (param.first == ClientType::kPreviewClient)
? StreamType::kPreviewOutput
: StreamType::kRecordingOutput;
uint32_t usage;
switch (param.first) {
case ClientType::kPreviewClient:
usage = cros::mojom::GRALLOC_USAGE_HW_COMPOSER;
if (camera_app_device &&
camera_app_device->GetCaptureIntent() ==
cros::mojom::CaptureIntent::kVideoRecord &&
!camera_app_device->IsMultipleStreamsEnabled()) {
usage |= cros::mojom::GRALLOC_USAGE_HW_VIDEO_ENCODER;
}
break;
case ClientType::kVideoClient:
usage = cros::mojom::GRALLOC_USAGE_HW_VIDEO_ENCODER;
break;
default:
NOTREACHED_IN_MIGRATION()
<< "Unrecognized client type: " << static_cast<int>(param.first);
}
stream->id = static_cast<uint64_t>(stream_type);
stream->stream_type = cros::mojom::Camera3StreamType::CAMERA3_STREAM_OUTPUT;
stream->width = param.second.requested_format.frame_size.width();
stream->height = param.second.requested_format.frame_size.height();
stream->format =
cros::mojom::HalPixelFormat::HAL_PIXEL_FORMAT_YCbCr_420_888;
stream->usage = usage;
stream->data_space = 0;
stream->rotation =
cros::mojom::Camera3StreamRotation::CAMERA3_STREAM_ROTATION_0;
if (device_api_version_ >= cros::mojom::CAMERA_DEVICE_API_VERSION_3_5) {
stream->physical_camera_id = "";
}
stream_config->streams.push_back(std::move(stream));
}
// Set up context for still capture stream. We set still capture stream to the
// JPEG stream configuration with maximum supported resolution.
int32_t blob_width = 0;
int32_t blob_height = 0;
if (require_photo) {
auto blob_resolution = GetBlobResolution(new_blob_resolution);
if (blob_resolution.IsEmpty()) {
LOG(ERROR) << "Failed to configure streans: No BLOB resolution found.";
return;
}
blob_width = blob_resolution.width();
blob_height = blob_resolution.height();
cros::mojom::Camera3StreamPtr still_capture_stream =
cros::mojom::Camera3Stream::New();
still_capture_stream->id = static_cast<uint64_t>(StreamType::kJpegOutput);
still_capture_stream->stream_type =
cros::mojom::Camera3StreamType::CAMERA3_STREAM_OUTPUT;
still_capture_stream->width = blob_width;
still_capture_stream->height = blob_height;
still_capture_stream->format =
cros::mojom::HalPixelFormat::HAL_PIXEL_FORMAT_BLOB;
still_capture_stream->data_space = 0;
still_capture_stream->rotation =
cros::mojom::Camera3StreamRotation::CAMERA3_STREAM_ROTATION_0;
if (device_api_version_ >= cros::mojom::CAMERA_DEVICE_API_VERSION_3_5) {
still_capture_stream->physical_camera_id = "";
}
stream_config->streams.push_back(std::move(still_capture_stream));
if (camera_app_device && camera_app_device->GetCaptureIntent() ==
cros::mojom::CaptureIntent::kPortraitCapture) {
auto portrait_mode_stream = cros::mojom::Camera3Stream::New();
portrait_mode_stream->id =
static_cast<uint64_t>(StreamType::kPortraitJpegOutput);
portrait_mode_stream->stream_type =
cros::mojom::Camera3StreamType::CAMERA3_STREAM_OUTPUT;
portrait_mode_stream->width = blob_width;
portrait_mode_stream->height = blob_height;
portrait_mode_stream->format =
cros::mojom::HalPixelFormat::HAL_PIXEL_FORMAT_BLOB;
portrait_mode_stream->data_space = 0;
portrait_mode_stream->rotation =
cros::mojom::Camera3StreamRotation::CAMERA3_STREAM_ROTATION_0;
if (device_api_version_ >= cros::mojom::CAMERA_DEVICE_API_VERSION_3_5) {
portrait_mode_stream->physical_camera_id = "";
}
portrait_mode_stream->effects =
std::vector<cros::mojom::Camera3StreamEffectPtr>();
cros::mojom::PortraitModeConfigPtr portrait_mode_config =
cros::mojom::PortraitModeConfig::New();
portrait_mode_config->enable_rectiface = false;
cros::mojom::Camera3StreamEffectPtr stream_effect =
cros::mojom::Camera3StreamEffect::NewPortraitModeConfig(
std::move(portrait_mode_config));
portrait_mode_stream->effects->push_back(std::move(stream_effect));
stream_config->streams.push_back(std::move(portrait_mode_stream));
}
}
stream_config->operation_mode = cros::mojom::Camera3StreamConfigurationMode::
CAMERA3_STREAM_CONFIGURATION_NORMAL_MODE;
if (device_api_version_ >= cros::mojom::CAMERA_DEVICE_API_VERSION_3_5) {
stream_config->session_parameters = cros::mojom::CameraMetadata::New();
ConfigureSessionParameters(&stream_config->session_parameters);
// TODO(b/336480993): Enable digital zoom in portrait mode.
// TODO(b/225112054): Remove the check for Chrome flag once the feature is
// enabled by default.
bool request_digital_zoom =
camera_app_device != nullptr &&
base::FeatureList::IsEnabled(ash::features::kCameraAppDigitalZoom) &&
camera_app_device->GetCaptureIntent() !=
cros::mojom::CaptureIntent::kPortraitCapture;
if (request_digital_zoom) {
SetDigitalZoomSessionParameters(&stream_config->session_parameters);
}
}
device_ops_->ConfigureStreams(
std::move(stream_config),
base::BindOnce(&CameraDeviceDelegate::OnConfiguredStreams, GetWeakPtr(),
gfx::Size(blob_width, blob_height)));
}
void CameraDeviceDelegate::OnConfiguredStreams(
gfx::Size blob_resolution,
int32_t result,
cros::mojom::Camera3StreamConfigurationPtr updated_config) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
TRACE_EVENT_END("camera", GetTraceTrack(CameraTraceEvent::kConfigureStreams));
if (device_context_->GetState() != CameraDeviceContext::State::kInitialized) {
DCHECK_EQ(device_context_->GetState(),
CameraDeviceContext::State::kStopping);
return;
}
if (result) {
device_context_->SetErrorState(
media::VideoCaptureError::
kCrosHalV3DeviceDelegateFailedToConfigureStreams,
FROM_HERE,
std::string("Failed to configure streams: ") +
base::safe_strerror(-result));
return;
}
if (!updated_config ||
updated_config->streams.size() > kMaxConfiguredStreams ||
updated_config->streams.size() < kMinConfiguredStreams) {
device_context_->SetErrorState(
media::VideoCaptureError::
kCrosHalV3DeviceDelegateWrongNumberOfStreamsConfigured,
FROM_HERE,
std::string("Wrong number of streams configured: ") +
base::NumberToString(updated_config->streams.size()));
return;
}
current_blob_resolution_.SetSize(blob_resolution.width(),
blob_resolution.height());
request_manager_->SetUpStreamsAndBuffers(chrome_capture_params_,
static_metadata_,
std::move(updated_config->streams));
device_context_->SetState(CameraDeviceContext::State::kStreamConfigured);
// Kick off the preview stream.
ConstructDefaultRequestSettings(StreamType::kPreviewOutput);
}
void CameraDeviceDelegate::ConstructDefaultRequestSettings(
StreamType stream_type) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
DCHECK(device_context_->GetState() ==
CameraDeviceContext::State::kStreamConfigured ||
device_context_->GetState() == CameraDeviceContext::State::kCapturing);
auto camera_app_device =
CameraAppDeviceBridgeImpl::GetInstance()->GetWeakCameraAppDevice(
device_descriptor_.device_id);
if (stream_type == StreamType::kPreviewOutput) {
// CCA uses the same stream for preview and video recording. Choose proper
// template here so the underlying camera HAL can set 3A tuning accordingly.
auto request_template =
camera_app_device && camera_app_device->GetCaptureIntent() ==
cros::mojom::CaptureIntent::kVideoRecord
? cros::mojom::Camera3RequestTemplate::CAMERA3_TEMPLATE_VIDEO_RECORD
: cros::mojom::Camera3RequestTemplate::CAMERA3_TEMPLATE_PREVIEW;
device_ops_->ConstructDefaultRequestSettings(
request_template,
base::BindOnce(
&CameraDeviceDelegate::OnConstructedDefaultPreviewRequestSettings,
GetWeakPtr()));
} else if (stream_type == StreamType::kJpegOutput) {
auto request_template =
camera_app_device && camera_app_device->GetCaptureIntent() ==
cros::mojom::CaptureIntent::kVideoRecord
? cros::mojom::Camera3RequestTemplate::
CAMERA3_TEMPLATE_VIDEO_SNAPSHOT
: cros::mojom::Camera3RequestTemplate::
CAMERA3_TEMPLATE_STILL_CAPTURE;
device_ops_->ConstructDefaultRequestSettings(
request_template,
base::BindOnce(&CameraDeviceDelegate::
OnConstructedDefaultStillCaptureRequestSettings,
GetWeakPtr(), request_template));
} else if (stream_type == StreamType::kPortraitJpegOutput) {
auto request_template =
cros::mojom::Camera3RequestTemplate::CAMERA3_TEMPLATE_STILL_CAPTURE;
device_ops_->ConstructDefaultRequestSettings(
request_template,
base::BindOnce(&CameraDeviceDelegate::
OnConstructedDefaultPortraitModeRequestSettings,
GetWeakPtr()));
} else {
NOTREACHED_IN_MIGRATION()
<< "No default request settings for stream: " << stream_type;
}
}
void CameraDeviceDelegate::OnConstructedDefaultPreviewRequestSettings(
cros::mojom::CameraMetadataPtr settings) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
if (device_context_->GetState() !=
CameraDeviceContext::State::kStreamConfigured) {
DCHECK_EQ(device_context_->GetState(),
CameraDeviceContext::State::kStopping);
return;
}
if (!settings) {
device_context_->SetErrorState(
media::VideoCaptureError::
kCrosHalV3DeviceDelegateFailedToGetDefaultRequestSettings,
FROM_HERE, "Failed to get default request settings");
return;
}
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
device_context_->SetState(CameraDeviceContext::State::kCapturing);
camera_3a_controller_->SetAutoFocusModeForStillCapture();
auto [target_min, target_max] = GetFrameRateRange();
if (target_min == 0 || target_max == 0) {
device_context_->SetErrorState(
media::VideoCaptureError::
kCrosHalV3DeviceDelegateFailedToGetDefaultRequestSettings,
FROM_HERE, "Failed to get valid frame rate range");
return;
}
SetFpsRangeInMetadata(&settings, target_min, target_max);
while (!on_reconfigured_callbacks_.empty()) {
std::move(on_reconfigured_callbacks_.front()).Run();
on_reconfigured_callbacks_.pop();
}
request_manager_->StartPreview(std::move(settings));
if (!take_photo_callbacks_.empty()) {
TakePhotoImpl(cros::mojom::Effect::kNoEffect);
}
}
void CameraDeviceDelegate::OnConstructedDefaultStillCaptureRequestSettings(
cros::mojom::Camera3RequestTemplate requset_template,
cros::mojom::CameraMetadataPtr settings) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
if (requset_template ==
cros::mojom::Camera3RequestTemplate::CAMERA3_TEMPLATE_STILL_CAPTURE) {
const VendorTagInfo* info =
camera_hal_delegate_->GetVendorTagInfoByName(kLibcameraStillCaptureMFNR);
if (info != nullptr) {
auto e = BuildMetadataEntry(info->tag, uint8_t{1});
AddOrUpdateMetadataEntry(&settings, std::move(e));
}
}
while (!take_photo_callbacks_.empty()) {
auto take_photo_callback = base::BindOnce(
&TakePhotoCallbackBundle, std::move(take_photo_callbacks_.front()),
base::BindOnce(&Camera3AController::SetAutoFocusModeForStillCapture,
camera_3a_controller_->GetWeakPtr()));
request_manager_->TakePhoto(settings.Clone(),
std::move(take_photo_callback));
take_photo_callbacks_.pop();
}
}
void CameraDeviceDelegate::OnConstructedDefaultPortraitModeRequestSettings(
cros::mojom::CameraMetadataPtr settings) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
TakePhotoCallbackMap callback_map;
if (take_portrait_photo_callbacks_.has_value()) {
callback_map[StreamType::kJpegOutput] = base::BindOnce(
&TakeNormalPhotoCallbackBundle,
std::move(take_portrait_photo_callbacks_->normal_photo_callback),
base::BindOnce(&Camera3AController::SetAutoFocusModeForStillCapture,
camera_3a_controller_->GetWeakPtr()));
callback_map[StreamType::kPortraitJpegOutput] =
std::move(take_portrait_photo_callbacks_->portrait_photo_callback);
request_manager_->TakePortraitPhoto(settings.Clone(),
std::move(callback_map));
take_portrait_photo_callbacks_.reset();
}
}
gfx::Size CameraDeviceDelegate::GetBlobResolution(
std::optional<gfx::Size> new_blob_resolution) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
std::vector<gfx::Size> blob_resolutions;
GetStreamResolutions(
static_metadata_, cros::mojom::Camera3StreamType::CAMERA3_STREAM_OUTPUT,
cros::mojom::HalPixelFormat::HAL_PIXEL_FORMAT_BLOB, &blob_resolutions);
if (blob_resolutions.empty()) {
return {};
}
// Try the given blob resolution first. If it is invalid, try the
// resolution specified through Mojo API as a fallback. If it fails too,
// use the largest resolution as default.
if (new_blob_resolution.has_value() &&
base::Contains(blob_resolutions, *new_blob_resolution)) {
return *new_blob_resolution;
}
auto camera_app_device =
CameraAppDeviceBridgeImpl::GetInstance()->GetWeakCameraAppDevice(
device_descriptor_.device_id);
if (camera_app_device) {
auto specified_capture_resolution =
camera_app_device->GetStillCaptureResolution();
if (!specified_capture_resolution.IsEmpty() &&
base::Contains(blob_resolutions, specified_capture_resolution)) {
return specified_capture_resolution;
}
}
return blob_resolutions.back();
}
void CameraDeviceDelegate::ProcessCaptureRequest(
cros::mojom::Camera3CaptureRequestPtr request,
base::OnceCallback<void(int32_t)> callback) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
TRACE_EVENT_BEGIN(
"camera", "Capture Request",
GetTraceTrack(CameraTraceEvent::kCaptureRequest, request->frame_number),
"frame_number", request->frame_number);
for (const auto& output_buffer : request->output_buffers) {
TRACE_EVENT_BEGIN(
"camera", "Capture Stream",
GetTraceTrack(CameraTraceEvent::kCaptureStream, request->frame_number,
output_buffer->stream_id),
"frame_number", request->frame_number, "stream_id",
output_buffer->stream_id);
}
current_request_frame_number_ = request->frame_number;
if (device_context_->GetState() != CameraDeviceContext::State::kCapturing) {
DCHECK_EQ(device_context_->GetState(),
CameraDeviceContext::State::kStopping);
return;
}
// Check if we have pending portrait mode callbacks in CameraAppDevice.
auto camera_app_device =
CameraAppDeviceBridgeImpl::GetInstance()->GetWeakCameraAppDevice(
device_descriptor_.device_id);
auto take_portrait_photo_callbacks =
camera_app_device ? camera_app_device->ConsumePortraitModeCallbacks()
: std::nullopt;
if (take_portrait_photo_callbacks.has_value()) {
take_portrait_photo_callbacks_ = std::move(take_portrait_photo_callbacks);
TakePhotoImpl(cros::mojom::Effect::kPortraitMode);
}
device_ops_->ProcessCaptureRequest(std::move(request), std::move(callback));
}
void CameraDeviceDelegate::Flush(base::OnceCallback<void(int32_t)> callback) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
device_ops_->Flush(std::move(callback));
}
bool CameraDeviceDelegate::SetPointsOfInterest(
const std::vector<mojom::Point2DPtr>& points_of_interest) {
if (points_of_interest.empty()) {
return true;
}
if (!camera_3a_controller_->IsPointOfInterestSupported()) {
LOG(WARNING) << "Point of interest is not supported on this device.";
return false;
}
if (points_of_interest.size() > 1) {
LOG(WARNING) << "Setting more than one point of interest is not "
"supported, only the first one is used.";
}
// A Point2D Point of Interest is interpreted to represent a pixel position in
// a normalized square space (|{x,y} ∈ [0.0, 1.0]|). The origin of coordinates
// |{x,y} = {0.0, 0.0}| represents the upper leftmost corner whereas the
// |{x,y} = {1.0, 1.0}| represents the lower rightmost corner: the x
// coordinate (columns) increases rightwards and the y coordinate (rows)
// increases downwards. Values beyond the mentioned limits will be clamped to
// the closest allowed value.
// ref: https://www.w3.org/TR/image-capture/#points-of-interest
double x = std::clamp(points_of_interest[0]->x, 0.0, 1.0);
double y = std::clamp(points_of_interest[0]->y, 0.0, 1.0);
// Handle rotation, still in normalized square space.
std::tie(x, y) = [&]() -> std::pair<double, double> {
switch (device_context_->GetCameraFrameRotation()) {
case 0:
return {x, y};
case 90:
return {y, 1.0 - x};
case 180:
return {1.0 - x, 1.0 - y};
case 270:
return {1.0 - y, x};
default:
NOTREACHED_IN_MIGRATION() << "Invalid orientation";
}
return {x, y};
}();
// TODO(shik): Respect to SCALER_CROP_REGION, which is unused now.
x *= active_array_size_.width() - 1;
y *= active_array_size_.height() - 1;
gfx::Point point = {static_cast<int>(x), static_cast<int>(y)};
camera_3a_controller_->SetPointOfInterest(point);
return true;
}
mojom::RangePtr CameraDeviceDelegate::GetControlRangeByVendorTagName(
const std::string& range_name,
const std::optional<int32_t>& current) {
const VendorTagInfo* info =
camera_hal_delegate_->GetVendorTagInfoByName(range_name);
if (info == nullptr) {
return mojom::Range::New();
}
auto static_val =
GetMetadataEntryAsSpan<int32_t>(static_metadata_, info->tag);
if (static_val.size() != 3) {
return mojom::Range::New();
}
if (!current) {
return mojom::Range::New();
}
mojom::RangePtr range = mojom::Range::New();
range->min = static_val[0];
range->max = static_val[1];
range->step = static_val[2];
range->current = current.value();
return range;
}
bool CameraDeviceDelegate::ShouldUseBlobVideoSnapshot() {
auto level = GetMetadataEntryAsSpan<uint8_t>(
static_metadata_,
cros::mojom::CameraMetadataTag::ANDROID_INFO_SUPPORTED_HARDWARE_LEVEL);
DCHECK_EQ(level.size(), 1u);
return level[0] ==
static_cast<uint8_t>(cros::mojom::AndroidInfoSupportedHardwareLevel::
ANDROID_INFO_SUPPORTED_HARDWARE_LEVEL_FULL);
}
void CameraDeviceDelegate::OnNewBuffer(
ClientType client_type,
cros::mojom::CameraBufferHandlePtr buffer) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
uint64_t buffer_id = buffer->buffer_id;
device_ops_->OnNewBuffer(
std::move(buffer),
base::BindOnce(&CameraDeviceDelegate::OnNewBufferResult, GetWeakPtr(),
client_type, buffer_id));
}
void CameraDeviceDelegate::OnNewBufferResult(ClientType client_type,
uint64_t buffer_id,
int32_t result) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
if (result != 0) {
device_context_->SetErrorState(
media::VideoCaptureError::kCrosHalV3BufferManagerFailedToRegisterBuffer,
FROM_HERE,
base::StrCat({"On new buffer failed: ", base::safe_strerror(-result)}));
return;
}
buffer_ids_known_by_hal_[client_type].insert(buffer_id);
if (pending_retire_ids_.contains(buffer_id)) {
OnBufferRetired(client_type, buffer_id);
pending_retire_ids_.erase(buffer_id);
}
}
void CameraDeviceDelegate::OnBufferRetired(ClientType client_type,
uint64_t buffer_id) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
if (device_context_->GetState() == CameraDeviceContext::State::kError) {
return;
}
auto buffer_ids = buffer_ids_known_by_hal_.find(client_type);
if (buffer_ids == buffer_ids_known_by_hal_.end() ||
!(buffer_ids->second.contains(buffer_id))) {
// Buffer has been notified to HAL but still not complete the registration,
// so here delay the retiring after the registration is done.
pending_retire_ids_.insert(buffer_id);
return;
}
device_ops_->OnBufferRetired(buffer_id);
buffer_ids->second.erase(buffer_id);
}
void CameraDeviceDelegate::OnAllBufferRetired(ClientType client_type) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
auto buffer_ids = buffer_ids_known_by_hal_.find(client_type);
if (buffer_ids == buffer_ids_known_by_hal_.end()) {
// No buffers known by hal for `client_type`.
return;
}
// Skip if mojo connection is already closed.
if (!device_ops_.is_bound()) {
return;
}
for (auto buffer_id : buffer_ids->second) {
device_ops_->OnBufferRetired(buffer_id);
}
buffer_ids->second.clear();
}
void CameraDeviceDelegate::OnResultMetadataAvailable(
uint32_t frame_number,
const cros::mojom::CameraMetadataPtr& result_metadata) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
auto get_vendor_int =
[&](const std::string& name,
const cros::mojom::CameraMetadataPtr& result_metadata,
std::optional<int32_t>* returned_value) {
returned_value->reset();
const VendorTagInfo* info =
camera_hal_delegate_->GetVendorTagInfoByName(name);
if (info == nullptr)
return;
auto val = GetMetadataEntryAsSpan<int32_t>(result_metadata, info->tag);
if (val.size() == 1)
*returned_value = val[0];
};
get_vendor_int(kBrightness, result_metadata, &result_metadata_.brightness);
get_vendor_int(kContrast, result_metadata, &result_metadata_.contrast);
get_vendor_int(kPan, result_metadata, &result_metadata_.pan);
get_vendor_int(kSaturation, result_metadata, &result_metadata_.saturation);
get_vendor_int(kSharpness, result_metadata, &result_metadata_.sharpness);
get_vendor_int(kTilt, result_metadata, &result_metadata_.tilt);
get_vendor_int(kZoom, result_metadata, &result_metadata_.zoom);
result_metadata_.scaler_crop_region.reset();
auto rect = GetMetadataEntryAsSpan<int32_t>(
result_metadata,
cros::mojom::CameraMetadataTag::ANDROID_SCALER_CROP_REGION);
if (rect.size() == 4) {
result_metadata_.scaler_crop_region =
gfx::Rect(rect[0], rect[1], rect[2], rect[3]);
}
result_metadata_.ae_mode.reset();
auto ae_mode = GetMetadataEntryAsSpan<uint8_t>(
result_metadata, cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AE_MODE);
if (ae_mode.size() == 1)
result_metadata_.ae_mode = ae_mode[0];
result_metadata_.exposure_time.reset();
auto exposure_time = GetMetadataEntryAsSpan<int64_t>(
result_metadata,
cros::mojom::CameraMetadataTag::ANDROID_SENSOR_EXPOSURE_TIME);
if (exposure_time.size() == 1)
result_metadata_.exposure_time = exposure_time[0];
result_metadata_.awb_mode.reset();
auto awb_mode = GetMetadataEntryAsSpan<uint8_t>(
result_metadata,
cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AWB_MODE);
if (awb_mode.size() == 1)
result_metadata_.awb_mode = awb_mode[0];
result_metadata_.af_mode.reset();
auto af_mode = GetMetadataEntryAsSpan<uint8_t>(
result_metadata, cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_MODE);
if (af_mode.size() == 1)
result_metadata_.af_mode = af_mode[0];
result_metadata_.focus_distance.reset();
auto focus_distance = GetMetadataEntryAsSpan<float>(
result_metadata,
cros::mojom::CameraMetadataTag::ANDROID_LENS_FOCUS_DISTANCE);
if (focus_distance.size() == 1)
result_metadata_.focus_distance = focus_distance[0];
result_metadata_.sensitivity.reset();
auto sensitivity = GetMetadataEntryAsSpan<int32_t>(
result_metadata,
cros::mojom::CameraMetadataTag::ANDROID_SENSOR_SENSITIVITY);
if (sensitivity.size() == 1)
result_metadata_.sensitivity = sensitivity[0];
result_metadata_.ae_compensation.reset();
auto ae_compensation = GetMetadataEntryAsSpan<int32_t>(
result_metadata,
cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AE_EXPOSURE_COMPENSATION);
if (ae_compensation.size() == 1)
result_metadata_.ae_compensation = ae_compensation[0];
auto lens_state = GetMetadataEntryAsSpan<uint8_t>(
result_metadata, cros::mojom::CameraMetadataTag::ANDROID_LENS_STATE);
result_metadata_frame_number_ = frame_number;
// We need to wait the new result metadata for new settings.
if (result_metadata_frame_number_ >
result_metadata_frame_number_for_photo_state_ &&
lens_state.size() == 1 &&
static_cast<cros::mojom::AndroidLensState>(lens_state[0]) ==
cros::mojom::AndroidLensState::ANDROID_LENS_STATE_STATIONARY) {
for (auto& request : get_photo_state_queue_)
ipc_task_runner_->PostTask(FROM_HERE, std::move(request));
get_photo_state_queue_.clear();
}
}
void CameraDeviceDelegate::OnCameraEffectsChanged(
cros::mojom::EffectsConfigPtr new_effects) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
if (!current_effects_.is_null() &&
current_effects_->blur_enabled != new_effects->blur_enabled) {
device_context_->OnCaptureConfigurationChanged();
}
current_effects_ = std::move(new_effects);
}
void CameraDeviceDelegate::OnAutoFramingStateChanged(
cros::mojom::CameraAutoFramingState state) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
if (current_auto_framing_state_ && *current_auto_framing_state_ != state) {
device_context_->OnCaptureConfigurationChanged();
}
current_auto_framing_state_ = state;
}
void CameraDeviceDelegate::DoGetPhotoState(
VideoCaptureDevice::GetPhotoStateCallback callback) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
auto photo_state = mojo::CreateEmptyPhotoState();
if (!device_context_ ||
(device_context_->GetState() !=
CameraDeviceContext::State::kStreamConfigured &&
device_context_->GetState() != CameraDeviceContext::State::kCapturing)) {
std::move(callback).Run(std::move(photo_state));
return;
}
std::vector<gfx::Size> blob_resolutions;
GetStreamResolutions(
static_metadata_, cros::mojom::Camera3StreamType::CAMERA3_STREAM_OUTPUT,
cros::mojom::HalPixelFormat::HAL_PIXEL_FORMAT_BLOB, &blob_resolutions);
if (blob_resolutions.empty()) {
std::move(callback).Run(std::move(photo_state));
return;
}
// Sets the correct range of min/max resolution in order to bypass checks that
// the resolution caller request should fall within the range when taking
// photos. And since we are not actually use the mechanism to get other
// resolutions, we set the step to 0.0 here.
photo_state->width->current = current_blob_resolution_.width();
photo_state->width->min =
base::ranges::min_element(blob_resolutions, {}, [](const gfx::Size& s) {
return s.width();
})->width();
photo_state->width->max =
base::ranges::max_element(blob_resolutions, {}, [](const gfx::Size& s) {
return s.width();
})->width();
photo_state->width->step = 0.0;
photo_state->height->current = current_blob_resolution_.height();
photo_state->height->min =
base::ranges::min_element(blob_resolutions, {}, [](const gfx::Size& s) {
return s.height();
})->height();
photo_state->height->max =
base::ranges::max_element(blob_resolutions, {}, [](const gfx::Size& s) {
return s.height();
})->height();
photo_state->height->step = 0.0;
photo_state->brightness = GetControlRangeByVendorTagName(
kBrightnessRange, result_metadata_.brightness);
photo_state->contrast =
GetControlRangeByVendorTagName(kContrastRange, result_metadata_.contrast);
photo_state->pan =
GetControlRangeByVendorTagName(kPanRange, result_metadata_.pan);
photo_state->saturation = GetControlRangeByVendorTagName(
kSaturationRange, result_metadata_.saturation);
photo_state->sharpness = GetControlRangeByVendorTagName(
kSharpnessRange, result_metadata_.sharpness);
photo_state->tilt =
GetControlRangeByVendorTagName(kTiltRange, result_metadata_.tilt);
// For zoom part, we check the scaler.availableMaxDigitalZoom first, if there
// is no metadata or the value is one we use zoom vendor tag.
//
// https://w3c.github.io/mediacapture-image/#zoom
//
// scaler.availableMaxDigitalZoom:
// We use area ratio for this type zoom.
//
// Vendor tag zoom:
// It is used by UVC camera usually.
// The zoom unit is driver-specific for V4L2_CID_ZOOM_ABSOLUTE.
// https://www.kernel.org/doc/html/latest/media/uapi/v4l/ext-ctrls-camera.html
auto max_digital_zoom = GetMetadataEntryAsSpan<float>(
static_metadata_, cros::mojom::CameraMetadataTag::
ANDROID_SCALER_AVAILABLE_MAX_DIGITAL_ZOOM);
if (max_digital_zoom.size() == 1 && max_digital_zoom[0] > 1 &&
result_metadata_.scaler_crop_region) {
photo_state->zoom->min = 1;
photo_state->zoom->max = max_digital_zoom[0] * max_digital_zoom[0];
photo_state->zoom->step = 0.1;
photo_state->zoom->current =
(active_array_size_.width() /
(float)result_metadata_.scaler_crop_region->width()) *
(active_array_size_.height() /
(float)result_metadata_.scaler_crop_region->height());
// get 0.1 precision
photo_state->zoom->current = round(photo_state->zoom->current * 10) / 10;
use_digital_zoom_ = true;
} else {
photo_state->zoom =
GetControlRangeByVendorTagName(kZoomRange, result_metadata_.zoom);
use_digital_zoom_ = false;
}
auto awb_available_modes = GetMetadataEntryAsSpan<uint8_t>(
static_metadata_,
cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AWB_AVAILABLE_MODES);
// Only enable white balance control when there are more than 1 control modes.
if (awb_available_modes.size() > 1 && result_metadata_.awb_mode) {
photo_state->supported_white_balance_modes.push_back(
mojom::MeteringMode::MANUAL);
photo_state->supported_white_balance_modes.push_back(
mojom::MeteringMode::CONTINUOUS);
const AwbModeTemperatureMap& map = GetAwbModeTemperatureMap();
int32_t current_temperature = 0;
if (result_metadata_.awb_mode ==
static_cast<uint8_t>(
cros::mojom::AndroidControlAwbMode::ANDROID_CONTROL_AWB_MODE_AUTO))
photo_state->current_white_balance_mode = mojom::MeteringMode::CONTINUOUS;
else {
// Need to find current color temperature.
photo_state->current_white_balance_mode = mojom::MeteringMode::MANUAL;
current_temperature = map.at(result_metadata_.awb_mode.value());
}
int32_t min = std::numeric_limits<int32_t>::max();
int32_t max = std::numeric_limits<int32_t>::min();
for (const auto& mode : awb_available_modes) {
auto it = map.find(mode);
if (it == map.end())
continue;
if (it->second < min)
min = it->second;
else if (it->second > max)
max = it->second;
}
photo_state->color_temperature->min = min;
photo_state->color_temperature->max = max;
photo_state->color_temperature->step = kColorTemperatureStep;
photo_state->color_temperature->current = current_temperature;
}
auto ae_available_modes = GetMetadataEntryAsSpan<uint8_t>(
static_metadata_,
cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AE_AVAILABLE_MODES);
bool support_manual_exposure_time = false;
if (ae_available_modes.size() > 1 && result_metadata_.ae_mode) {
support_manual_exposure_time = base::Contains(
ae_available_modes,
static_cast<uint8_t>(
cros::mojom::AndroidControlAeMode::ANDROID_CONTROL_AE_MODE_OFF));
}
auto exposure_time_range = GetMetadataEntryAsSpan<int64_t>(
static_metadata_,
cros::mojom::CameraMetadataTag::ANDROID_SENSOR_INFO_EXPOSURE_TIME_RANGE);
if (support_manual_exposure_time && exposure_time_range.size() == 2 &&
result_metadata_.exposure_time) {
photo_state->supported_exposure_modes.push_back(
mojom::MeteringMode::MANUAL);
photo_state->supported_exposure_modes.push_back(
mojom::MeteringMode::CONTINUOUS);
if (result_metadata_.ae_mode ==
static_cast<uint8_t>(
cros::mojom::AndroidControlAeMode::ANDROID_CONTROL_AE_MODE_OFF))
photo_state->current_exposure_mode = mojom::MeteringMode::MANUAL;
else
photo_state->current_exposure_mode = mojom::MeteringMode::CONTINUOUS;
// The unit of photo_state->exposure_time is 100 microseconds and from
// metadata is nanoseconds.
photo_state->exposure_time->min = std::ceil(
static_cast<float>(exposure_time_range[0]) / (100 * kMicroToNano));
photo_state->exposure_time->max =
exposure_time_range[1] / (100 * kMicroToNano);
photo_state->exposure_time->step = 1; // 100 microseconds
photo_state->exposure_time->current =
result_metadata_.exposure_time.value() / (100 * kMicroToNano);
}
auto af_available_modes = GetMetadataEntryAsSpan<uint8_t>(
static_metadata_,
cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_AVAILABLE_MODES);
bool support_manual_focus_distance = false;
if (af_available_modes.size() > 1 && result_metadata_.af_mode) {
support_manual_focus_distance = base::Contains(
af_available_modes,
static_cast<uint8_t>(
cros::mojom::AndroidControlAfMode::ANDROID_CONTROL_AF_MODE_OFF));
}
auto minimum_focus_distance = GetMetadataEntryAsSpan<float>(
static_metadata_,
cros::mojom::CameraMetadataTag::ANDROID_LENS_INFO_MINIMUM_FOCUS_DISTANCE);
// If the lens is fixed-focus, minimum_focus_distance will be 0.
if (support_manual_focus_distance && minimum_focus_distance.size() == 1 &&
minimum_focus_distance[0] != 0 && result_metadata_.focus_distance) {
photo_state->supported_focus_modes.push_back(mojom::MeteringMode::MANUAL);
photo_state->supported_focus_modes.push_back(
mojom::MeteringMode::CONTINUOUS);
if (result_metadata_.af_mode ==
static_cast<uint8_t>(
cros::mojom::AndroidControlAfMode::ANDROID_CONTROL_AF_MODE_OFF))
photo_state->current_focus_mode = mojom::MeteringMode::MANUAL;
else
photo_state->current_focus_mode = mojom::MeteringMode::CONTINUOUS;
// The unit of photo_state->focus_distance is meter and from metadata is
// diopter.
photo_state->focus_distance->min =
std::roundf(100.0 / minimum_focus_distance[0]) / 100.0;
photo_state->focus_distance->max = std::numeric_limits<double>::infinity();
photo_state->focus_distance->step = 0.01;
if (result_metadata_.focus_distance.value() == 0) {
photo_state->focus_distance->current =
std::numeric_limits<double>::infinity();
} else {
// We want to make sure |current| is a possible value of
// |min| + |steps(0.01)|*X. The minimum can be divided by step(0.01). So
// we only need to round the value less than 0.01.
double meters = 1.0 / result_metadata_.focus_distance.value();
photo_state->focus_distance->current = std::roundf(meters * 100) / 100.0;
}
}
auto sensitivity_range = GetMetadataEntryAsSpan<int32_t>(
static_metadata_,
cros::mojom::CameraMetadataTag::ANDROID_SENSOR_INFO_SENSITIVITY_RANGE);
if (sensitivity_range.size() == 2 && result_metadata_.sensitivity) {
photo_state->iso->min = sensitivity_range[0];
photo_state->iso->max = sensitivity_range[1];
photo_state->iso->step = 1;
photo_state->iso->current = result_metadata_.sensitivity.value();
}
auto ae_compensation_range = GetMetadataEntryAsSpan<int32_t>(
static_metadata_,
cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AE_COMPENSATION_RANGE);
ae_compensation_step_ = 0.0;
if (ae_compensation_range.size() == 2) {
if (ae_compensation_range[0] != 0 || ae_compensation_range[1] != 0) {
auto ae_compensation_step = GetMetadataEntryAsSpan<Rational>(
static_metadata_,
cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AE_COMPENSATION_STEP);
if (ae_compensation_step.size() == 1) {
if (ae_compensation_step[0].numerator == 0 ||
ae_compensation_step[0].denominator == 0) {
LOG(WARNING) << "AE_COMPENSATION_STEP: numerator:"
<< ae_compensation_step[0].numerator << ", denominator:"
<< ae_compensation_step[0].denominator;
} else {
ae_compensation_step_ =
static_cast<float>(ae_compensation_step[0].numerator) /
static_cast<float>(ae_compensation_step[0].denominator);
photo_state->exposure_compensation->min =
ae_compensation_range[0] * ae_compensation_step_;
photo_state->exposure_compensation->max =
ae_compensation_range[1] * ae_compensation_step_;
photo_state->exposure_compensation->step = ae_compensation_step_;
if (result_metadata_.ae_compensation)
photo_state->exposure_compensation->current =
result_metadata_.ae_compensation.value() *
ae_compensation_step_;
else
photo_state->exposure_compensation->current = 0;
}
}
}
}
// For background blur and face framing part, we only set capabilities and
// current configuration setting if the feature flag is enabled.
//
// https://w3c.github.io/mediacapture-extensions/#exposing-mediastreamtrack-source-background-blur-support
if (ash::features::IsVcWebApiEnabled() && !current_effects_.is_null()) {
photo_state->supported_background_blur_modes = {
current_effects_->blur_enabled ? mojom::BackgroundBlurMode::BLUR
: mojom::BackgroundBlurMode::OFF};
photo_state->background_blur_mode = current_effects_->blur_enabled
? mojom::BackgroundBlurMode::BLUR
: mojom::BackgroundBlurMode::OFF;
}
// https://w3c.github.io/mediacapture-extensions/#exposing-mediastreamtrack-source-automatic-face-framing-support
if (ash::features::IsVcWebApiEnabled() && current_auto_framing_state_) {
photo_state->supported_face_framing_modes = {
current_auto_framing_state_ == cros::mojom::CameraAutoFramingState::OFF
? mojom::MeteringMode::NONE
: mojom::MeteringMode::SINGLE_SHOT};
photo_state->current_face_framing_mode =
current_auto_framing_state_ == cros::mojom::CameraAutoFramingState::OFF
? mojom::MeteringMode::NONE
: mojom::MeteringMode::SINGLE_SHOT;
}
std::move(callback).Run(std::move(photo_state));
}
std::pair<int32_t, int32_t> CameraDeviceDelegate::GetFrameRateRange() {
auto camera_app_device =
CameraAppDeviceBridgeImpl::GetInstance()->GetWeakCameraAppDevice(
device_descriptor_.device_id);
auto specified_fps_range =
camera_app_device ? camera_app_device->GetFpsRange() : std::nullopt;
if (specified_fps_range) {
return std::make_pair(specified_fps_range->GetMin(),
specified_fps_range->GetMax());
}
// Assumes the frame_rate will be the same for all |chrome_capture_params|.
int32_t requested_frame_rate =
std::round(chrome_capture_params_[ClientType::kPreviewClient]
.requested_format.frame_rate);
bool prefer_constant_frame_rate =
base::FeatureList::IsEnabled(ash::features::kPreferConstantFrameRate) ||
(camera_app_device && camera_app_device->GetCaptureIntent() ==
cros::mojom::CaptureIntent::kVideoRecord);
return GetTargetFrameRateRange(static_metadata_, requested_frame_rate,
prefer_constant_frame_rate);
}
void CameraDeviceDelegate::ConfigureSessionParameters(
cros::mojom::CameraMetadataPtr* session_parameters) {
auto session_keys = GetMetadataEntryAsSpan<int32_t>(
static_metadata_,
cros::mojom::CameraMetadataTag::ANDROID_REQUEST_AVAILABLE_SESSION_KEYS);
for (auto tag_value : session_keys) {
auto metadata_tag = static_cast<cros::mojom::CameraMetadataTag>(tag_value);
switch (metadata_tag) {
case cros::mojom::CameraMetadataTag::
ANDROID_CONTROL_AE_TARGET_FPS_RANGE: {
VLOG(1) << "Setting " << metadata_tag << " in session_parameters.";
auto [fps_min, fps_max] = GetFrameRateRange();
if (fps_min == 0 || fps_max == 0) {
LOG(WARNING) << "Failed to get valid fps range, did not set "
<< metadata_tag << " in session_parameters.";
break;
}
SetFpsRangeInMetadata(session_parameters, fps_min, fps_max);
break;
}
default: {
VLOG(1) << "Did not set " << metadata_tag << " in session_parameters.";
break;
}
}
}
}
void CameraDeviceDelegate::SetDigitalZoomSessionParameters(
cros::mojom::CameraMetadataPtr* session_parameters) {
CAMERA_LOG(EVENT)
<< "Setting kCrosDigitalZoomRequestedVendorKey in session_parameters.";
auto e = BuildMetadataEntry(static_cast<cros::mojom::CameraMetadataTag>(
kCrosDigitalZoomRequestedVendorKey),
uint8_t{1});
AddOrUpdateMetadataEntry(session_parameters, std::move(e));
}
} // namespace media