/* * 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 FAILURE_DEBUG_PREFIX "jpeg" #include #include #include #include extern "C" { #include } #include #include #include "debug.h" #include "exif.h" #include "jpeg.h" #include "yuv.h" namespace android { namespace hardware { namespace camera { namespace provider { namespace implementation { namespace jpeg { namespace { constexpr int kJpegMCUSize = 16; // we have to feed `jpeg_write_raw_data` in multiples of this // compressYUVImplPixelsFast handles the case where the image width is a multiple // of kJpegMCUSize. In this case no additional memcpy is required. See // compressYUVImplPixelsSlow below for the cases where the image width is not // a multiple of kJpegMCUSize. bool compressYUVImplPixelsFast(const android_ycbcr& image, jpeg_compress_struct* cinfo) { const uint8_t* y[kJpegMCUSize]; const uint8_t* cb[kJpegMCUSize / 2]; const uint8_t* cr[kJpegMCUSize / 2]; const uint8_t** planes[] = { y, cb, cr }; const int height = cinfo->image_height; const int height1 = height - 1; const int ystride = image.ystride; const int cstride = image.cstride; while (true) { const int nscl = cinfo->next_scanline; if (nscl >= height) { break; } for (int i = 0; i < kJpegMCUSize; ++i) { const int nscli = std::min(nscl + i, height1); y[i] = static_cast(image.y) + nscli * ystride; if ((i & 1) == 0) { const int offset = (nscli / 2) * cstride; cb[i / 2] = static_cast(image.cb) + offset; cr[i / 2] = static_cast(image.cr) + offset; } } if (!jpeg_write_raw_data(cinfo, const_cast(planes), kJpegMCUSize)) { return FAILURE(false); } } return true; } // Since JPEG processes everything in blocks of kJpegMCUSize, we have to make // both width and height a multiple of kJpegMCUSize. The height is handled by // repeating the last line. compressYUVImplPixelsSlow handles the case when the // image width is not a multiple of kJpegMCUSize by allocating a memory block // large enough to hold kJpegMCUSize rows of the image with width aligned up to // the next multiple of kJpegMCUSize. The original image has to be copied // chunk-by-chunk into this memory block. bool compressYUVImplPixelsSlow(const android_ycbcr& image, jpeg_compress_struct* cinfo, const size_t alignedWidth, uint8_t* const alignedMemory) { uint8_t* y[kJpegMCUSize]; uint8_t* cb[kJpegMCUSize / 2]; uint8_t* cr[kJpegMCUSize / 2]; uint8_t** planes[] = { y, cb, cr }; { uint8_t* y0 = alignedMemory; for (int i = 0; i < kJpegMCUSize; ++i, y0 += alignedWidth) { y[i] = y0; } const size_t alignedWidth2 = alignedWidth / 2; uint8_t* cb0 = y0; uint8_t* cr0 = &cb0[kJpegMCUSize / 2 * alignedWidth2]; for (int i = 0; i < kJpegMCUSize / 2; ++i, cb0 += alignedWidth2, cr0 += alignedWidth2) { cb[i] = cb0; cr[i] = cr0; } } const int width = cinfo->image_width; const int width2 = width / 2; const int height = cinfo->image_height; const int height1 = height - 1; const int ystride = image.ystride; const int cstride = image.cstride; while (true) { const int nscl = cinfo->next_scanline; if (nscl >= height) { break; } for (int i = 0; i < kJpegMCUSize; ++i) { const int nscli = std::min(nscl + i, height1); memcpy(y[i], static_cast(image.y) + nscli * ystride, width); if ((i & 1) == 0) { const int offset = (nscli / 2) * cstride; memcpy(cb[i / 2], static_cast(image.cb) + offset, width2); memcpy(cr[i / 2], static_cast(image.cr) + offset, width2); } } if (!jpeg_write_raw_data(cinfo, const_cast(planes), kJpegMCUSize)) { return FAILURE(false); } } return true; } struct JpegErrorMgr : public jpeg_error_mgr { JpegErrorMgr() { error_exit = &onJpegErrorS; } void onJpegError(j_common_ptr cinfo) { { char errorMessage[JMSG_LENGTH_MAX]; memset(errorMessage, 0, sizeof(errorMessage)); (*format_message)(cinfo, errorMessage); ALOGE("%s:%d: JPEG compression failed with '%s'", __func__, __LINE__, errorMessage); } longjmp(jumpBuffer, 1); } static void onJpegErrorS(j_common_ptr cinfo) { static_cast(cinfo->err)->onJpegError(cinfo); } jmp_buf jumpBuffer; }; bool compressYUVImpl(const android_ycbcr& image, const Rect imageSize, unsigned char* const rawExif, const unsigned rawExifSize, const int quality, jpeg_destination_mgr* sink) { if (image.chroma_step != 1) { return FAILURE(false); } std::vector alignedMemory; jpeg_compress_struct cinfo; JpegErrorMgr err; bool result; cinfo.err = jpeg_std_error(&err); jpeg_create_compress(&cinfo); cinfo.image_width = imageSize.width; cinfo.image_height = imageSize.height; cinfo.input_components = 3; cinfo.in_color_space = JCS_YCbCr; jpeg_set_defaults(&cinfo); jpeg_set_quality(&cinfo, quality, TRUE); jpeg_default_colorspace(&cinfo); cinfo.raw_data_in = TRUE; cinfo.dct_method = JDCT_IFAST; cinfo.comp_info[0].h_samp_factor = 2; cinfo.comp_info[0].v_samp_factor = 2; cinfo.comp_info[1].h_samp_factor = 1; cinfo.comp_info[1].v_samp_factor = 1; cinfo.comp_info[2].h_samp_factor = 1; cinfo.comp_info[2].v_samp_factor = 1; cinfo.dest = sink; if (setjmp(err.jumpBuffer)) { jpeg_destroy_compress(&cinfo); return FAILURE(false); } jpeg_start_compress(&cinfo, TRUE); if (rawExif) { jpeg_write_marker(&cinfo, JPEG_APP0 + 1, rawExif, rawExifSize); } if (imageSize.width % kJpegMCUSize) { const size_t alignedWidth = ((imageSize.width + kJpegMCUSize) / kJpegMCUSize) * kJpegMCUSize; alignedMemory.resize(alignedWidth * kJpegMCUSize * 3 / 2); result = compressYUVImplPixelsSlow(image, &cinfo, alignedWidth, alignedMemory.data()); } else { result = compressYUVImplPixelsFast(image, &cinfo); } jpeg_finish_compress(&cinfo); jpeg_destroy_compress(&cinfo); return result; } android_ycbcr resizeYUV(const android_ycbcr& srcYCbCr, const Rect srcSize, const Rect dstSize, std::vector* pDstData) { if (srcYCbCr.chroma_step != 1) { return FAILURE(android_ycbcr()); } const size_t dstWidth = dstSize.width; const size_t dstHeight = dstSize.height; if ((dstWidth & 1) || (dstHeight & 1)) { return FAILURE(android_ycbcr()); } std::vector dstData(yuv::NV21size(dstWidth, dstHeight)); const android_ycbcr dstYCbCr = yuv::NV21init(dstWidth, dstHeight, dstData.data()); const int result = libyuv::I420Scale( static_cast(srcYCbCr.y), srcYCbCr.ystride, static_cast(srcYCbCr.cb), srcYCbCr.cstride, static_cast(srcYCbCr.cr), srcYCbCr.cstride, srcSize.width, srcSize.height, static_cast(dstYCbCr.y), dstYCbCr.ystride, static_cast(dstYCbCr.cb), dstYCbCr.cstride, static_cast(dstYCbCr.cr), dstYCbCr.cstride, dstWidth, dstHeight, libyuv::kFilterBilinear); if (result) { return FAILURE_V(android_ycbcr(), "libyuv::I420Scale failed with %d", result); } else { *pDstData = std::move(dstData); return dstYCbCr; } } struct StaticBufferSink : public jpeg_destination_mgr { StaticBufferSink(void* dst, const size_t dstCapacity) { next_output_byte = static_cast(dst); free_in_buffer = dstCapacity; init_destination = &initDestinationS; empty_output_buffer = &emptyOutputBufferS; term_destination = &termDestinationS; } static void initDestinationS(j_compress_ptr) {} static boolean emptyOutputBufferS(j_compress_ptr) { return 0; } static void termDestinationS(j_compress_ptr) {} }; constexpr int kDefaultQuality = 85; int sanitizeJpegQuality(const int quality) { if (quality <= 0) { return kDefaultQuality; } else if (quality > 100) { return 100; } else { return quality; } } } // namespace size_t compressYUV(const android_ycbcr& image, const Rect imageSize, const CameraMetadata& metadata, void* const jpegData, const size_t jpegDataCapacity) { std::vector nv21data; const android_ycbcr imageNV21 = yuv::toNV21Shallow(imageSize.width, imageSize.height, image, &nv21data); auto exifData = exif::createExifData(metadata, imageSize); if (!exifData) { return FAILURE(0); } const camera_metadata_t* const rawMetadata = reinterpret_cast(metadata.metadata.data()); camera_metadata_ro_entry_t metadataEntry; do { Rect thumbnailSize = {0, 0}; int thumbnailQuality = 0; if (find_camera_metadata_ro_entry(rawMetadata, ANDROID_JPEG_THUMBNAIL_SIZE, &metadataEntry)) { break; } else { thumbnailSize.width = metadataEntry.data.i32[0]; thumbnailSize.height = metadataEntry.data.i32[1]; if ((thumbnailSize.width <= 0) || (thumbnailSize.height <= 0)) { break; } } if (find_camera_metadata_ro_entry(rawMetadata, ANDROID_JPEG_THUMBNAIL_QUALITY, &metadataEntry)) { thumbnailQuality = kDefaultQuality; } else { thumbnailQuality = sanitizeJpegQuality(metadataEntry.data.i32[0]); } std::vector thumbnailData; const android_ycbcr thumbmnail = resizeYUV(imageNV21, imageSize, thumbnailSize, &thumbnailData); if (!thumbmnail.y) { return FAILURE(0); } StaticBufferSink sink(jpegData, jpegDataCapacity); if (!compressYUVImpl(thumbmnail, thumbnailSize, nullptr, 0, thumbnailQuality, &sink)) { return FAILURE(0); } const size_t thumbnailJpegSize = jpegDataCapacity - sink.free_in_buffer; void* exifThumbnailJpegDataPtr = exif::exifDataAllocThumbnail( exifData.get(), thumbnailJpegSize); if (!exifThumbnailJpegDataPtr) { return FAILURE(0); } memcpy(exifThumbnailJpegDataPtr, jpegData, thumbnailJpegSize); } while (false); int quality; if (find_camera_metadata_ro_entry(rawMetadata, ANDROID_JPEG_QUALITY, &metadataEntry)) { quality = kDefaultQuality; } else { quality = sanitizeJpegQuality(metadataEntry.data.i32[0]); } unsigned char* rawExif = nullptr; unsigned rawExifSize = 0; exif_data_save_data(const_cast(exifData.get()), &rawExif, &rawExifSize); if (!rawExif) { return FAILURE(0); } StaticBufferSink sink(jpegData, jpegDataCapacity); const bool success = compressYUVImpl(imageNV21, imageSize, rawExif, rawExifSize, quality, &sink); free(rawExif); return success ? (jpegDataCapacity - sink.free_in_buffer) : 0; } } // namespace jpeg } // namespace implementation } // namespace provider } // namespace camera } // namespace hardware } // namespace android