blob: 8449f418465fea2ec6ec8abc883ea1fd16f69a62 [file] [log] [blame]
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +01001/*
2 * Copyright (C) 2023 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17// #define LOG_NDEBUG 0
18#define LOG_TAG "VirtualCameraSession"
19#include "VirtualCameraSession.h"
20
Jan Sebechlebsky39129f82024-01-19 16:42:11 +010021#include <algorithm>
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +010022#include <atomic>
23#include <chrono>
24#include <cstddef>
25#include <cstdint>
26#include <cstring>
27#include <map>
28#include <memory>
29#include <mutex>
Jan Sebechlebskyc3e1a632024-02-06 14:19:05 +010030#include <numeric>
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +010031#include <optional>
32#include <tuple>
33#include <unordered_set>
34#include <utility>
35#include <vector>
36
37#include "CameraMetadata.h"
38#include "EGL/egl.h"
Jan Sebechlebsky3b478c42023-11-23 13:15:56 +010039#include "VirtualCameraDevice.h"
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +010040#include "VirtualCameraRenderThread.h"
41#include "VirtualCameraStream.h"
42#include "aidl/android/hardware/camera/common/Status.h"
43#include "aidl/android/hardware/camera/device/BufferCache.h"
44#include "aidl/android/hardware/camera/device/BufferStatus.h"
45#include "aidl/android/hardware/camera/device/CaptureRequest.h"
46#include "aidl/android/hardware/camera/device/HalStream.h"
47#include "aidl/android/hardware/camera/device/NotifyMsg.h"
Jan Sebechlebskyc3e1a632024-02-06 14:19:05 +010048#include "aidl/android/hardware/camera/device/RequestTemplate.h"
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +010049#include "aidl/android/hardware/camera/device/ShutterMsg.h"
50#include "aidl/android/hardware/camera/device/StreamBuffer.h"
51#include "aidl/android/hardware/camera/device/StreamConfiguration.h"
52#include "aidl/android/hardware/camera/device/StreamRotation.h"
53#include "aidl/android/hardware/graphics/common/BufferUsage.h"
54#include "aidl/android/hardware/graphics/common/PixelFormat.h"
55#include "android/hardware_buffer.h"
56#include "android/native_window_aidl.h"
57#include "fmq/AidlMessageQueue.h"
58#include "system/camera_metadata.h"
59#include "ui/GraphicBuffer.h"
60#include "util/EglDisplayContext.h"
61#include "util/EglFramebuffer.h"
62#include "util/EglProgram.h"
63#include "util/JpegUtil.h"
64#include "util/MetadataBuilder.h"
65#include "util/TestPatternHelper.h"
66#include "util/Util.h"
67
68namespace android {
69namespace companion {
70namespace virtualcamera {
71
72using ::aidl::android::companion::virtualcamera::Format;
73using ::aidl::android::companion::virtualcamera::IVirtualCameraCallback;
Jan Sebechlebskyc3e1a632024-02-06 14:19:05 +010074using ::aidl::android::companion::virtualcamera::SupportedStreamConfiguration;
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +010075using ::aidl::android::hardware::camera::common::Status;
76using ::aidl::android::hardware::camera::device::BufferCache;
77using ::aidl::android::hardware::camera::device::CameraMetadata;
78using ::aidl::android::hardware::camera::device::CameraOfflineSessionInfo;
79using ::aidl::android::hardware::camera::device::CaptureRequest;
80using ::aidl::android::hardware::camera::device::HalStream;
81using ::aidl::android::hardware::camera::device::ICameraDeviceCallback;
82using ::aidl::android::hardware::camera::device::ICameraOfflineSession;
83using ::aidl::android::hardware::camera::device::RequestTemplate;
84using ::aidl::android::hardware::camera::device::Stream;
85using ::aidl::android::hardware::camera::device::StreamBuffer;
86using ::aidl::android::hardware::camera::device::StreamConfiguration;
87using ::aidl::android::hardware::camera::device::StreamRotation;
88using ::aidl::android::hardware::common::fmq::MQDescriptor;
89using ::aidl::android::hardware::common::fmq::SynchronizedReadWrite;
90using ::aidl::android::hardware::graphics::common::BufferUsage;
91using ::aidl::android::hardware::graphics::common::PixelFormat;
92using ::android::base::unique_fd;
93
94namespace {
95
96using metadata_ptr =
97 std::unique_ptr<camera_metadata_t, void (*)(camera_metadata_t*)>;
98
99using namespace std::chrono_literals;
100
101// Size of request/result metadata fast message queue.
102// Setting to 0 to always disables FMQ.
103static constexpr size_t kMetadataMsgQueueSize = 0;
104
105// Maximum number of buffers to use per single stream.
106static constexpr size_t kMaxStreamBuffers = 2;
107
Jan Sebechlebskyc3e1a632024-02-06 14:19:05 +0100108camera_metadata_enum_android_control_capture_intent_t requestTemplateToIntent(
109 const RequestTemplate type) {
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100110 switch (type) {
111 case RequestTemplate::PREVIEW:
Jan Sebechlebskyc3e1a632024-02-06 14:19:05 +0100112 return ANDROID_CONTROL_CAPTURE_INTENT_PREVIEW;
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100113 case RequestTemplate::STILL_CAPTURE:
Jan Sebechlebskyc3e1a632024-02-06 14:19:05 +0100114 return ANDROID_CONTROL_CAPTURE_INTENT_STILL_CAPTURE;
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100115 case RequestTemplate::VIDEO_RECORD:
Jan Sebechlebskyc3e1a632024-02-06 14:19:05 +0100116 return ANDROID_CONTROL_CAPTURE_INTENT_VIDEO_RECORD;
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100117 case RequestTemplate::VIDEO_SNAPSHOT:
Jan Sebechlebskyc3e1a632024-02-06 14:19:05 +0100118 return ANDROID_CONTROL_CAPTURE_INTENT_VIDEO_SNAPSHOT;
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100119 default:
Jan Sebechlebskyc3e1a632024-02-06 14:19:05 +0100120 // Return PREVIEW by default
121 return ANDROID_CONTROL_CAPTURE_INTENT_PREVIEW;
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100122 }
Jan Sebechlebskyc3e1a632024-02-06 14:19:05 +0100123}
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100124
Jan Sebechlebskyc3e1a632024-02-06 14:19:05 +0100125int getMaxFps(const std::vector<SupportedStreamConfiguration>& configs) {
126 return std::transform_reduce(
127 configs.begin(), configs.end(), 0,
128 [](const int a, const int b) { return std::max(a, b); },
129 [](const SupportedStreamConfiguration& config) { return config.maxFps; });
130}
131
132CameraMetadata createDefaultRequestSettings(
133 const RequestTemplate type,
134 const std::vector<SupportedStreamConfiguration>& inputConfigs) {
135 int maxFps = getMaxFps(inputConfigs);
136 auto metadata =
137 MetadataBuilder()
138 .setControlCaptureIntent(requestTemplateToIntent(type))
139 .setControlMode(ANDROID_CONTROL_MODE_AUTO)
140 .setControlAeMode(ANDROID_CONTROL_AE_MODE_ON)
141 .setControlAeExposureCompensation(0)
142 .setControlAeTargetFpsRange(maxFps, maxFps)
143 .setControlAeAntibandingMode(ANDROID_CONTROL_AE_ANTIBANDING_MODE_AUTO)
144 .setControlAePrecaptureTrigger(
145 ANDROID_CONTROL_AE_PRECAPTURE_TRIGGER_IDLE)
146 .setControlAfTrigger(ANDROID_CONTROL_AF_TRIGGER_IDLE)
147 .setControlAfMode(ANDROID_CONTROL_AF_MODE_AUTO)
148 .setControlAwbMode(ANDROID_CONTROL_AWB_MODE_AUTO)
149 .setFaceDetectMode(ANDROID_STATISTICS_FACE_DETECT_MODE_OFF)
150 .setFlashMode(ANDROID_FLASH_MODE_OFF)
151 .build();
152 if (metadata == nullptr) {
153 ALOGE("%s: Failed to construct metadata for default request type %s",
154 __func__, toString(type).c_str());
155 return CameraMetadata();
156 } else {
157 ALOGV("%s: Successfully created metadata for request type %s", __func__,
158 toString(type).c_str());
159 }
160 return *metadata;
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100161}
162
163HalStream getHalStream(const Stream& stream) {
164 HalStream halStream;
165 halStream.id = stream.id;
166 halStream.physicalCameraId = stream.physicalCameraId;
167 halStream.maxBuffers = kMaxStreamBuffers;
168
169 if (stream.format == PixelFormat::IMPLEMENTATION_DEFINED) {
170 // If format is implementation defined we need it to override
171 // it with actual format.
172 // TODO(b/301023410) Override with the format based on the
173 // camera configuration, once we support more formats.
174 halStream.overrideFormat = PixelFormat::YCBCR_420_888;
175 } else {
176 halStream.overrideFormat = stream.format;
177 }
178 halStream.overrideDataSpace = stream.dataSpace;
179
180 halStream.producerUsage = BufferUsage::GPU_RENDER_TARGET;
181 halStream.supportOffline = false;
182 return halStream;
183}
184
Jan Sebechlebsky39129f82024-01-19 16:42:11 +0100185Stream getHighestResolutionStream(const std::vector<Stream>& streams) {
186 return *(std::max_element(streams.begin(), streams.end(),
187 [](const Stream& a, const Stream& b) {
188 return a.width * a.height < b.width * b.height;
189 }));
190}
191
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100192} // namespace
193
194VirtualCameraSession::VirtualCameraSession(
Jan Sebechlebsky0bb5e092023-12-08 16:17:54 +0100195 std::shared_ptr<VirtualCameraDevice> cameraDevice,
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100196 std::shared_ptr<ICameraDeviceCallback> cameraDeviceCallback,
197 std::shared_ptr<IVirtualCameraCallback> virtualCameraClientCallback)
Jan Sebechlebsky3b478c42023-11-23 13:15:56 +0100198 : mCameraDevice(cameraDevice),
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100199 mCameraDeviceCallback(cameraDeviceCallback),
200 mVirtualCameraClientCallback(virtualCameraClientCallback) {
201 mRequestMetadataQueue = std::make_unique<RequestMetadataQueue>(
202 kMetadataMsgQueueSize, false /* non blocking */);
203 if (!mRequestMetadataQueue->isValid()) {
204 ALOGE("%s: invalid request fmq", __func__);
205 }
206
207 mResultMetadataQueue = std::make_shared<ResultMetadataQueue>(
208 kMetadataMsgQueueSize, false /* non blocking */);
209 if (!mResultMetadataQueue->isValid()) {
210 ALOGE("%s: invalid result fmq", __func__);
211 }
212}
213
214ndk::ScopedAStatus VirtualCameraSession::close() {
215 ALOGV("%s", __func__);
216
217 if (mVirtualCameraClientCallback != nullptr) {
218 mVirtualCameraClientCallback->onStreamClosed(/*streamId=*/0);
219 }
220
Jan Sebechlebskyb0d8cab2023-11-28 10:55:04 +0100221 {
222 std::lock_guard<std::mutex> lock(mLock);
223 if (mRenderThread != nullptr) {
224 mRenderThread->stop();
225 mRenderThread = nullptr;
226 }
227 }
228
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100229 mSessionContext.closeAllStreams();
230 return ndk::ScopedAStatus::ok();
231}
232
233ndk::ScopedAStatus VirtualCameraSession::configureStreams(
234 const StreamConfiguration& in_requestedConfiguration,
235 std::vector<HalStream>* _aidl_return) {
236 ALOGV("%s: requestedConfiguration: %s", __func__,
237 in_requestedConfiguration.toString().c_str());
238
239 if (_aidl_return == nullptr) {
240 return cameraStatus(Status::ILLEGAL_ARGUMENT);
241 }
242
Jan Sebechlebsky0bb5e092023-12-08 16:17:54 +0100243 std::shared_ptr<VirtualCameraDevice> virtualCamera = mCameraDevice.lock();
244 if (virtualCamera == nullptr) {
245 ALOGW("%s: configure called on already unregistered camera", __func__);
246 return cameraStatus(Status::CAMERA_DISCONNECTED);
247 }
248
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100249 mSessionContext.removeStreamsNotInStreamConfiguration(
250 in_requestedConfiguration);
251
252 auto& streams = in_requestedConfiguration.streams;
253 auto& halStreams = *_aidl_return;
254 halStreams.clear();
255 halStreams.resize(in_requestedConfiguration.streams.size());
256
257 sp<Surface> inputSurface = nullptr;
258 int inputWidth;
259 int inputHeight;
260
Jan Sebechlebsky0bb5e092023-12-08 16:17:54 +0100261 if (!virtualCamera->isStreamCombinationSupported(in_requestedConfiguration)) {
Jan Sebechlebsky3b478c42023-11-23 13:15:56 +0100262 ALOGE("%s: Requested stream configuration is not supported", __func__);
263 return cameraStatus(Status::ILLEGAL_ARGUMENT);
264 }
265
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100266 {
267 std::lock_guard<std::mutex> lock(mLock);
268 for (int i = 0; i < in_requestedConfiguration.streams.size(); ++i) {
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100269 halStreams[i] = getHalStream(streams[i]);
270 if (mSessionContext.initializeStream(streams[i])) {
271 ALOGV("Configured new stream: %s", streams[i].toString().c_str());
272 }
273 }
274
Jan Sebechlebsky39129f82024-01-19 16:42:11 +0100275 Stream maxResStream = getHighestResolutionStream(streams);
276 inputWidth = maxResStream.width;
277 inputHeight = maxResStream.height;
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100278 if (mRenderThread == nullptr) {
279 // If there's no client callback, start camera in test mode.
280 const bool testMode = mVirtualCameraClientCallback == nullptr;
281 mRenderThread = std::make_unique<VirtualCameraRenderThread>(
282 mSessionContext, inputWidth, inputHeight, mCameraDeviceCallback,
283 testMode);
284 mRenderThread->start();
285 inputSurface = mRenderThread->getInputSurface();
286 }
287 }
288
289 if (mVirtualCameraClientCallback != nullptr && inputSurface != nullptr) {
290 // TODO(b/301023410) Pass streamId based on client input stream id once
291 // support for multiple input streams is implemented. For now we always
292 // create single texture.
293 mVirtualCameraClientCallback->onStreamConfigured(
294 /*streamId=*/0, aidl::android::view::Surface(inputSurface.get()),
295 inputWidth, inputHeight, Format::YUV_420_888);
296 }
297
298 mFirstRequest.store(true);
299 return ndk::ScopedAStatus::ok();
300}
301
302ndk::ScopedAStatus VirtualCameraSession::constructDefaultRequestSettings(
303 RequestTemplate in_type, CameraMetadata* _aidl_return) {
304 ALOGV("%s: type %d", __func__, static_cast<int32_t>(in_type));
305
Jan Sebechlebskyc3e1a632024-02-06 14:19:05 +0100306 std::shared_ptr<VirtualCameraDevice> camera = mCameraDevice.lock();
307 if (camera == nullptr) {
308 ALOGW(
309 "%s: constructDefaultRequestSettings called on already unregistered "
310 "camera",
311 __func__);
312 return cameraStatus(Status::CAMERA_DISCONNECTED);
313 }
314
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100315 switch (in_type) {
316 case RequestTemplate::PREVIEW:
317 case RequestTemplate::STILL_CAPTURE:
Jan Sebechlebskyb0119fa2023-12-04 10:29:06 +0100318 case RequestTemplate::VIDEO_RECORD:
319 case RequestTemplate::VIDEO_SNAPSHOT: {
Jan Sebechlebskyc3e1a632024-02-06 14:19:05 +0100320 *_aidl_return =
321 createDefaultRequestSettings(in_type, camera->getInputConfigs());
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100322 return ndk::ScopedAStatus::ok();
323 }
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100324 case RequestTemplate::MANUAL:
325 case RequestTemplate::ZERO_SHUTTER_LAG:
326 // Don't support VIDEO_SNAPSHOT, MANUAL, ZSL templates
327 return ndk::ScopedAStatus::fromServiceSpecificError(
328 static_cast<int32_t>(Status::ILLEGAL_ARGUMENT));
329 ;
330 default:
331 ALOGE("%s: unknown request template type %d", __FUNCTION__,
332 static_cast<int>(in_type));
333 return ndk::ScopedAStatus::fromServiceSpecificError(
334 static_cast<int32_t>(Status::ILLEGAL_ARGUMENT));
335 ;
336 }
337}
338
339ndk::ScopedAStatus VirtualCameraSession::flush() {
340 ALOGV("%s", __func__);
341 std::lock_guard<std::mutex> lock(mLock);
Jan Sebechlebskyb0d8cab2023-11-28 10:55:04 +0100342 if (mRenderThread != nullptr) {
343 mRenderThread->flush();
344 }
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100345 return ndk::ScopedAStatus::ok();
346}
347
348ndk::ScopedAStatus VirtualCameraSession::getCaptureRequestMetadataQueue(
349 MQDescriptor<int8_t, SynchronizedReadWrite>* _aidl_return) {
350 ALOGV("%s", __func__);
351 *_aidl_return = mRequestMetadataQueue->dupeDesc();
352 return ndk::ScopedAStatus::ok();
353}
354
355ndk::ScopedAStatus VirtualCameraSession::getCaptureResultMetadataQueue(
356 MQDescriptor<int8_t, SynchronizedReadWrite>* _aidl_return) {
357 ALOGV("%s", __func__);
358 *_aidl_return = mResultMetadataQueue->dupeDesc();
359 return ndk::ScopedAStatus::ok();
360}
361
362ndk::ScopedAStatus VirtualCameraSession::isReconfigurationRequired(
363 const CameraMetadata& in_oldSessionParams,
364 const CameraMetadata& in_newSessionParams, bool* _aidl_return) {
365 ALOGV("%s: oldSessionParams: %s newSessionParams: %s", __func__,
366 in_newSessionParams.toString().c_str(),
367 in_oldSessionParams.toString().c_str());
368
369 if (_aidl_return == nullptr) {
370 return ndk::ScopedAStatus::fromServiceSpecificError(
371 static_cast<int32_t>(Status::ILLEGAL_ARGUMENT));
372 }
373
374 *_aidl_return = true;
375 return ndk::ScopedAStatus::ok();
376}
377
378ndk::ScopedAStatus VirtualCameraSession::processCaptureRequest(
379 const std::vector<CaptureRequest>& in_requests,
380 const std::vector<BufferCache>& in_cachesToRemove, int32_t* _aidl_return) {
381 ALOGV("%s", __func__);
382
383 if (!in_cachesToRemove.empty()) {
384 mSessionContext.removeBufferCaches(in_cachesToRemove);
385 }
386
387 for (const auto& captureRequest : in_requests) {
388 auto status = processCaptureRequest(captureRequest);
389 if (!status.isOk()) {
390 return status;
391 }
392 }
393 *_aidl_return = in_requests.size();
394 return ndk::ScopedAStatus::ok();
395}
396
397ndk::ScopedAStatus VirtualCameraSession::signalStreamFlush(
398 const std::vector<int32_t>& in_streamIds, int32_t in_streamConfigCounter) {
399 ALOGV("%s", __func__);
400
401 (void)in_streamIds;
402 (void)in_streamConfigCounter;
403 return ndk::ScopedAStatus::ok();
404}
405
406ndk::ScopedAStatus VirtualCameraSession::switchToOffline(
407 const std::vector<int32_t>& in_streamsToKeep,
408 CameraOfflineSessionInfo* out_offlineSessionInfo,
409 std::shared_ptr<ICameraOfflineSession>* _aidl_return) {
410 ALOGV("%s", __func__);
411
412 (void)in_streamsToKeep;
413 (void)out_offlineSessionInfo;
414
415 if (_aidl_return == nullptr) {
416 return ndk::ScopedAStatus::fromServiceSpecificError(
417 static_cast<int32_t>(Status::ILLEGAL_ARGUMENT));
418 }
419
420 *_aidl_return = nullptr;
421 return cameraStatus(Status::OPERATION_NOT_SUPPORTED);
422}
423
424ndk::ScopedAStatus VirtualCameraSession::repeatingRequestEnd(
425 int32_t in_frameNumber, const std::vector<int32_t>& in_streamIds) {
426 ALOGV("%s", __func__);
427 (void)in_frameNumber;
428 (void)in_streamIds;
429 return ndk::ScopedAStatus::ok();
430}
431
432std::set<int> VirtualCameraSession::getStreamIds() const {
433 return mSessionContext.getStreamIds();
434}
435
436ndk::ScopedAStatus VirtualCameraSession::processCaptureRequest(
437 const CaptureRequest& request) {
438 ALOGD("%s: request: %s", __func__, request.toString().c_str());
439
440 if (mFirstRequest.exchange(false) && request.settings.metadata.empty()) {
441 return cameraStatus(Status::ILLEGAL_ARGUMENT);
442 }
443
444 std::shared_ptr<ICameraDeviceCallback> cameraCallback = nullptr;
445 {
446 std::lock_guard<std::mutex> lock(mLock);
447 cameraCallback = mCameraDeviceCallback;
448 }
449
450 if (cameraCallback == nullptr) {
451 ALOGE(
452 "%s: processCaptureRequest called, but there's no camera callback "
453 "configured",
454 __func__);
455 return cameraStatus(Status::INTERNAL_ERROR);
456 }
457
458 if (!mSessionContext.importBuffersFromCaptureRequest(request)) {
459 ALOGE("Failed to import buffers from capture request.");
460 return cameraStatus(Status::INTERNAL_ERROR);
461 }
462
463 std::vector<CaptureRequestBuffer> taskBuffers;
464 taskBuffers.reserve(request.outputBuffers.size());
465 for (const StreamBuffer& streamBuffer : request.outputBuffers) {
466 taskBuffers.emplace_back(streamBuffer.streamId, streamBuffer.bufferId,
467 importFence(streamBuffer.acquireFence));
468 }
469
470 {
471 std::lock_guard<std::mutex> lock(mLock);
472 if (mRenderThread == nullptr) {
473 ALOGE(
474 "%s: processCaptureRequest (frameNumber %d)called before configure "
475 "(render thread not initialized)",
476 __func__, request.frameNumber);
477 return cameraStatus(Status::INTERNAL_ERROR);
478 }
479 mRenderThread->enqueueTask(std::make_unique<ProcessCaptureRequestTask>(
480 request.frameNumber, taskBuffers));
481 }
482
483 if (mVirtualCameraClientCallback != nullptr) {
484 auto status = mVirtualCameraClientCallback->onProcessCaptureRequest(
485 /*streamId=*/0, request.frameNumber);
486 if (!status.isOk()) {
487 ALOGE(
488 "Failed to invoke onProcessCaptureRequest client callback for frame "
489 "%d",
490 request.frameNumber);
491 }
492 }
493
494 return ndk::ScopedAStatus::ok();
495}
496
497} // namespace virtualcamera
498} // namespace companion
499} // namespace android