Move virtual camera service to frameworks/av/services
Bug: 311647154
Bug: 301023410
Test: atest virtual_camera_tests
Test: build & flash & adb shell cmd virtual_camera help
Change-Id: I6d43a2b70f454c9c01ec2abcae9f138cd78c6a85
diff --git a/services/camera/virtualcamera/util/JpegUtil.cc b/services/camera/virtualcamera/util/JpegUtil.cc
new file mode 100644
index 0000000..6f10376
--- /dev/null
+++ b/services/camera/virtualcamera/util/JpegUtil.cc
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2023 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 "JpegUtil"
+#include "JpegUtil.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+
+#include "android/hardware_buffer.h"
+#include "jpeglib.h"
+#include "log/log.h"
+#include "ui/GraphicBuffer.h"
+#include "ui/GraphicBufferMapper.h"
+#include "utils/Errors.h"
+
+namespace android {
+namespace companion {
+namespace virtualcamera {
+namespace {
+
+constexpr int kJpegQuality = 80;
+
+class LibJpegContext {
+ public:
+ LibJpegContext(int width, int height, const android_ycbcr& ycbcr,
+ const size_t outBufferSize, void* outBuffer)
+ : mYCbCr(ycbcr),
+ mWidth(width),
+ mHeight(height),
+ mDstBufferSize(outBufferSize),
+ mDstBuffer(outBuffer) {
+ // Initialize error handling for libjpeg.
+ // We call jpeg_std_error to initialize standard error
+ // handling and then override:
+ // * output_message not to print to stderr, but use ALOG instead.
+ // * error_exit not to terminate the process, but failure flag instead.
+ mCompressStruct.err = jpeg_std_error(&mErrorMgr);
+ mCompressStruct.err->output_message = onOutputError;
+ mCompressStruct.err->error_exit = onErrorExit;
+ jpeg_create_compress(&mCompressStruct);
+
+ // Configure input image parameters.
+ mCompressStruct.image_width = width;
+ mCompressStruct.image_height = height;
+ mCompressStruct.input_components = 3;
+ mCompressStruct.in_color_space = JCS_YCbCr;
+ // We pass pointer to this instance as a client data so we can
+ // access this object from the static callbacks invoked by
+ // libjpeg.
+ mCompressStruct.client_data = this;
+
+ // Configure destination manager for libjpeg.
+ mCompressStruct.dest = &mDestinationMgr;
+ mDestinationMgr.init_destination = onInitDestination;
+ mDestinationMgr.empty_output_buffer = onEmptyOutputBuffer;
+ mDestinationMgr.term_destination = onTermDestination;
+ mDestinationMgr.next_output_byte = reinterpret_cast<JOCTET*>(mDstBuffer);
+ mDestinationMgr.free_in_buffer = mDstBufferSize;
+
+ // Configure everything else based on input configuration above.
+ jpeg_set_defaults(&mCompressStruct);
+
+ // Set quality and colorspace.
+ jpeg_set_quality(&mCompressStruct, kJpegQuality, 1);
+ jpeg_set_colorspace(&mCompressStruct, JCS_YCbCr);
+
+ // Configure RAW input mode - this let's libjpeg know we're providing raw,
+ // subsampled YCbCr data.
+ mCompressStruct.raw_data_in = 1;
+ mCompressStruct.dct_method = JDCT_IFAST;
+
+ // Configure sampling factors - this states that every 2 Y
+ // samples share 1 Cb & 1 Cr component vertically & horizontally (YUV420).
+ mCompressStruct.comp_info[0].h_samp_factor = 2;
+ mCompressStruct.comp_info[0].v_samp_factor = 2;
+ mCompressStruct.comp_info[1].h_samp_factor = 1;
+ mCompressStruct.comp_info[1].v_samp_factor = 1;
+ mCompressStruct.comp_info[2].h_samp_factor = 1;
+ mCompressStruct.comp_info[2].v_samp_factor = 1;
+ }
+
+ bool compress() {
+ // Prepare arrays of pointers to scanlines of each plane.
+ std::vector<JSAMPROW> yLines(mHeight);
+ std::vector<JSAMPROW> cbLines(mHeight / 2);
+ std::vector<JSAMPROW> crLines(mHeight / 2);
+
+ uint8_t* y = static_cast<uint8_t*>(mYCbCr.y);
+ uint8_t* cb = static_cast<uint8_t*>(mYCbCr.cb);
+ uint8_t* cr = static_cast<uint8_t*>(mYCbCr.cr);
+
+ // Since UV samples might be interleaved (semiplanar) we need to copy
+ // them to separate planes, since libjpeg doesn't directly
+ // support processing semiplanar YUV.
+ const int c_samples = (mWidth / 2) * (mHeight / 2);
+ std::vector<uint8_t> cb_plane(c_samples);
+ std::vector<uint8_t> cr_plane(c_samples);
+
+ // TODO(b/301023410) - Use libyuv or ARM SIMD for "unzipping" the data.
+ for (int i = 0; i < c_samples; ++i) {
+ cb_plane[i] = *cb;
+ cr_plane[i] = *cr;
+ cb += mYCbCr.chroma_step;
+ cr += mYCbCr.chroma_step;
+ }
+
+ // Collect pointers to individual scanline of each plane.
+ for (int i = 0; i < mHeight; ++i) {
+ yLines[i] = y + i * mYCbCr.ystride;
+ }
+ for (int i = 0; i < (mHeight / 2); ++i) {
+ cbLines[i] = cb_plane.data() + i * (mWidth / 2);
+ crLines[i] = cr_plane.data() + i * (mWidth / 2);
+ }
+
+ // Perform actual compression.
+ jpeg_start_compress(&mCompressStruct, TRUE);
+
+ while (mCompressStruct.next_scanline < mCompressStruct.image_height) {
+ const uint32_t batchSize = DCTSIZE * 2;
+ const uint32_t nl = mCompressStruct.next_scanline;
+ JSAMPARRAY planes[3]{&yLines[nl], &cbLines[nl / 2], &crLines[nl / 2]};
+
+ uint32_t done = jpeg_write_raw_data(&mCompressStruct, planes, batchSize);
+
+ if (done != batchSize) {
+ ALOGE("%s: compressed %u lines, expected %u (total %u/%u)",
+ __FUNCTION__, done, batchSize, mCompressStruct.next_scanline,
+ mCompressStruct.image_height);
+ return false;
+ }
+ }
+ jpeg_finish_compress(&mCompressStruct);
+ return mSuccess;
+ }
+
+ private:
+ void setSuccess(const boolean success) {
+ mSuccess = success;
+ }
+
+ void initDestination() {
+ mDestinationMgr.next_output_byte = reinterpret_cast<JOCTET*>(mDstBuffer);
+ mDestinationMgr.free_in_buffer = mDstBufferSize;
+ ALOGV("%s:%d jpeg start: %p [%zu]", __FUNCTION__, __LINE__, mDstBuffer,
+ mDstBufferSize);
+ }
+
+ void termDestination() {
+ mEncodedSize = mDstBufferSize - mDestinationMgr.free_in_buffer;
+ ALOGV("%s:%d Done with jpeg: %zu", __FUNCTION__, __LINE__, mEncodedSize);
+ }
+
+ // === libjpeg callbacks below ===
+
+ static void onOutputError(j_common_ptr cinfo) {
+ char buffer[JMSG_LENGTH_MAX];
+ (*cinfo->err->format_message)(cinfo, buffer);
+ ALOGE("libjpeg error: %s", buffer);
+ };
+
+ static void onErrorExit(j_common_ptr cinfo) {
+ static_cast<LibJpegContext*>(cinfo->client_data)->setSuccess(false);
+ };
+
+ static void onInitDestination(j_compress_ptr cinfo) {
+ static_cast<LibJpegContext*>(cinfo->client_data)->initDestination();
+ }
+
+ static int onEmptyOutputBuffer(j_compress_ptr cinfo __unused) {
+ ALOGV("%s:%d Out of buffer", __FUNCTION__, __LINE__);
+ return 0;
+ }
+
+ static void onTermDestination(j_compress_ptr cinfo) {
+ static_cast<LibJpegContext*>(cinfo->client_data)->termDestination();
+ }
+
+ jpeg_compress_struct mCompressStruct;
+ jpeg_error_mgr mErrorMgr;
+ jpeg_destination_mgr mDestinationMgr;
+
+ // Layout of the input image.
+ android_ycbcr mYCbCr;
+
+ // Dimensions of the input image.
+ int mWidth;
+ int mHeight;
+
+ // Destination buffer and it's capacity.
+ size_t mDstBufferSize;
+ void* mDstBuffer;
+
+ // This will be set to size of encoded data
+ // written to the outputBuffer when encoding finishes.
+ size_t mEncodedSize;
+ // Set to true/false based on whether the encoding
+ // was successful.
+ boolean mSuccess = true;
+};
+
+} // namespace
+
+// Returns true if the EGL is in an error state and logs the error.
+bool compressJpeg(int width, int height, const android_ycbcr& ycbcr,
+ size_t outBufferSize, void* outBuffer) {
+ return LibJpegContext(width, height, ycbcr, outBufferSize, outBuffer)
+ .compress();
+}
+
+} // namespace virtualcamera
+} // namespace companion
+} // namespace android