// Copyright 2018 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/request_manager.h"
#include <sync/sync.h>
#include <initializer_list>
#include <map>
#include <set>
#include <string>
#include <utility>
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/posix/safe_strerror.h"
#include "base/strings/string_number_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/trace_event/typed_macros.h"
#include "gpu/ipc/common/gpu_memory_buffer_impl.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_metadata_utils.h"
#include "media/capture/video/chromeos/camera_trace_utils.h"
#include "media/capture/video/chromeos/video_capture_features_chromeos.h"
#include "mojo/public/cpp/platform/platform_handle.h"
#include "mojo/public/cpp/system/platform_handle.h"
#include "third_party/abseil-cpp/absl/cleanup/cleanup.h"
namespace media {
namespace {
constexpr uint32_t kUndefinedFrameNumber = 0xFFFFFFFF;
// Choose a JPEG thumbnail size for the JPEG output stream size from the
// JPEG_AVAILABLE_THUMBNAIL_SIZES static metadata. Note that [0, 0] indicates no
// thumbnail should be generated, and can be returned by this function if
// there's no non-zero JPEG thumbnail size available.
gfx::Size GetJpegThumbnailSize(
const cros::mojom::CameraMetadataPtr& static_metadata,
const std::vector<cros::mojom::Camera3StreamPtr>& streams) {
gfx::Size jpeg_size;
gfx::Size portrait_jpeg_size;
for (auto& stream : streams) {
const StreamType stream_type = StreamIdToStreamType(stream->id);
if (stream_type == StreamType::kJpegOutput) {
jpeg_size = gfx::Size(base::checked_cast<int>(stream->width),
base::checked_cast<int>(stream->height));
}
if (stream_type == StreamType::kPortraitJpegOutput) {
portrait_jpeg_size = gfx::Size(base::checked_cast<int>(stream->width),
base::checked_cast<int>(stream->height));
// The sizes of the JPEG stream and portrait JPEG stream should be the
// same.
CHECK_EQ(jpeg_size, portrait_jpeg_size);
}
}
if (jpeg_size.IsEmpty())
return gfx::Size();
const auto available_sizes = GetMetadataEntryAsSpan<int32_t>(
static_metadata,
cros::mojom::CameraMetadataTag::ANDROID_JPEG_AVAILABLE_THUMBNAIL_SIZES);
DCHECK_EQ(available_sizes.size() % 2, 0u);
// Choose the thumbnail size with the closest aspect ratio to the JPEG size.
// If there are multiple options, choose the smallest one.
constexpr int kPrecisionFactor = 1000;
const int target_aspect_ratio =
kPrecisionFactor * jpeg_size.width() / jpeg_size.height();
std::vector<std::tuple<int, int, int>> items;
for (size_t i = 0; i < available_sizes.size(); i += 2) {
const gfx::Size size(base::strict_cast<int>(available_sizes[i]),
base::strict_cast<int>(available_sizes[i + 1]));
if (size.IsEmpty())
continue;
const int aspect_ratio = kPrecisionFactor * size.width() / size.height();
items.emplace_back(std::abs(aspect_ratio - target_aspect_ratio),
size.width(), size.height());
}
const auto iter = std::min_element(items.begin(), items.end());
if (iter == items.end())
return gfx::Size();
return gfx::Size(std::get<1>(*iter), std::get<2>(*iter));
}
} // namespace
VideoCaptureBufferObserver::VideoCaptureBufferObserver(
base::WeakPtr<RequestManager> request_manager)
: request_manager_(std::move(request_manager)) {}
VideoCaptureBufferObserver::~VideoCaptureBufferObserver() = default;
void VideoCaptureBufferObserver::OnNewBuffer(
ClientType client_type,
cros::mojom::CameraBufferHandlePtr buffer) {
if (request_manager_) {
request_manager_->OnNewBuffer(client_type, std::move(buffer));
}
}
void VideoCaptureBufferObserver::OnBufferRetired(ClientType client_type,
uint64_t buffer_id) {
if (request_manager_) {
request_manager_->OnBufferRetired(client_type, buffer_id);
}
}
RequestManager::RequestManager(
const std::string& device_id,
mojo::PendingReceiver<cros::mojom::Camera3CallbackOps>
callback_ops_receiver,
std::unique_ptr<StreamCaptureInterface> capture_interface,
CameraDeviceContext* device_context,
VideoCaptureBufferType buffer_type,
std::unique_ptr<CameraBufferFactory> camera_buffer_factory,
BlobifyCallback blobify_callback,
scoped_refptr<base::SingleThreadTaskRunner> ipc_task_runner,
uint32_t device_api_version,
bool use_buffer_management_apis)
: device_id_(device_id),
callback_ops_(this, std::move(callback_ops_receiver)),
capture_interface_(std::move(capture_interface)),
device_context_(device_context),
video_capture_use_gmb_(buffer_type ==
VideoCaptureBufferType::kGpuMemoryBuffer),
blobify_callback_(std::move(blobify_callback)),
ipc_task_runner_(std::move(ipc_task_runner)),
capturing_(false),
partial_result_count_(1),
first_frame_shutter_time_(base::TimeTicks()),
device_api_version_(device_api_version),
use_buffer_management_apis_(use_buffer_management_apis) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
DCHECK(callback_ops_.is_bound());
DCHECK(device_context_);
stream_buffer_manager_ = std::make_unique<StreamBufferManager>(
device_context_, video_capture_use_gmb_, std::move(camera_buffer_factory),
std::make_unique<VideoCaptureBufferObserver>(GetWeakPtr()));
// We use base::Unretained() for the StreamBufferManager here since we
// guarantee |request_buffer_callback| is only used by RequestBuilder. In
// addition, since C++ destroys member variables in reverse order of
// construction, we can ensure that RequestBuilder will be destroyed prior
// to StreamBufferManager since RequestBuilder constructs after
// StreamBufferManager.
auto request_buffer_callback =
base::BindRepeating(&StreamBufferManager::RequestBufferForCaptureRequest,
base::Unretained(stream_buffer_manager_.get()));
request_builder_ = std::make_unique<RequestBuilder>(
device_context_, std::move(request_buffer_callback),
use_buffer_management_apis_);
}
RequestManager::~RequestManager() = default;
void RequestManager::SetUpStreamsAndBuffers(
base::flat_map<ClientType, VideoCaptureParams> capture_params,
const cros::mojom::CameraMetadataPtr& static_metadata,
std::vector<cros::mojom::Camera3StreamPtr> streams) {
auto request_keys = GetMetadataEntryAsSpan<int32_t>(
static_metadata,
cros::mojom::CameraMetadataTag::ANDROID_REQUEST_AVAILABLE_REQUEST_KEYS);
zero_shutter_lag_supported_ = base::Contains(
request_keys,
static_cast<int32_t>(
cros::mojom::CameraMetadataTag::ANDROID_CONTROL_ENABLE_ZSL));
VLOG(1) << "Zero-shutter lag is "
<< (zero_shutter_lag_supported_ ? "" : "not ") << "supported";
// The partial result count metadata is optional; defaults to 1 in case it
// is not set in the static metadata.
const cros::mojom::CameraMetadataEntryPtr* partial_count = GetMetadataEntry(
static_metadata,
cros::mojom::CameraMetadataTag::ANDROID_REQUEST_PARTIAL_RESULT_COUNT);
if (partial_count) {
partial_result_count_ =
*reinterpret_cast<int32_t*>((*partial_count)->data.data());
}
auto pipeline_depth = GetMetadataEntryAsSpan<uint8_t>(
static_metadata,
cros::mojom::CameraMetadataTag::ANDROID_REQUEST_PIPELINE_MAX_DEPTH);
CHECK_EQ(pipeline_depth.size(), 1u);
pipeline_depth_ = pipeline_depth[0];
preview_buffers_queued_ = 0;
// Set the last received frame number for each stream types to be undefined.
for (const auto& stream : streams) {
StreamType stream_type = StreamIdToStreamType(stream->id);
last_received_frame_number_map_[stream_type] = kUndefinedFrameNumber;
}
jpeg_thumbnail_size_ = GetJpegThumbnailSize(static_metadata, streams);
stream_buffer_manager_->SetUpStreamsAndBuffers(
capture_params, static_metadata, std::move(streams));
}
cros::mojom::Camera3StreamPtr RequestManager::GetStreamConfiguration(
StreamType stream_type) {
return stream_buffer_manager_->GetStreamConfiguration(stream_type);
}
bool RequestManager::HasStreamsConfiguredForTakePhoto() {
if (stream_buffer_manager_->IsPortraitModeSupported()) {
return stream_buffer_manager_->HasStreamsConfigured(
{StreamType::kPreviewOutput, StreamType::kJpegOutput,
StreamType::kPortraitJpegOutput});
} else {
return stream_buffer_manager_->HasStreamsConfigured(
{StreamType::kPreviewOutput, StreamType::kJpegOutput});
}
}
void RequestManager::StartPreview(
cros::mojom::CameraMetadataPtr preview_settings) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
DCHECK(repeating_request_settings_.is_null());
capturing_ = true;
repeating_request_settings_ = std::move(preview_settings);
PrepareCaptureRequest();
}
void RequestManager::StopPreview(base::OnceCallback<void(int32_t)> callback) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
capturing_ = false;
repeating_request_settings_ = nullptr;
if (callback) {
capture_interface_->Flush(std::move(callback));
}
}
void RequestManager::TakePhoto(cros::mojom::CameraMetadataPtr settings,
VideoCaptureDevice::TakePhotoCallback callback) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
take_photo_callback_queue_.push(std::move(callback));
take_photo_settings_queue_.push(std::move(settings));
}
void RequestManager::TakePortraitPhoto(cros::mojom::CameraMetadataPtr settings,
TakePhotoCallbackMap callbacks_map) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
take_portrait_photo_callback_map_ = std::move(callbacks_map);
take_photo_settings_queue_.push(std::move(settings));
}
base::WeakPtr<RequestManager> RequestManager::GetWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
void RequestManager::AddResultMetadataObserver(
ResultMetadataObserver* observer) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
DCHECK(!result_metadata_observers_.count(observer));
result_metadata_observers_.insert(observer);
}
void RequestManager::RemoveResultMetadataObserver(
ResultMetadataObserver* observer) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
DCHECK(result_metadata_observers_.count(observer));
result_metadata_observers_.erase(observer);
}
void RequestManager::OnNewBuffer(ClientType client_type,
cros::mojom::CameraBufferHandlePtr buffer) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
capture_interface_->OnNewBuffer(client_type, std::move(buffer));
}
void RequestManager::OnBufferRetired(ClientType client_type,
uint64_t buffer_id) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
capture_interface_->OnBufferRetired(client_type, buffer_id);
}
void RequestManager::SetCaptureMetadata(cros::mojom::CameraMetadataTag tag,
cros::mojom::EntryType type,
size_t count,
std::vector<uint8_t> value) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
cros::mojom::CameraMetadataEntryPtr setting =
cros::mojom::CameraMetadataEntry::New();
setting->tag = tag;
setting->type = type;
setting->count = count;
setting->data = std::move(value);
capture_settings_override_.push_back(std::move(setting));
}
void RequestManager::SetRepeatingCaptureMetadata(
cros::mojom::CameraMetadataTag tag,
cros::mojom::EntryType type,
size_t count,
std::vector<uint8_t> value) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
cros::mojom::CameraMetadataEntryPtr setting =
cros::mojom::CameraMetadataEntry::New();
setting->tag = tag;
setting->type = type;
setting->count = count;
setting->data = std::move(value);
capture_settings_repeating_override_[tag] = std::move(setting);
}
void RequestManager::UnsetRepeatingCaptureMetadata(
cros::mojom::CameraMetadataTag tag) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
auto it = capture_settings_repeating_override_.find(tag);
if (it == capture_settings_repeating_override_.end()) {
LOG(ERROR) << "Unset a non-existent metadata: " << tag;
return;
}
capture_settings_repeating_override_.erase(it);
}
void RequestManager::SetPortraitModeVendorKey(
cros::mojom::CameraMetadataPtr* settings) {
auto e = BuildMetadataEntry(
static_cast<cros::mojom::CameraMetadataTag>(kPortraitModeVendorKey),
uint8_t{1});
AddOrUpdateMetadataEntry(settings, std::move(e));
}
void RequestManager::SetJpegOrientation(
cros::mojom::CameraMetadataPtr* settings,
int32_t orientation) {
auto e = BuildMetadataEntry(
cros::mojom::CameraMetadataTag::ANDROID_JPEG_ORIENTATION, orientation);
AddOrUpdateMetadataEntry(settings, std::move(e));
}
void RequestManager::SetJpegThumbnailSize(
cros::mojom::CameraMetadataPtr* settings) const {
std::vector<uint8_t> data(sizeof(int32_t) * 2);
auto* data_i32 = reinterpret_cast<int32_t*>(data.data());
data_i32[0] = base::checked_cast<int32_t>(jpeg_thumbnail_size_.width());
data_i32[1] = base::checked_cast<int32_t>(jpeg_thumbnail_size_.height());
cros::mojom::CameraMetadataEntryPtr e =
cros::mojom::CameraMetadataEntry::New();
e->tag = cros::mojom::CameraMetadataTag::ANDROID_JPEG_THUMBNAIL_SIZE;
e->type = cros::mojom::EntryType::TYPE_INT32;
e->count = data.size() / sizeof(int32_t);
e->data = std::move(data);
AddOrUpdateMetadataEntry(settings, std::move(e));
}
void RequestManager::SetZeroShutterLag(cros::mojom::CameraMetadataPtr* settings,
bool enabled) {
auto e = BuildMetadataEntry(
cros::mojom::CameraMetadataTag::ANDROID_CONTROL_ENABLE_ZSL,
static_cast<uint8_t>(enabled));
AddOrUpdateMetadataEntry(settings, std::move(e));
}
void RequestManager::PrepareCaptureRequest() {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
if (!capturing_) {
return;
}
// We has several possible combinations of streams:
//
// If ZSL is enabled by default, the preview stream is not included in still
// capture request.
// 1. Preview (YuvOutput)
// 2. Preview + Recording (YuvOutput)
// 3. Capture (BlobOutput)
// 4. Capture + Portrait Capture (BlobOutput + BlobOutput)
//
// If ZSL is not supported, the preview stream is included in still capture
// request.
// 1. Preview (YuvOutput)
// 2. Preview + Recording (YuvOutput)
// 3. Preview + Capture (YuvOutput + BlobOutput)
std::set<StreamType> stream_types;
cros::mojom::CameraMetadataPtr settings;
VideoCaptureDevice::TakePhotoCallback callback = base::NullCallback();
TakePhotoCallbackMap callbacks_map;
bool is_portrait_request = false;
bool is_preview_request = false;
bool is_oneshot_request = false;
bool is_recording_request = false;
// First, check if there are pending portrait requests.
is_portrait_request =
TryPreparePortraitModeRequest(&stream_types, &settings, &callbacks_map);
// If there is no pending portrait request, then check if there are pending
// one-shot requests. And also try to put preview in the request.
if (!is_portrait_request) {
if (!zero_shutter_lag_supported_) {
is_preview_request = TryPreparePreviewRequest(&stream_types, &settings);
// Order matters here. If the preview request and oneshot request are both
// added in single capture request, the settings will be overridden by the
// later.
is_oneshot_request =
TryPrepareOneShotRequest(&stream_types, &settings, &callback);
} else {
// Zero-shutter lag could potentially give a frame from the past. Don't
// prepare a preview request when a one shot request has been prepared.
is_oneshot_request =
TryPrepareOneShotRequest(&stream_types, &settings, &callback);
if (!is_oneshot_request) {
is_preview_request = TryPreparePreviewRequest(&stream_types, &settings);
}
}
}
if (is_preview_request) {
is_recording_request = TryPrepareRecordingRequest(&stream_types);
}
if (!is_portrait_request && !is_oneshot_request && !is_preview_request &&
!is_recording_request) {
// We have to keep the pipeline full.
if (preview_buffers_queued_ < pipeline_depth_) {
ipc_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&RequestManager::PrepareCaptureRequest, GetWeakPtr()));
}
return;
}
// Sets crop region if there is a value set from Camera app.
auto camera_app_device =
CameraAppDeviceBridgeImpl::GetInstance()->GetWeakCameraAppDevice(
device_id_);
if (camera_app_device) {
auto crop_region = camera_app_device->GetCropRegion();
if (crop_region.has_value()) {
SetCaptureMetadata(
cros::mojom::CameraMetadataTag::ANDROID_SCALER_CROP_REGION,
cros::mojom::EntryType::TYPE_INT32, crop_region->size(),
SerializeMetadataValueFromSpan<int32_t>(*crop_region));
}
}
auto capture_request = request_builder_->BuildRequest(std::move(stream_types),
std::move(settings));
CHECK_GT(capture_request->output_buffers.size(), 0u);
CaptureResult& pending_result =
pending_results_[capture_request->frame_number];
pending_result.unsubmitted_buffer_count =
capture_request->output_buffers.size();
pending_result.still_capture_callback = std::move(callback);
pending_result.portrait_callbacks_map = std::move(callbacks_map);
if (is_preview_request) {
++preview_buffers_queued_;
}
UpdateCaptureSettings(&capture_request->settings);
if (device_api_version_ >= cros::mojom::CAMERA_DEVICE_API_VERSION_3_5) {
capture_request->physcam_settings =
std::vector<cros::mojom::Camera3PhyscamMetadataPtr>();
}
capture_interface_->ProcessCaptureRequest(
std::move(capture_request),
base::BindOnce(&RequestManager::OnProcessedCaptureRequest, GetWeakPtr()));
}
bool RequestManager::TryPreparePortraitModeRequest(
std::set<StreamType>* stream_types,
cros::mojom::CameraMetadataPtr* settings,
TakePhotoCallbackMap* callbacks_map) {
if (take_photo_settings_queue_.empty() ||
!take_portrait_photo_callback_map_[StreamType::kJpegOutput] ||
!take_portrait_photo_callback_map_[StreamType::kPortraitJpegOutput] ||
!stream_buffer_manager_->HasFreeBuffers({StreamType::kJpegOutput}) ||
!stream_buffer_manager_->HasFreeBuffers(
{StreamType::kPortraitJpegOutput})) {
return false;
}
stream_types->insert(
{StreamType::kJpegOutput, StreamType::kPortraitJpegOutput});
*callbacks_map = std::move(take_portrait_photo_callback_map_);
// Prepare metadata by adding extra metadata.
*settings = std::move(take_photo_settings_queue_.front());
SetPortraitModeVendorKey(settings);
SetJpegOrientation(settings, device_context_->GetCameraFrameRotation());
SetJpegThumbnailSize(settings);
SetZeroShutterLag(settings, true);
take_photo_settings_queue_.pop();
return true;
}
bool RequestManager::TryPreparePreviewRequest(
std::set<StreamType>* stream_types,
cros::mojom::CameraMetadataPtr* settings) {
if (preview_buffers_queued_ == pipeline_depth_) {
return false;
}
if (!stream_buffer_manager_->HasFreeBuffers({StreamType::kPreviewOutput})) {
// Try our best to reserve an usable buffer. If the reservation still
// fails, then we'd have to drop the camera frame.
DLOG(WARNING) << "Late request for reserving preview buffer";
stream_buffer_manager_->ReserveBuffer(StreamType::kPreviewOutput);
if (!stream_buffer_manager_->HasFreeBuffers({StreamType::kPreviewOutput})) {
DLOG(WARNING) << "No free buffer for preview stream";
return false;
}
}
stream_types->insert({StreamType::kPreviewOutput});
*settings = repeating_request_settings_.Clone();
return true;
}
bool RequestManager::TryPrepareOneShotRequest(
std::set<StreamType>* stream_types,
cros::mojom::CameraMetadataPtr* settings,
VideoCaptureDevice::TakePhotoCallback* callback) {
if (take_photo_settings_queue_.empty() ||
take_photo_callback_queue_.empty() ||
!stream_buffer_manager_->HasFreeBuffers({StreamType::kJpegOutput})) {
return false;
}
stream_types->insert({StreamType::kJpegOutput});
*callback = std::move(take_photo_callback_queue_.front());
take_photo_callback_queue_.pop();
*settings = std::move(take_photo_settings_queue_.front());
SetJpegOrientation(settings, device_context_->GetCameraFrameRotation());
SetJpegThumbnailSize(settings);
SetZeroShutterLag(settings, true);
take_photo_settings_queue_.pop();
return true;
}
bool RequestManager::TryPrepareRecordingRequest(
std::set<StreamType>* stream_types) {
if (!stream_buffer_manager_->IsRecordingSupported()) {
return false;
}
if (!stream_buffer_manager_->HasFreeBuffers({StreamType::kRecordingOutput})) {
// Try our best to reserve an usable buffer. If the reservation still
// fails, then we'd have to drop the camera frame.
DLOG(WARNING) << "Late request for reserving recording buffer";
stream_buffer_manager_->ReserveBuffer(StreamType::kRecordingOutput);
if (!stream_buffer_manager_->HasFreeBuffers(
{StreamType::kRecordingOutput})) {
DLOG(WARNING) << "No free buffer for recording stream";
return false;
}
}
stream_types->insert({StreamType::kRecordingOutput});
return true;
}
void RequestManager::OnProcessedCaptureRequest(int32_t result) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
if (!capturing_) {
return;
}
if (result != 0) {
device_context_->SetErrorState(
media::VideoCaptureError::
kCrosHalV3BufferManagerProcessCaptureRequestFailed,
FROM_HERE,
std::string("Process capture request failed: ") +
base::safe_strerror(-result));
return;
}
PrepareCaptureRequest();
}
void RequestManager::ProcessCaptureResult(
cros::mojom::Camera3CaptureResultPtr result) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
uint32_t frame_number = result->frame_number;
if (!capturing_) {
if (result->output_buffers) {
for (auto& stream_buffer : result->output_buffers.value()) {
TRACE_EVENT_END("camera",
GetTraceTrack(CameraTraceEvent::kCaptureStream,
frame_number, stream_buffer->stream_id));
}
}
TRACE_EVENT("camera", "Capture Result", "frame_number", frame_number);
TRACE_EVENT_END("camera", GetTraceTrack(CameraTraceEvent::kCaptureRequest,
frame_number));
return;
}
// A new partial result may be created in either ProcessCaptureResult or
// Notify.
CaptureResult& pending_result = pending_results_[frame_number];
// |result->partial_result| is set to 0 if the capture result contains only
// the result buffer handles and no result metadata.
if (result->partial_result != 0) {
uint32_t result_id = result->partial_result;
if (result_id > partial_result_count_) {
device_context_->SetErrorState(
media::VideoCaptureError::
kCrosHalV3BufferManagerInvalidPendingResultId,
FROM_HERE,
std::string("Invalid pending_result id: ") +
base::NumberToString(result_id));
return;
}
if (pending_result.partial_metadata_received.count(result_id)) {
device_context_->SetErrorState(
media::VideoCaptureError::
kCrosHalV3BufferManagerReceivedDuplicatedPartialMetadata,
FROM_HERE,
std::string("Received duplicated partial metadata: ") +
base::NumberToString(result_id));
return;
}
DVLOG(2) << "Received partial result " << result_id << " for frame "
<< frame_number;
pending_result.partial_metadata_received.insert(result_id);
MergeMetadata(&pending_result.metadata, result->result);
}
if (result->output_buffers) {
if (result->output_buffers->size() > kMaxConfiguredStreams) {
device_context_->SetErrorState(
media::VideoCaptureError::
kCrosHalV3BufferManagerIncorrectNumberOfOutputBuffersReceived,
FROM_HERE,
std::string("Incorrect number of output buffers received: ") +
base::NumberToString(result->output_buffers->size()));
return;
}
for (auto& stream_buffer : result->output_buffers.value()) {
auto stream_id = stream_buffer->stream_id;
DVLOG(2) << "Received capture result for frame " << frame_number
<< " stream_id: " << stream_id;
StreamType stream_type = StreamIdToStreamType(stream_id);
if (stream_type == StreamType::kUnknown) {
device_context_->SetErrorState(
media::VideoCaptureError::
kCrosHalV3BufferManagerInvalidTypeOfOutputBuffersReceived,
FROM_HERE,
std::string("Invalid type of output buffers received: ") +
base::NumberToString(stream_id));
return;
}
// The camera HAL v3 API specifies that only one capture result can carry
// the result buffer for any given frame number.
if (last_received_frame_number_map_[stream_type] ==
kUndefinedFrameNumber) {
last_received_frame_number_map_[stream_type] = frame_number;
} else {
if (last_received_frame_number_map_[stream_type] == frame_number) {
device_context_->SetErrorState(
media::VideoCaptureError::
kCrosHalV3BufferManagerReceivedMultipleResultBuffersForFrame,
FROM_HERE,
std::string("Received multiple result buffers for frame ") +
base::NumberToString(frame_number) +
std::string(" for stream ") +
base::NumberToString(stream_id));
return;
} else if (last_received_frame_number_map_[stream_type] >
frame_number) {
device_context_->SetErrorState(
media::VideoCaptureError::
kCrosHalV3BufferManagerReceivedFrameIsOutOfOrder,
FROM_HERE,
std::string("Received frame is out-of-order; expect frame number "
"greater than ") +
base::NumberToString(
last_received_frame_number_map_[stream_type]) +
std::string(" but got ") +
base::NumberToString(frame_number));
} else {
last_received_frame_number_map_[stream_type] = frame_number;
}
}
if (stream_buffer->status ==
cros::mojom::Camera3BufferStatus::CAMERA3_BUFFER_STATUS_ERROR) {
// If the buffer is marked as error, its content is discarded for this
// frame. Send the buffer to the free list directly through
// SubmitCaptureResult.
SubmitCaptureResult(frame_number, stream_type,
std::move(stream_buffer));
} else {
pending_result.buffers[stream_type] = std::move(stream_buffer);
}
TRACE_EVENT_END("camera", GetTraceTrack(CameraTraceEvent::kCaptureStream,
frame_number, stream_id));
}
}
TRACE_EVENT("camera", "Capture Result", "frame_number", frame_number);
TrySubmitPendingBuffers(frame_number);
}
void RequestManager::TrySubmitPendingBuffers(uint32_t frame_number) {
if (!pending_results_.count(frame_number)) {
return;
}
CaptureResult& pending_result = pending_results_[frame_number];
// If the metadata is not ready, or the shutter time is not set, just
// returned.
bool is_ready_to_submit =
pending_result.partial_metadata_received.size() > 0 &&
*pending_result.partial_metadata_received.rbegin() ==
partial_result_count_ &&
!pending_result.reference_time.is_null();
if (!is_ready_to_submit) {
return;
}
if (!pending_result.buffers.empty()) {
// Put pending buffers into local map since |pending_result| might be
// deleted in SubmitCaptureResult(). We should not reference pending_result
// after SubmitCaptureResult() is triggered.
std::map<StreamType, cros::mojom::Camera3StreamBufferPtr> buffers =
std::move(pending_result.buffers);
for (auto& it : buffers) {
SubmitCaptureResult(frame_number, it.first, std::move(it.second));
}
}
TRACE_EVENT_END(
"camera", GetTraceTrack(CameraTraceEvent::kCaptureRequest, frame_number));
}
void RequestManager::Notify(cros::mojom::Camera3NotifyMsgPtr message) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
if (!capturing_) {
return;
}
auto camera_app_device =
CameraAppDeviceBridgeImpl::GetInstance()->GetWeakCameraAppDevice(
device_id_);
if (message->type == cros::mojom::Camera3MsgType::CAMERA3_MSG_ERROR) {
auto error = std::move(message->message->get_error());
uint32_t frame_number = error->frame_number;
if (pending_results_.count(frame_number)) {
CaptureResult& pending_result = pending_results_[frame_number];
if (camera_app_device &&
(pending_result.still_capture_callback ||
pending_result.portrait_callbacks_map[StreamType::kJpegOutput])) {
camera_app_device->OnShutterDone();
}
}
uint64_t error_stream_id = error->error_stream_id;
StreamType stream_type = StreamIdToStreamType(error_stream_id);
if (stream_type == StreamType::kUnknown) {
device_context_->SetErrorState(
media::VideoCaptureError::
kCrosHalV3BufferManagerUnknownStreamInCamera3NotifyMsg,
FROM_HERE,
std::string("Unknown stream in Camera3NotifyMsg: ") +
base::NumberToString(error_stream_id));
return;
}
cros::mojom::Camera3ErrorMsgCode error_code = error->error_code;
HandleNotifyError(frame_number, stream_type, error_code);
} else if (message->type ==
cros::mojom::Camera3MsgType::CAMERA3_MSG_SHUTTER) {
auto shutter = std::move(message->message->get_shutter());
uint32_t frame_number = shutter->frame_number;
uint64_t shutter_time = shutter->timestamp;
DVLOG(2) << "Received shutter time for frame " << frame_number;
if (!shutter_time) {
device_context_->SetErrorState(
media::VideoCaptureError::
kCrosHalV3BufferManagerReceivedInvalidShutterTime,
FROM_HERE,
std::string("Received invalid shutter time: ") +
base::NumberToString(shutter_time));
return;
}
CaptureResult& pending_result = pending_results_[frame_number];
pending_result.shutter_timestamp = shutter_time;
// Shutter timestamp is in ns.
base::TimeTicks reference_time =
base::TimeTicks() + base::Microseconds(shutter_time / 1000);
pending_result.reference_time = reference_time;
if (first_frame_shutter_time_.is_null()) {
// Record the shutter time of the first frame for calculating the
// timestamp.
first_frame_shutter_time_ = reference_time;
}
pending_result.timestamp = reference_time - first_frame_shutter_time_;
if (camera_app_device &&
(pending_result.still_capture_callback ||
pending_result.portrait_callbacks_map[StreamType::kJpegOutput])) {
camera_app_device->OnShutterDone();
}
TrySubmitPendingBuffers(frame_number);
}
}
void RequestManager::HandleNotifyError(
uint32_t frame_number,
StreamType stream_type,
cros::mojom::Camera3ErrorMsgCode error_code) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
std::string warning_msg;
switch (error_code) {
case cros::mojom::Camera3ErrorMsgCode::CAMERA3_MSG_ERROR_DEVICE:
// Fatal error and no more frames will be produced by the device.
device_context_->SetErrorState(
media::VideoCaptureError::kCrosHalV3BufferManagerFatalDeviceError,
FROM_HERE, "Fatal device error");
return;
case cros::mojom::Camera3ErrorMsgCode::CAMERA3_MSG_ERROR_REQUEST:
// An error has occurred in processing the request; the request
// specified by |frame_number| has been dropped by the camera device.
// Subsequent requests are unaffected.
//
// The HAL will call ProcessCaptureResult with the buffers' state set to
// STATUS_ERROR. The content of the buffers will be dropped and the
// buffers will be reused in SubmitCaptureResult.
warning_msg =
std::string("An error occurred while processing request for frame ") +
base::NumberToString(frame_number);
break;
case cros::mojom::Camera3ErrorMsgCode::CAMERA3_MSG_ERROR_RESULT:
// An error has occurred in producing the output metadata buffer for a
// result; the output metadata will not be available for the frame
// specified by |frame_number|. Subsequent requests are unaffected.
warning_msg = std::string(
"An error occurred while producing result "
"metadata for frame ") +
base::NumberToString(frame_number);
break;
case cros::mojom::Camera3ErrorMsgCode::CAMERA3_MSG_ERROR_BUFFER:
// An error has occurred in placing the output buffer into a stream for
// a request. |frame_number| specifies the request for which the buffer
// was dropped, and |stream_type| specifies the stream that dropped
// the buffer.
//
// The HAL will call ProcessCaptureResult with the buffer's state set to
// STATUS_ERROR. The content of the buffer will be dropped and the
// buffer will be reused in SubmitCaptureResult.
warning_msg =
std::string(
"An error occurred while filling output buffer for frame ") +
base::NumberToString(frame_number);
break;
default:
// To eliminate the warning for not handling CAMERA3_MSG_NUM_ERRORS
break;
}
LOG(WARNING) << warning_msg << " with type = " << stream_type;
device_context_->LogToClient(warning_msg);
// If the buffer is already returned by the HAL, submit it and we're done.
if (pending_results_.count(frame_number)) {
auto it = pending_results_[frame_number].buffers.find(stream_type);
if (it != pending_results_[frame_number].buffers.end()) {
auto stream_buffer = std::move(it->second);
pending_results_[frame_number].buffers.erase(stream_type);
SubmitCaptureResult(frame_number, stream_type, std::move(stream_buffer));
}
}
}
void RequestManager::RequestStreamBuffers(
std::vector<cros::mojom::Camera3BufferRequestPtr> buffer_reqs,
RequestStreamBuffersCallback callback) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
if (!capturing_) {
std::move(callback).Run(cros::mojom::Camera3BufferRequestStatus::
CAMERA3_BUF_REQ_FAILED_CONFIGURING,
{});
return;
}
// Validate arguments.
if (buffer_reqs.empty()) {
std::move(callback).Run(cros::mojom::Camera3BufferRequestStatus::
CAMERA3_BUF_REQ_FAILED_ILLEGAL_ARGUMENTS,
{});
return;
}
std::set<uint64_t> stream_ids;
for (const auto& req : buffer_reqs) {
StreamType stream_type = StreamIdToStreamType(req->stream_id);
if (stream_type == StreamType::kUnknown ||
stream_ids.count(req->stream_id) > 0) {
std::move(callback).Run(cros::mojom::Camera3BufferRequestStatus::
CAMERA3_BUF_REQ_FAILED_ILLEGAL_ARGUMENTS,
{});
return;
}
stream_ids.insert(req->stream_id);
}
std::vector<mojo::StructPtr<cros::mojom::Camera3StreamBufferRet>> rets;
size_t error_count = 0;
for (const auto& req : buffer_reqs) {
rets.push_back(cros::mojom::Camera3StreamBufferRet::New());
auto& ret = rets.back();
ret->stream_id = req->stream_id;
StreamType stream_type = StreamIdToStreamType(req->stream_id);
if (stream_buffer_manager_->GetFreeBufferCount(stream_type) <
req->num_buffers_requested) {
ret->status = cros::mojom::Camera3StreamBufferReqStatus::
CAMERA3_PS_BUF_REQ_MAX_BUFFER_EXCEEDED;
++error_count;
continue;
}
ret->status =
cros::mojom::Camera3StreamBufferReqStatus::CAMERA3_PS_BUF_REQ_OK;
ret->output_buffers = std::vector<cros::mojom::Camera3StreamBufferPtr>();
for (size_t i = 0; i < req->num_buffers_requested; ++i) {
std::optional<BufferInfo> buffer_info =
stream_buffer_manager_->RequestBufferForCaptureRequest(stream_type);
if (!buffer_info.has_value()) {
// Return buffers to |stream_buffer_manager_|.
for (const auto& b : *ret->output_buffers) {
stream_buffer_manager_->ReleaseBufferFromCaptureResult(stream_type,
b->buffer_id);
}
ret->status = cros::mojom::Camera3StreamBufferReqStatus::
CAMERA3_PS_BUF_REQ_UNKNOWN_ERROR;
++error_count;
break;
}
cros::mojom::Camera3StreamBufferPtr stream_buffer =
request_builder_->CreateStreamBuffer(stream_type,
std::move(*buffer_info));
ret->output_buffers->push_back(std::move(stream_buffer));
}
}
cros::mojom::Camera3BufferRequestStatus status =
cros::mojom::Camera3BufferRequestStatus::CAMERA3_BUF_REQ_OK;
if (error_count == buffer_reqs.size()) {
status =
cros::mojom::Camera3BufferRequestStatus::CAMERA3_BUF_REQ_FAILED_UNKNOWN;
} else if (error_count > 0) {
status =
cros::mojom::Camera3BufferRequestStatus::CAMERA3_BUF_REQ_FAILED_PARTIAL;
}
std::move(callback).Run(status, std::move(rets));
}
void RequestManager::ReturnStreamBuffers(
std::vector<cros::mojom::Camera3StreamBufferPtr> buffers) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
for (const auto& buffer : buffers) {
StreamType stream_type = StreamIdToStreamType(buffer->stream_id);
if (stream_type == StreamType::kUnknown) {
continue;
}
stream_buffer_manager_->ReleaseBufferFromCaptureResult(stream_type,
buffer->buffer_id);
}
}
void RequestManager::SubmitCaptureResult(
uint32_t frame_number,
StreamType stream_type,
cros::mojom::Camera3StreamBufferPtr stream_buffer) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
DCHECK(pending_results_.count(frame_number));
CaptureResult& pending_result = pending_results_[frame_number];
DVLOG(2) << "Submit capture result of frame " << frame_number
<< " for stream " << static_cast<int>(stream_type);
for (ResultMetadataObserver* observer : result_metadata_observers_) {
observer->OnResultMetadataAvailable(frame_number, pending_result.metadata);
}
auto camera_app_device =
CameraAppDeviceBridgeImpl::GetInstance()->GetWeakCameraAppDevice(
device_id_);
if (camera_app_device) {
camera_app_device->OnResultMetadataAvailable(
pending_result.metadata,
static_cast<cros::mojom::StreamType>(stream_type));
}
// Wait on release fence before delivering the result buffer to client.
if (stream_buffer->release_fence.is_valid()) {
const int kSyncWaitTimeoutMs = 1000;
mojo::PlatformHandle fence =
mojo::UnwrapPlatformHandle(std::move(stream_buffer->release_fence));
if (!fence.is_valid()) {
device_context_->SetErrorState(
media::VideoCaptureError::
kCrosHalV3BufferManagerFailedToUnwrapReleaseFenceFd,
FROM_HERE, "Failed to unwrap release fence fd");
return;
}
if (sync_wait(fence.GetFD().get(), kSyncWaitTimeoutMs)) {
device_context_->SetErrorState(
media::VideoCaptureError::
kCrosHalV3BufferManagerSyncWaitOnReleaseFenceTimedOut,
FROM_HERE, "Sync wait on release fence timed out");
return;
}
}
uint64_t buffer_ipc_id = stream_buffer->buffer_id;
// Deliver the captured data to client.
if (stream_buffer->status ==
cros::mojom::Camera3BufferStatus::CAMERA3_BUFFER_STATUS_OK) {
if (stream_type == StreamType::kPreviewOutput ||
stream_type == StreamType::kRecordingOutput) {
SubmitCapturedPreviewRecordingBuffer(frame_number, buffer_ipc_id,
stream_type);
} else if (stream_type == StreamType::kJpegOutput ||
stream_type == StreamType::kPortraitJpegOutput) {
SubmitCapturedJpegBuffer(frame_number, buffer_ipc_id, stream_type);
}
} else {
stream_buffer_manager_->ReleaseBufferFromCaptureResult(stream_type,
buffer_ipc_id);
}
if (stream_type == StreamType::kPreviewOutput) {
--preview_buffers_queued_;
}
pending_result.unsubmitted_buffer_count--;
if (pending_result.unsubmitted_buffer_count == 0) {
pending_results_.erase(frame_number);
}
// Every time a buffer is released, try to prepare another capture request
// again.
PrepareCaptureRequest();
}
void RequestManager::SubmitCapturedPreviewRecordingBuffer(
uint32_t frame_number,
uint64_t buffer_ipc_id,
StreamType stream_type) {
const CaptureResult& pending_result = pending_results_[frame_number];
auto client_type = kStreamClientTypeMap[static_cast<int>(stream_type)];
if (video_capture_use_gmb_) {
VideoCaptureFormat format;
std::optional<VideoCaptureDevice::Client::Buffer> buffer =
stream_buffer_manager_->AcquireBufferForClientById(
stream_type, buffer_ipc_id, &format);
CHECK(buffer);
// TODO: Figure out the right color space for the camera frame. We may need
// to populate the camera metadata with the color space reported by the V4L2
// device.
VideoFrameMetadata metadata;
if (!device_context_->IsCameraFrameRotationEnabledAtSource()) {
// Camera frame rotation at source is disabled, so we record the intended
// video frame rotation in the metadata. The consumer of the video frame
// is responsible for taking care of the frame rotation.
auto translate_rotation = [](const int rotation) -> VideoRotation {
switch (rotation) {
case 0:
return VIDEO_ROTATION_0;
case 90:
return VIDEO_ROTATION_90;
case 180:
return VIDEO_ROTATION_180;
case 270:
return VIDEO_ROTATION_270;
}
return VIDEO_ROTATION_0;
};
metadata.transformation =
translate_rotation(device_context_->GetCameraFrameRotation());
} else {
// All frames are pre-rotated to the display orientation.
metadata.transformation = VIDEO_ROTATION_0;
}
auto camera_app_device =
CameraAppDeviceBridgeImpl::GetInstance()->GetWeakCameraAppDevice(
device_id_);
if (camera_app_device && stream_type == StreamType::kPreviewOutput) {
camera_app_device->MaybeDetectDocumentCorners(
stream_buffer_manager_->CreateGpuMemoryBuffer(
buffer->handle_provider->GetGpuMemoryBufferHandle(), format,
gfx::BufferUsage::VEA_READ_CAMERA_AND_CPU_READ_WRITE),
metadata.transformation->rotation);
}
device_context_->SubmitCapturedVideoCaptureBuffer(
client_type, std::move(*buffer), format, pending_result.reference_time,
pending_result.timestamp, metadata);
// |buffer| ownership is transferred to client, so we need to reserve a
// new video buffer.
stream_buffer_manager_->ReserveBuffer(stream_type);
} else {
gfx::GpuMemoryBuffer* gmb = stream_buffer_manager_->GetGpuMemoryBufferById(
stream_type, buffer_ipc_id);
CHECK(gmb);
device_context_->SubmitCapturedGpuMemoryBuffer(
client_type, gmb,
stream_buffer_manager_->GetStreamCaptureFormat(stream_type),
pending_result.reference_time, pending_result.timestamp);
stream_buffer_manager_->ReleaseBufferFromCaptureResult(stream_type,
buffer_ipc_id);
}
}
void RequestManager::SubmitCapturedJpegBuffer(uint32_t frame_number,
uint64_t buffer_ipc_id,
StreamType stream_type) {
CaptureResult& pending_result = pending_results_[frame_number];
gfx::Size buffer_dimension =
stream_buffer_manager_->GetBufferDimension(stream_type);
gfx::GpuMemoryBuffer* gmb = stream_buffer_manager_->GetGpuMemoryBufferById(
stream_type, buffer_ipc_id);
CHECK(gmb);
if (!gmb->Map()) {
device_context_->SetErrorState(
media::VideoCaptureError::
kCrosHalV3BufferManagerFailedToCreateGpuMemoryBuffer,
FROM_HERE, "Failed to map GPU memory buffer");
return;
}
absl::Cleanup unmap_gmb = [gmb] { gmb->Unmap(); };
const Camera3JpegBlob* header = reinterpret_cast<Camera3JpegBlob*>(
reinterpret_cast<const uintptr_t>(gmb->memory(0)) +
buffer_dimension.width() - sizeof(Camera3JpegBlob));
if (header->jpeg_blob_id != kCamera3JpegBlobId) {
device_context_->SetErrorState(
media::VideoCaptureError::kCrosHalV3BufferManagerInvalidJpegBlob,
FROM_HERE, "Invalid JPEG blob");
return;
}
// Still capture result from HALv3 already has orientation info in EXIF,
// so just provide 0 as screen rotation in |blobify_callback_| parameters.
mojom::BlobPtr blob = blobify_callback_.Run(
reinterpret_cast<const uint8_t*>(gmb->memory(0)), header->jpeg_size,
stream_buffer_manager_->GetStreamCaptureFormat(stream_type), 0);
if (blob) {
if (stream_type == StreamType::kJpegOutput &&
pending_result.portrait_callbacks_map[StreamType::kJpegOutput]) {
std::move(pending_result.portrait_callbacks_map[StreamType::kJpegOutput])
.Run(0, std::move(blob));
} else if (stream_type == StreamType::kPortraitJpegOutput &&
pending_result
.portrait_callbacks_map[StreamType::kPortraitJpegOutput]) {
int status = CameraAppDeviceImpl::GetPortraitSegResultCode(
&pending_result.metadata);
std::move(pending_result
.portrait_callbacks_map[StreamType::kPortraitJpegOutput])
.Run(status, std::move(blob));
} else if (pending_result.still_capture_callback) {
std::move(pending_result.still_capture_callback).Run(std::move(blob));
}
} else {
// TODO(wtlee): If it is fatal, we should set error state here.
LOG(ERROR) << "Failed to blobify the captured JPEG image";
}
stream_buffer_manager_->ReleaseBufferFromCaptureResult(stream_type,
buffer_ipc_id);
}
void RequestManager::UpdateCaptureSettings(
cros::mojom::CameraMetadataPtr* capture_settings) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
if (capture_settings_override_.empty() &&
capture_settings_repeating_override_.empty()) {
return;
}
for (const auto& setting : capture_settings_repeating_override_) {
AddOrUpdateMetadataEntry(capture_settings, setting.second.Clone());
}
for (auto& s : capture_settings_override_) {
AddOrUpdateMetadataEntry(capture_settings, std::move(s));
}
capture_settings_override_.clear();
SortCameraMetadata(capture_settings);
}
RequestManager::CaptureResult::CaptureResult()
: metadata(cros::mojom::CameraMetadata::New()),
unsubmitted_buffer_count(0) {}
RequestManager::CaptureResult::~CaptureResult() = default;
} // namespace media