| /* |
| * Copyright (C) 2025 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| // #define LOG_NDEBUG 0 |
| #define LOG_TAG "VirtualCameraImagePassthroughHandler" |
| |
| #include "VirtualCameraImagePassthroughHandler.h" |
| |
| #include <aidl/android/hardware/camera/device/CameraBlob.h> |
| #include <aidl/android/hardware/camera/device/CameraBlobId.h> |
| #include <android/hardware_buffer.h> |
| #include <gui/Surface.h> |
| #include <media/NdkImageReader.h> |
| #include <media/NdkMediaError.h> |
| #include <sys/select.h> |
| #include <unistd.h> |
| |
| #include <algorithm> |
| #include <chrono> |
| #include <cstddef> |
| #include <cstdint> |
| #include <functional> |
| #include <memory> |
| |
| #include "VirtualCameraCaptureRequest.h" |
| #include "VirtualCameraSessionContext.h" |
| #include "aidl/android/companion/virtualcamera/Format.h" |
| #include "aidl/android/hardware/camera/device/CaptureResult.h" |
| #include "aidl/android/hardware/camera/device/Stream.h" |
| #include "android/binder_auto_utils.h" |
| #include "util/JpegUtil.h" |
| #include "util/Util.h" |
| #include "utils/Errors.h" |
| |
| namespace android { |
| namespace companion { |
| namespace virtualcamera { |
| namespace { |
| |
| using ::aidl::android::companion::virtualcamera::Format; |
| using ::aidl::android::hardware::camera::common::Status; |
| using ::aidl::android::hardware::camera::device::CameraBlob; |
| using ::aidl::android::hardware::camera::device::CameraBlobId; |
| using ::aidl::android::hardware::camera::device::Stream; |
| |
| using ScopedAImageReader = |
| std::unique_ptr<AImageReader, |
| CustomDeleter<AImageReader, AImageReader_delete>>; |
| using ScopedAImage = |
| std::unique_ptr<AImage, CustomDeleter<AImage, AImage_delete>>; |
| constexpr int kHardwareBufferUsageFlags = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN | |
| AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN; |
| constexpr int kImageFrameCount = 10; |
| |
| const int kSurfaceSizeWidth = kMaxJpegSize + sizeof(CameraBlob); |
| constexpr int kSurfaceSizeHeight = 1; |
| |
| void onImageReceived(void* context, AImageReader* /*reader*/) { |
| if (!context) { |
| ALOGE("%s: null context in onImageReceived() callback", __func__); |
| return; |
| } |
| |
| VirtualCameraImagePassthroughHandler* imageConsumer = |
| reinterpret_cast<VirtualCameraImagePassthroughHandler*>(context); |
| imageConsumer->onFrameAvailable(); |
| } |
| } // namespace |
| |
| std::unique_ptr<VirtualCameraImagePassthroughHandler> |
| VirtualCameraImagePassthroughHandler::create( |
| VirtualCameraSessionContext& sessionContext, Format imageFormat, |
| std::function<void(void)> frameReadyCallback) { |
| ALOGV("%s: allocating ImageReader, width=%d, height=%d, frame_count=%d", |
| __func__, kSurfaceSizeWidth, kSurfaceSizeHeight, kImageFrameCount); |
| |
| AImageReader* reader = nullptr; |
| media_status_t ret = AImageReader_newWithUsage( |
| kSurfaceSizeWidth, kSurfaceSizeHeight, |
| static_cast<AIMAGE_FORMATS>(imageFormat), kHardwareBufferUsageFlags, |
| kImageFrameCount, &reader); |
| |
| if (ret != AMEDIA_OK || !reader) { |
| ALOGE("%s: Fail to start RenderThread because cannot create an ImageReader", |
| __func__); |
| return nullptr; |
| } |
| |
| ScopedAImageReader scopedImageReader{reader}; |
| |
| ANativeWindow* window = nullptr; |
| if (AImageReader_getWindow(reader, &window) != AMEDIA_OK) { |
| ALOGE("%s: Fail to get the native window of the ImageReader", __func__); |
| return nullptr; |
| } |
| |
| sp<Surface> inputSurface = Surface::from(window); |
| |
| auto imageConsumer = std::make_unique<VirtualCameraImagePassthroughHandler>( |
| sessionContext, imageFormat, std::move(frameReadyCallback), |
| std::move(inputSurface), std::move(scopedImageReader)); |
| |
| AImageReader_ImageListener imagelistener{ |
| .context = imageConsumer.get(), |
| .onImageAvailable = onImageReceived, |
| }; |
| |
| if (AImageReader_setImageListener(reader, &imagelistener) != AMEDIA_OK) { |
| ALOGE("%s: Failed to set the image listener", __func__); |
| return nullptr; |
| } |
| |
| return imageConsumer; |
| } |
| |
| VirtualCameraImagePassthroughHandler::VirtualCameraImagePassthroughHandler( |
| VirtualCameraSessionContext& sessionContext, Format imageFormat, |
| std::function<void(void)> frameReadyCallback, sp<Surface> inputSurface, |
| std::unique_ptr<AImageReader, CustomDeleter<AImageReader, AImageReader_delete>> |
| imageReader) |
| : mSessionContext{sessionContext}, |
| mImageFormat{imageFormat}, |
| mOnFrameAvailable{std::move(frameReadyCallback)}, |
| mFrameAvailablePromiseSignalled{false}, |
| mIsFirstFrameDrawn{false}, |
| mInputSurface{std::move(inputSurface)}, |
| mImageReader{std::move(imageReader)} { |
| resetFrameAvailableSignalingMechanism(); |
| } |
| |
| VirtualCameraImagePassthroughHandler::~VirtualCameraImagePassthroughHandler() { |
| // Clear current ImageReader callback. This ensures that the internal |
| // ImageReader thread cannot access VirtualCameraImagePassthroughHandler |
| // member variables during the destruction sequence. |
| AImageReader_ImageListener imagelistener{ |
| .context = nullptr, |
| .onImageAvailable = nullptr, |
| }; |
| |
| if (AImageReader_setImageListener(mImageReader.get(), &imagelistener) != |
| AMEDIA_OK) { |
| ALOGE("%s: Failed to clear image listener", __func__); |
| } |
| |
| mImageReader.reset(); |
| |
| { |
| std::lock_guard<std::mutex> criticalSection{mFrameAvailableCallbackMutex}; |
| |
| // Signal mFrameAvailablePromise but indicate that a new frame has not |
| // arrived. It is possible that a call to waitForFrame() is currently |
| // blocked and fulfilling the promise will allow that routine to make |
| // progress. |
| if (!mFrameAvailablePromiseSignalled) { |
| mFrameAvailablePromise->set_value(false); |
| } |
| |
| mFrameAvailableFuture.reset(); |
| mFrameAvailablePromise.reset(); |
| } |
| } |
| |
| bool VirtualCameraImagePassthroughHandler::waitForInputFrame( |
| const std::chrono::nanoseconds timeoutNs) { |
| // Attempt to obtain image in non-blocking mode |
| AImage* image = nullptr; |
| int syncFd = 0; |
| media_status_t acquireImageResult = |
| AImageReader_acquireLatestImageAsync(mImageReader.get(), &image, &syncFd); |
| |
| if (acquireImageResult == AMEDIA_OK) { |
| // A sync fd of -1 indicates frame is available now |
| if (syncFd == -1) { |
| ALOGV("%s: Received new image", __func__); |
| mCurrentImage = ScopedAImage{image}; |
| mIsFirstFrameDrawn = true; |
| |
| // New frame has been acquired. Reset now spent promise/future |
| // to facilitate signaling of next frame |
| resetFrameAvailableSignalingMechanism(); |
| return true; |
| |
| } else { |
| // Ignore sync fd if it refers to valid file descriptor. This is because |
| // we rely on the promise/future signaling mechanism rather than file |
| // descriptors. The Android graphics system makes valid sync fds available |
| // only under certain conditions, specifically those involving HW |
| // rendering pathways. The Virtual Camera module must handle cases in |
| // which the CPU is the producer, therefore we ignore the file descriptor |
| // approach altogether and rely on an alternative signaling mechanism that |
| // works for both HW- and SW-based producer paradigms. |
| ALOGW("%s: Unexpectedly received valid sync fd (%d). Discarding...", |
| __func__, syncFd); |
| close(syncFd); |
| } |
| |
| } else if (acquireImageResult != AMEDIA_IMGREADER_NO_BUFFER_AVAILABLE) { |
| ALOGE("%s: failure in AImageReader_acquireLatestImageAsync(): error=%d", |
| __func__, (int)acquireImageResult); |
| return false; |
| } |
| |
| if (mFrameAvailableFuture->wait_for(timeoutNs) != std::future_status::ready) { |
| ALOGE("%s: timed out after %llu ns waiting for frame arrival", __func__, |
| timeoutNs.count()); |
| return false; |
| } |
| |
| bool frameAvailable = mFrameAvailableFuture->get(); |
| resetFrameAvailableSignalingMechanism(); |
| return frameAvailable; |
| } |
| |
| void VirtualCameraImagePassthroughHandler::interruptWait() { |
| std::lock_guard<std::mutex> criticalSection{mFrameAvailableCallbackMutex}; |
| if (!mFrameAvailablePromiseSignalled && mFrameAvailablePromise != nullptr) { |
| mFrameAvailablePromiseSignalled = true; |
| mFrameAvailablePromise->set_value(false); |
| } |
| } |
| |
| void VirtualCameraImagePassthroughHandler::updateTexture() { |
| // This function is generally called after the surface "on frame" callback has |
| // been triggered, so we consume the new frame immediately |
| waitForInputFrame(std::chrono::nanoseconds{0}); |
| } |
| |
| std::chrono::nanoseconds VirtualCameraImagePassthroughHandler::getTimestamp() { |
| int64_t timestampNs; |
| if (AImage_getTimestamp(mCurrentImage.get(), ×tampNs) != AMEDIA_OK) { |
| ALOGE("%s: failure during AImage_getTimestamp()", __func__); |
| return std::chrono::nanoseconds{0}; |
| } |
| |
| return std::chrono::nanoseconds{timestampNs}; |
| } |
| |
| bool VirtualCameraImagePassthroughHandler::isFirstFrameDrawn() { |
| return mIsFirstFrameDrawn; |
| } |
| |
| sp<Surface> VirtualCameraImagePassthroughHandler::getInputSurface() { |
| return mInputSurface; |
| } |
| |
| ndk::ScopedAStatus VirtualCameraImagePassthroughHandler::fillOutputBuffer( |
| const RequestSettings& /*requestSettings*/, |
| const CaptureRequestBuffer& requestBuffer, const Stream& halStreamConfig, |
| ::aidl::android::hardware::camera::device::CaptureResult& /*captureResult*/) { |
| const int streamId = requestBuffer.getStreamId(); |
| const int bufferId = requestBuffer.getBufferId(); |
| |
| std::shared_ptr<AHardwareBuffer> hwBuffer = |
| mSessionContext.fetchHardwareBuffer(streamId, bufferId); |
| if (hwBuffer == nullptr) { |
| ALOGE("%s: Failed to fetch hardware buffer %d for streamId %d", __func__, |
| bufferId, streamId); |
| return cameraStatus(Status::INTERNAL_ERROR); |
| } |
| |
| PlanesLockGuard planesLock(hwBuffer, AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN, |
| requestBuffer.getFence()); |
| if (planesLock.getStatus() != OK) { |
| ALOGE("%s: Failed to lock hwBuffer planes", __func__); |
| return cameraStatus(Status::INTERNAL_ERROR); |
| } |
| |
| // Avoid overwriting the CameraBlob footer, which may be placed at the very |
| // end of the BLOB buffer. |
| // |
| // See |
| // hardware/interfaces/camera/device/aidl/android/hardware/camera/device/CameraBlobId.aidl |
| int32_t outBufferSize = halStreamConfig.bufferSize - sizeof(CameraBlob); |
| uint8_t* outBuffer = reinterpret_cast<uint8_t*>((*planesLock).planes[0].data); |
| |
| int planeIndex = 0; |
| uint8_t* inputBuffer = nullptr; |
| int32_t inputBufferSize = 0; |
| if (AImage_getPlaneData(mCurrentImage.get(), planeIndex, &inputBuffer, |
| &inputBufferSize) != AMEDIA_OK) { |
| ALOGE("%s: Failure during AImage_getPlaneData()", __func__); |
| return cameraStatus(Status::INTERNAL_ERROR); |
| } |
| |
| // Determine the actual payload size by checking for an official HAL footer. |
| int32_t payloadSize = |
| findActualPayloadSize(inputBuffer, inputBufferSize, mImageFormat); |
| |
| if (outBufferSize < payloadSize) { |
| ALOGW( |
| "%s: BLOB input consumed from surface is larger (%d bytes) than output " |
| "buffer size (%d bytes)", |
| __func__, payloadSize, outBufferSize); |
| } |
| |
| size_t copySize = std::min(static_cast<int32_t>(outBufferSize), payloadSize); |
| |
| // Write BLOB payload to beginning of output buffer |
| memcpy(outBuffer, inputBuffer, copySize); |
| |
| // TODO(b/457285222): add BLOB footer to output buffer once ByteBuffer bug is |
| // resolved. Note that the footer is an optional optimization so it is |
| // acceptable to omit. |
| |
| ALOGV("%s: Successfully transferred BLOB payload, format=0x%x, size=%d", |
| __func__, mImageFormat, payloadSize); |
| |
| return ndk::ScopedAStatus::ok(); |
| } |
| |
| int32_t VirtualCameraImagePassthroughHandler::findActualPayloadSize( |
| const uint8_t* buffer, int32_t size, const Format format) const { |
| // Supported CameraBlob IDs for blob transport headers. |
| // JPEG and JPEG_APP_SEGMENTS are defined in CameraBlobId.aidl. |
| // HEIC and HEIC_CLEAR_ON_RELEASE are used for HEIC streams but are not yet |
| // included in the CameraBlobId AIDL enum. |
| // HEIC (0x00FE) is consistent with libcameraservice/api2/HeicCompositeStream.cpp. |
| // HEIC_CLEAR_ON_RELEASE (0x00FD) is used by some JNI users to indicate |
| // that the footer should be cleared upon image release. |
| static constexpr CameraBlobId kCameraBlobIdJpeg = CameraBlobId::JPEG; |
| static constexpr CameraBlobId kCameraBlobIdJpegAppSegments = |
| CameraBlobId::JPEG_APP_SEGMENTS; |
| static constexpr CameraBlobId kCameraBlobIdHeic = |
| static_cast<CameraBlobId>(0x00FE); |
| static constexpr CameraBlobId kCameraBlobIdHeicClearOnRelease = |
| static_cast<CameraBlobId>(0x00FD); |
| |
| if ((format == Format::JPEG || format == Format::HEIC) && |
| size >= static_cast<int32_t>(sizeof(CameraBlob))) { |
| const CameraBlob* blob = |
| reinterpret_cast<const CameraBlob*>(buffer + size - sizeof(CameraBlob)); |
| if (blob->blobId == kCameraBlobIdJpeg || |
| blob->blobId == kCameraBlobIdJpegAppSegments || |
| blob->blobId == kCameraBlobIdHeic || |
| blob->blobId == kCameraBlobIdHeicClearOnRelease) { |
| ALOGV("%s: Found CameraBlob footer. ID=0x%x, size=%d", __func__, |
| static_cast<int>(blob->blobId), blob->blobSizeBytes); |
| return blob->blobSizeBytes; |
| } |
| } |
| |
| ALOGE( |
| "%s: CameraBlob footer NOT found or invalid. Defaulting to full buffer " |
| "size (%d)", |
| __func__, size); |
| return size; |
| } |
| |
| void VirtualCameraImagePassthroughHandler::onFrameAvailable() { |
| { |
| std::lock_guard<std::mutex> criticalSection{mFrameAvailableCallbackMutex}; |
| |
| // Signal the future corresponding to mFrameAvailablePromise to indicate |
| // that new frame has arrived. It is possible that a call to waitForFrame() |
| // is in progress and is currently blocked waiting for this signal. |
| // Fulfilling mFrameAvailablePromise unblocks waitForFrame() and allows |
| // it to make progress. Only issue a new signal if the current instance of |
| // mFrameAvailablePromise has not already been fulfilled. |
| if (!mFrameAvailablePromiseSignalled) { |
| mFrameAvailablePromiseSignalled = true; |
| mFrameAvailablePromise->set_value(true); |
| } |
| } |
| |
| mOnFrameAvailable(); |
| } |
| |
| void VirtualCameraImagePassthroughHandler::resetFrameAvailableSignalingMechanism() { |
| std::lock_guard<std::mutex> criticalSection{mFrameAvailableCallbackMutex}; |
| |
| // If the promise has not already been signalled, set its value to false |
| // so it can be safely destroyed. |
| if (!mFrameAvailablePromiseSignalled && mFrameAvailablePromise != nullptr) { |
| mFrameAvailablePromise->set_value(false); |
| } |
| |
| mFrameAvailablePromiseSignalled = false; |
| mFrameAvailablePromise = std::make_unique<std::promise<bool>>(); |
| mFrameAvailableFuture = std::make_unique<std::future<bool>>(); |
| *mFrameAvailableFuture = mFrameAvailablePromise->get_future(); |
| } |
| |
| } // namespace virtualcamera |
| } // namespace companion |
| } // namespace android |