diff options
Diffstat (limited to 'libhwjpeg/ExynosJpegEncoderForCamera.cpp')
-rw-r--r-- | libhwjpeg/ExynosJpegEncoderForCamera.cpp | 888 |
1 files changed, 888 insertions, 0 deletions
diff --git a/libhwjpeg/ExynosJpegEncoderForCamera.cpp b/libhwjpeg/ExynosJpegEncoderForCamera.cpp new file mode 100644 index 0000000..45695ae --- /dev/null +++ b/libhwjpeg/ExynosJpegEncoderForCamera.cpp @@ -0,0 +1,888 @@ +/* + * Copyright Samsung Electronics Co.,LTD. + * Copyright (C) 2015 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. + */ + +#include <sys/mman.h> +#include <sys/types.h> + +#include <linux/videodev2.h> + +#include <hardware/exynos/ion.h> +#include <system/graphics.h> + +#include <ExynosJpegEncoderForCamera.h> + +#include "hwjpeg-internal.h" +#include "AppMarkerWriter.h" +#include "LibScalerForJpeg.h" +#include "IFDWriter.h" + +// Data length written by H/W without the scan data. +#define NECESSARY_JPEG_LENGTH (0x24B + 2 * JPEG_MARKER_SIZE) + +static size_t GetImageLength(unsigned int width, unsigned int height, int v4l2Format) +{ + size_t size = width * height; + + switch(v4l2Format) { + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_YVYU: + case V4L2_PIX_FMT_UYVY: + case V4L2_PIX_FMT_VYUY: + case V4L2_PIX_FMT_NV16: + case V4L2_PIX_FMT_NV61: + case V4L2_PIX_FMT_YUV422P: + return size * 2; + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV21: + case V4L2_PIX_FMT_NV12M: + case V4L2_PIX_FMT_NV21M: + case V4L2_PIX_FMT_YUV420: + return size + (size / 4) * 2; + } + + return 0; +} + +static int GetThumbnailFormat(int v4l2Format) +{ + if (v4l2Format == V4L2_PIX_FMT_NV12M) + return V4L2_PIX_FMT_NV12; + else if (v4l2Format == V4L2_PIX_FMT_NV21M) + return V4L2_PIX_FMT_NV21; + else + return v4l2Format; +} + +ExynosJpegEncoderForCamera::ExynosJpegEncoderForCamera(bool bBTBComp) + : m_phwjpeg4thumb(NULL), m_fdIONClient(-1), m_fdIONThumbImgBuffer(-1), m_pIONThumbImgBuffer(NULL), + m_szIONThumbImgBuffer(0), m_pIONThumbJpegBuffer(NULL), m_fdIONThumbJpegBuffer(-1), m_szIONThumbJpegBuffer(0), + m_nThumbWidth(0), m_nThumbHeight(0), m_nThumbQuality(0), + m_pStreamBase(NULL), m_fThumbBufferType(0) +{ + m_pAppWriter = new CAppMarkerWriter(); + if (!m_pAppWriter) { + ALOGE("Failed to allocated an instance of CAppMarkerWriter"); + return; + } + + m_phwjpeg4thumb = new CHWJpegV4L2Compressor(); + if (!m_phwjpeg4thumb) { + ALOGE("Failed to create thumbnail compressor!"); + return; + } + + if (!m_phwjpeg4thumb->SetChromaSampFactor(2, 2)) { + ALOGE("Failed to configure chroma subsampling factor to YUV420 for thumbnail compression"); + } + + m_fdIONClient = exynos_ion_open(); + if (m_fdIONClient < 0) { + ALOGERR("Failed to create ION client for thumbnail conversion"); + } + + if (!bBTBComp) + SetState(STATE_NO_BTBCOMP); + + // STATE_THUMBSIZE_CHANGED is to know if thumbnail image size need to be + // configured to HWJPEG. If HWJPEG does not support for back-to-back + // compression, it should not be configured. + if (IsBTBCompressionSupported()) + SetState(STATE_THUMBSIZE_CHANGED); + + ALOGD("ExynosJpegEncoderForCamera Created: %p, ION %d", this, m_fdIONClient); +} + +ExynosJpegEncoderForCamera::~ExynosJpegEncoderForCamera() +{ + delete m_pAppWriter; + delete m_phwjpeg4thumb; + + if (m_pIONThumbImgBuffer != NULL) + munmap(m_pIONThumbImgBuffer, m_szIONThumbImgBuffer); + + if (m_fdIONThumbImgBuffer >= 0) + close(m_fdIONThumbImgBuffer); + + if (m_pIONThumbJpegBuffer) + munmap(m_pIONThumbJpegBuffer, m_szIONThumbJpegBuffer); + + if (m_fdIONThumbJpegBuffer >= 0) + close(m_fdIONThumbJpegBuffer); + + if (m_fdIONClient >= 0) + exynos_ion_close(m_fdIONClient); + + ALOGD("ExynosJpegEncoderForCamera Destroyed: %p, ION %d, ThumIMG %d", + this, m_fdIONClient, m_fdIONThumbImgBuffer); +} + +int ExynosJpegEncoderForCamera::setThumbnailSize(int w, int h) +{ + if ((m_nThumbWidth == w) && (m_nThumbHeight == h)) + return 0; + + // w == 0 and h == 0 resets thumbnail configuration + if (((w | h) != 0) && ((w < 16) || (h < 16))) { + ALOGE("Too small thumbnail image size %dx%d", w, h); + return -1; + } + + m_nThumbWidth = w; + m_nThumbHeight = h; + + if (IsBTBCompressionSupported()) + SetState(STATE_THUMBSIZE_CHANGED); + + return 0; +} + +int ExynosJpegEncoderForCamera::setThumbnailQuality(int quality) +{ + if (m_nThumbQuality == quality) + return 0; + + if ((quality > 100) || (quality < 1)) { + ALOGE("Invalid quality factor %d for thumbnail image", quality); + return -1; + } + + m_nThumbQuality = quality; + + return GetCompressor().SetQuality(0, m_nThumbQuality) ? 0 : -1; +} + +bool ExynosJpegEncoderForCamera::EnsureFormatIsApplied() { + if (TestStateEither(STATE_PIXFMT_CHANGED | STATE_SIZE_CHANGED | STATE_THUMBSIZE_CHANGED)) { + int thumb_width = m_nThumbWidth; + int thumb_height = m_nThumbHeight; + int width = 0; + int height = 0; + + if (IsThumbGenerationNeeded() || !IsBTBCompressionSupported()) { + thumb_width = 0; + thumb_height = 0; + } + + getSize(&width, &height); + if (!GetCompressor().SetImageFormat( + getColorFormat(), width, height, thumb_width, thumb_height)) + return false; + + ClearState(STATE_PIXFMT_CHANGED | STATE_SIZE_CHANGED | STATE_THUMBSIZE_CHANGED); + } + + return true; +} + +size_t ExynosJpegEncoderForCamera::RemoveTrailingDummies(char *base, size_t len) +{ + ALOG_ASSERT(len > 4); + ALOG_ASSERT((base[0] == 0xFF) && (base[1] == 0xD8)); // SOI marker + + size_t riter = len - 2; + + while (riter > 0) { + if ((base[riter] == 0xFF) && (base[riter + 1] == 0xD9)) { // EOI marker + ALOGI_IF(riter < (len - 2), "Found %zu dummies after EOI", len - riter - 2); + return riter + 2; + } + riter--; + } + + ALOGE("EOI is not found!"); + ALOG_ASSERT(true); + + return 0; +} + +void *ExynosJpegEncoderForCamera::tCompressThumbnail(void *p) +{ + ExynosJpegEncoderForCamera *encoder = reinterpret_cast<ExynosJpegEncoderForCamera *>(p); + + size_t thumblen = encoder->CompressThumbnail(); + return reinterpret_cast<void *>(thumblen); +} + +bool ExynosJpegEncoderForCamera::ProcessExif(char *base, size_t limit, + exif_attribute_t *exifInfo, + debug_attribute_t *debuginfo) +{ + // PREREQUISITES: The main and the thumbnail image size should be configured before. + + // Sanity chck + uint32_t width = 0; + uint32_t height = 0; + + getSize(reinterpret_cast<int *>(&width), reinterpret_cast<int *>(&height)); + + if (exifInfo) { + if ((exifInfo->width != width) || (exifInfo->height != height)) { + ALOGE("Inconsistant image dimension: Exif %dx%d, Thumb %dx%d", + exifInfo->width, exifInfo->height, width, height); + return false; + } + + if (exifInfo->enableThumb) { + if ((exifInfo->widthThumb != static_cast<uint32_t>(m_nThumbWidth)) || + (exifInfo->heightThumb != static_cast<uint32_t>(m_nThumbHeight))) { + ALOGE("Inconsistant thumbnail information: Exif %dx%d, Thumb %dx%d", + exifInfo->widthThumb, exifInfo->heightThumb, m_nThumbWidth, m_nThumbHeight); + return false; + } + } + } + + // Giving appwriter the address beyond SOS marker + // because it is handled by this class + size_t align = 16; + if (!!(GetDeviceCapabilities() & V4L2_CAP_EXYNOS_JPEG_NO_STREAMBASE_ALIGN)) + align = 1; + + m_pAppWriter->PrepareAppWriter(base + JPEG_MARKER_SIZE, exifInfo, debuginfo); + + if (limit <= (m_pAppWriter->CalculateAPPSize(0) + NECESSARY_JPEG_LENGTH)) { + ALOGE("Too small JPEG stream buffer size, %zu bytes", limit); + return false; + } + + bool reserve_thumbspace = true; + + // If the length of the given stream buffer is too small, and thumbnail + // compression is also required, the compressed stream data of the main + // image is appeneded after the end of the fields if IFD1. The place is + // actually reserved for the embedded thumbnail but the main JPEG stream + // is written in this case because it is unknown how the compressed data + // of the thumbnail image will be. + // After the main and the thumbnail image compressions are completed, + // the compressed data of the main image is shifted by the length of the + // compressed data of the thumbnail image. Then the compressed data of + // the thumbnail image is copied to the place for it. + if (!exifInfo || !exifInfo->enableThumb || (limit < (JPEG_MAX_SEGMENT_SIZE * 10))) + reserve_thumbspace = false; + + m_pAppWriter->Write(reserve_thumbspace, JPEG_MARKER_SIZE, align, + TestState(STATE_HWFC_ENABLED)); + + ALOGD("Image compression starts from offset %zu (APPx size %zu, HWFC? %d, NBTB? %d)", + PTR_DIFF(base, m_pAppWriter->GetMainStreamBase()), m_pAppWriter->CalculateAPPSize(), + TestState(STATE_HWFC_ENABLED),TestState(STATE_NO_BTBCOMP)); + + return true; +} + +bool ExynosJpegEncoderForCamera::PrepareCompression(bool thumbnail) +{ + if (!thumbnail) + return true; + + if (IsThumbGenerationNeeded()) { + if (pthread_create(&m_threadWorker, NULL, + tCompressThumbnail, reinterpret_cast<void *>(this)) != 0) { + ALOGERR("Failed to create thumbnail generation thread"); + return false; + } + } else { + // allocate temporary thumbnail stream buffer + // to prevent overflow of the compressed stream + if (!AllocThumbJpegBuffer()) { + return false; + } + } + + if (!TestState(STATE_NO_BTBCOMP) && IsBTBCompressionSupported()) { + if (checkOutBufType() == JPEG_BUF_TYPE_USER_PTR) { + if (!GetCompressor().SetJpegBuffer2(m_pIONThumbJpegBuffer, m_szIONThumbJpegBuffer)) { + ALOGE("Failed to configure thumbnail buffer @ %p(size %zu)", + m_pIONThumbJpegBuffer, m_szIONThumbJpegBuffer); + return false; + } + } else { + if (!GetCompressor().SetJpegBuffer2(m_fdIONThumbJpegBuffer, m_szIONThumbJpegBuffer)) { + ALOGE("Failed to configure thumbnail buffer @ %d(size %zu)", + m_fdIONThumbJpegBuffer, m_szIONThumbJpegBuffer); + return false; + } + } + } + + return true; +} + +int ExynosJpegEncoderForCamera::encode(int *size, exif_attribute_t *exifInfo, + char** pcJpegBuffer, debug_attribute_t *debugInfo) +{ + return encode(size, exifInfo, -1, pcJpegBuffer, debugInfo); +} + +int ExynosJpegEncoderForCamera::encode(int *size, exif_attribute_t *exifInfo, + int fdJpegBuffer, char** pcJpegBuffer, + debug_attribute_t *debugInfo) +{ + if (!(*pcJpegBuffer)) { + ALOGE("Target stream buffer is not specified"); + return -1; + } + + if (*size <= 0) { + ALOGE("Too small stram buffer length %d bytes", *size); + return -1; + } + + m_pStreamBase = *pcJpegBuffer; + m_nStreamSize = *size; // contains max buffer length until the compression finishes + + char *jpeg_base = m_pStreamBase; + + ALOGI_IF(!exifInfo, "Exif is not specified. Skipping writing APP1 marker"); + ALOGI_IF(!debugInfo, + "Debugging information is not specified. Skipping writing APP4 marker"); + ALOGD("Given stream buffer size: %d bytes", *size); + + CStopWatch stopwatch(true); + + if (!ProcessExif(jpeg_base, m_nStreamSize, exifInfo, debugInfo)) + return -1; + + int offset = PTR_DIFF(m_pStreamBase, m_pAppWriter->GetMainStreamBase()); + int buffsize = static_cast<int>(m_nStreamSize - offset); + if ((fdJpegBuffer < 0) || !(GetDeviceCapabilities() & V4L2_CAP_EXYNOS_JPEG_DMABUF_OFFSET)) { // JPEG_BUF_TYPE_USER_PTR + if (setOutBuf(m_pAppWriter->GetMainStreamBase(), buffsize) < 0) { + ALOGE("Failed to configure stream buffer : fd %d, addr %p, streamSize %d", + fdJpegBuffer, m_pAppWriter->GetMainStreamBase(), buffsize); + return -1; + } + } else { // JPEG_BUF_TYPE_DMA_BUF + if (setOutBuf(fdJpegBuffer, buffsize, offset) < 0) { + ALOGE("Failed to configure stream buffer : fd %d, addr %p, streamSize %d", + fdJpegBuffer, m_pAppWriter->GetMainStreamBase(), buffsize); + return -1; + } + } + + bool block_mode = !TestState(STATE_HWFC_ENABLED); + bool thumbenc = m_pAppWriter->GetThumbStreamBase() != NULL; + size_t thumblen = 0; + + // THUMB REQ? | THUMB IMG GIVEN? | B2B COMP? | HWFC(NONBLOCKING)? + // CASE1: O | X | - | X + // CASE2: O | X | - | O + // CASE3: O | O | X | X + // CASE4: O | O | O | X + // CASE5: O | O | O | O + // CASE6: X | - | - | - + // CASE7: O | O | X | O + // + // CASE1 = thumbenc && IsThumbGenerationNeeded() && block_mode + // CASE2 = thumbenc && IsThumbGenerationNeeded() && !block_mode + // CASE3 = thumbenc && !IsThumbGenerationNeeded() && !IsBTBCompressionSupported() && !block_mode + // CASE4 = thumbenc && !IsThumbGenerationNeeded() && !STATE_NO_BTBCOMP && IsBTBCompressionSupported() && !block_mode + // CASE5 = thumbenc && !IsThumbGenerationNeeded() && !STATE_NO_BTBCOMP && IsBTBCompressionSupported() && block_mode + // CASE6 = !thumbenc + // CASE7 = thumbenc && !IsThumbGenerationNeeded() && STATE_NO_BTBCOMP && block_mode + + if (!thumbenc) { + // Confirm that no thumbnail information is transferred to HWJPEG + setThumbnailSize(0, 0); + } else if (!IsThumbGenerationNeeded() && IsBTBCompressionSupported() && + (m_fThumbBufferType != checkInBufType())) { + ALOGE("Buffer types of thumbnail(%d) and main(%d) images should be the same", + m_fThumbBufferType, checkInBufType()); + return -1; + } else if (!IsThumbGenerationNeeded() && (m_fThumbBufferType == 0)) { + // Thumbnail buffer configuration failed but the client forces to compress with thumbnail + ThumbGenerationNeeded(); + SetState(STATE_THUMBSIZE_CHANGED); + } + + if (!EnsureFormatIsApplied()) { + ALOGE("Failed to confirm format"); + return -1; + } + + if (!PrepareCompression(thumbenc)) { + ALOGE("Failed to prepare compression"); + return -1; + } + + ssize_t mainlen = GetCompressor().Compress(&thumblen, block_mode); + if (mainlen < 0) { + ALOGE("Error occured while JPEG compression: %zd", mainlen); + return -1; + } + + if (mainlen == 0) { /* non-blocking compression */ + ALOGD("Waiting for MCSC run"); + return 0; + } + + *size = static_cast<int>(FinishCompression(mainlen, thumblen)); + if (*size < 0) + return -1; + + ALOGD("....compression delay(usec.): HW %u, Total %lu)", + GetHWDelay(), stopwatch.GetElapsed()); + + return 0; +} + +ssize_t ExynosJpegEncoderForCamera::FinishCompression(size_t mainlen, size_t thumblen) +{ + bool btb = false; + size_t max_streamsize = m_nStreamSize; + char *mainbase = m_pAppWriter->GetMainStreamBase(); + char *thumbbase = m_pAppWriter->GetThumbStreamBase(); + + m_nStreamSize = 0; + + mainlen = RemoveTrailingDummies(mainbase, mainlen); + + // Clearing SOI of the main image written by H/W + m_pAppWriter->GetMainStreamBase()[0] = 0; + m_pAppWriter->GetMainStreamBase()[1] = 0; + + if (thumbbase) { + if (IsThumbGenerationNeeded()) { + void *len; + int ret = pthread_join(m_threadWorker, &len); + if (ret != 0) { + ALOGERR("Failed to wait thumbnail thread(%d)", ret); + return -1; + } + + if (len == NULL) + ALOGE("Error occurred during thumbnail creation: no thumbnail is embedded"); + + thumblen = reinterpret_cast<size_t>(len); + } else if (TestState(STATE_NO_BTBCOMP) || !IsBTBCompressionSupported()) { + thumblen = CompressThumbnailOnly(m_pAppWriter->GetMaxThumbnailSize(), m_nThumbQuality, getColorFormat(), checkInBufType()); + } else { + btb = true; + } + + size_t max_thumb = min(m_pAppWriter->GetMaxThumbnailSize(), max_streamsize - m_pAppWriter->CalculateAPPSize(0) - mainlen); + + if (thumblen > max_thumb) { + ALOGI("Too large thumbnail (%dx%d) stream size %zu (max: %zu, quality factor %d)", + m_nThumbWidth, m_nThumbHeight, thumblen, max_thumb, m_nThumbQuality); + ALOGI("Retrying thumbnail compression with quality factor 50"); + thumblen = CompressThumbnailOnly(max_thumb, 50, getColorFormat(), checkInBufType()); + if (thumblen == 0) + return -1; + } + + if (!m_pAppWriter->IsThumbSpaceReserved()) { + if (PTR_TO_ULONG(m_pStreamBase + max_streamsize) < + PTR_TO_ULONG(mainbase + mainlen + thumblen - JPEG_MARKER_SIZE)) { + ALOGE("Too small JPEG buffer length %zu (APP %zu, Main %zu, Thumb %zu)", + max_streamsize, m_pAppWriter->CalculateAPPSize(thumblen), mainlen, thumblen); + return -1; + } + + // the SOI of the stream of the main image is stored after the APP4 or APP11 segment if they exist. + memmove(m_pAppWriter->GetApp1End() + thumblen, m_pAppWriter->GetApp1End(), + mainlen + PTR_DIFF(m_pAppWriter->GetApp1End(), m_pAppWriter->GetMainStreamBase())); + m_pAppWriter->UpdateApp1Size(thumblen); + + // m_nAppLength has the value of appwriter.GetExactAPPSize() + // Therefore m_nStreamSize should be initialized with thumbnail stream length; + } + + if (thumblen > 0) { + memcpy(m_pAppWriter->GetThumbStreamBase(), m_pIONThumbJpegBuffer, thumblen); + m_pAppWriter->Finalize(thumblen); + } + + if (m_pAppWriter->IsThumbSpaceReserved()) { + // clear the possible stale data in the dummy area after the thumbnail stream + memset(m_pAppWriter->GetThumbStreamBase() + thumblen, 0, + m_pAppWriter->GetMaxThumbnailSize() - thumblen + m_pAppWriter->GetAPP1ResrevedSize()); + } + } else { + thumblen = 0; + } + + m_nStreamSize += m_pAppWriter->CalculateAPPSize(thumblen) + mainlen; + + /* + * m_nAppLength: The size of APP1 segment and APP4 segment including markers + * getJpegSize(): size of the compressed stream of the main image + * Note that 2 byte(size of SOI marker) is included in APP1 segment size. + * Thus the size of SOI marker in front of the stream is not added. + */ + ALOGD("Completed image compression (%zd(thumb %zu) bytes, HWFC? %d, BTB? %d)", + mainlen, thumblen, TestState(STATE_HWFC_ENABLED), btb); + + m_pStreamBase[0] = 0xFF; + m_pStreamBase[1] = 0xD8; + + return m_nStreamSize; +} + +/* The logic in WaitForHWFC() is the same with encode() */ +ssize_t ExynosJpegEncoderForCamera::WaitForCompression() +{ + if (!TestState(STATE_HWFC_ENABLED)) + return m_nStreamSize; + + size_t thumblen = 0; + ssize_t streamlen = GetCompressor().WaitForCompression(&thumblen); + if (streamlen < 0) + return streamlen; + + return FinishCompression(streamlen, thumblen); +} + +bool ExynosJpegEncoderForCamera::GenerateThumbnailImage() +{ + int main_width, main_height; + if (getSize(&main_width, &main_height) < 0) { + ALOGE("Failed to get main image size"); + return false; + } + + int v4l2Format = getColorFormat(); + + if (!AllocThumbBuffer(v4l2Format)) + return false; + + ALOGD("Generating thumbnail image: %dx%d -> %dx%d", + main_width, main_height, m_nThumbWidth, m_nThumbHeight); + + int len_srcbufs[3] = {0, 0, 0}; + void *srcbufs[3] = {NULL, NULL, NULL}; + int memtype; + + if (checkInBufType() == JPEG_BUF_TYPE_USER_PTR) { + char *bufs[3]; + if (getInBuf(bufs, len_srcbufs, 3) < 0) { + ALOGE("Failed to retrieve the main image buffers"); + return false; + } + memtype = V4L2_MEMORY_USERPTR; + srcbufs[0] = reinterpret_cast<void *>(bufs[0]); + srcbufs[1] = reinterpret_cast<void *>(bufs[1]); + srcbufs[2] = reinterpret_cast<void *>(bufs[2]); + } else { // mainbuftype == JPEG_BUF_TYPE_DMA_BUF + int bufs[3]; + if (getInBuf(bufs, len_srcbufs, 3) < 0) { + ALOGE("Failed to retrieve the main image buffers"); + return false; + } + memtype = V4L2_MEMORY_DMABUF; + srcbufs[0] = reinterpret_cast<void *>(bufs[0]); + srcbufs[1] = reinterpret_cast<void *>(bufs[1]); + srcbufs[2] = reinterpret_cast<void *>(bufs[2]); + } + + void *dstbuf[3] = {NULL, NULL, NULL}; + dstbuf[0] = reinterpret_cast<void *>(m_fdIONThumbImgBuffer); + + if (!m_pLibScaler.SetSrcImage(main_width, main_height, v4l2Format, srcbufs, memtype)) { + ALOGE("Failed to configure the main image format to LibScalerForJpeg"); + return false; + } + if (!m_pLibScaler.SetDstImage(m_nThumbWidth, m_nThumbHeight, + GetThumbnailFormat(v4l2Format), dstbuf, V4L2_MEMORY_DMABUF)) { + ALOGE("Failed to configure the target image format to LibScalerForJpeg"); + return false; + } + + if (!m_pLibScaler.RunStream()) { + ALOGE("Failed to convert the main image to thumbnail with LibScalerForJpeg"); + return false; + } + + return true; +} + +size_t ExynosJpegEncoderForCamera::CompressThumbnail() +{ + unsigned int v4l2Format = getColorFormat(); + int buftype = checkInBufType(); + + if (IsThumbGenerationNeeded()) { + if (!GenerateThumbnailImage()) + return 0; + + // libcsc output configured by this class is always NV21. + v4l2Format = GetThumbnailFormat(getColorFormat()); + + buftype = JPEG_BUF_TYPE_DMA_BUF; + // reduced setInBuf2() + m_fdThumbnailImageBuffer[0] = m_fdIONThumbImgBuffer; + m_szThumbnailImageLen[0] = m_szIONThumbImgBuffer; + } + + return CompressThumbnailOnly(m_pAppWriter->GetMaxThumbnailSize(), m_nThumbQuality, v4l2Format, buftype); +} + +bool ExynosJpegEncoderForCamera::AllocThumbBuffer(int v4l2Format) +{ + if (m_fdIONClient < 0) { + ALOGE("ION client is not created"); + return false; + } + + size_t thumbbufsize = GetImageLength(m_nThumbWidth, m_nThumbHeight, v4l2Format); + if (thumbbufsize == 0) { + ALOGE("Unsupported V4L2 format %#X for thumbnail", v4l2Format); + return false; + } + + if (m_fdIONThumbImgBuffer >= 0) { + if (m_szIONThumbImgBuffer >= thumbbufsize) + return true; + + if (m_pIONThumbImgBuffer != NULL) + munmap(m_pIONThumbImgBuffer, m_szIONThumbImgBuffer); + + close(m_fdIONThumbImgBuffer); + + m_fdIONThumbImgBuffer = -1; + m_pIONThumbImgBuffer = NULL; + m_szIONThumbImgBuffer = 0; + } + + m_fdIONThumbImgBuffer = exynos_ion_alloc(m_fdIONClient, thumbbufsize, EXYNOS_ION_HEAP_SYSTEM_MASK, 0); + if (m_fdIONThumbImgBuffer < 0) { + ALOGERR("Failed to allocate %zu bytes for NV12 %ux%u", thumbbufsize, m_nThumbHeight, m_nThumbWidth); + m_fdIONThumbImgBuffer = -1; + return false; + } + + m_szIONThumbImgBuffer = thumbbufsize; + + return AllocThumbJpegBuffer(); +} + +bool ExynosJpegEncoderForCamera::AllocThumbJpegBuffer() +{ + if (m_fdIONClient < 0) { + ALOGE("ION client is not created"); + return false; + } + + size_t thumbbufsize = m_nThumbHeight * m_nThumbWidth * 3; + + if (m_pIONThumbJpegBuffer) { + if (m_szIONThumbJpegBuffer >= thumbbufsize) + return true; + + munmap(m_pIONThumbJpegBuffer, m_szIONThumbJpegBuffer); + close(m_fdIONThumbJpegBuffer); + + m_szIONThumbJpegBuffer = 0; + m_pIONThumbJpegBuffer = NULL; + m_fdIONThumbJpegBuffer = -1; + } + + m_fdIONThumbJpegBuffer = exynos_ion_alloc(m_fdIONClient, thumbbufsize, EXYNOS_ION_HEAP_SYSTEM_MASK, + ION_FLAG_CACHED | ION_FLAG_CACHED_NEEDS_SYNC); + if (m_fdIONThumbJpegBuffer < 0) { + ALOGERR("Failed to allocate %zu bytes for thumbnail stream buffer of %ux%u", + thumbbufsize, m_nThumbHeight, m_nThumbWidth); + return false; + } + + m_pIONThumbJpegBuffer = reinterpret_cast<char *>( + mmap(NULL, thumbbufsize, PROT_READ | PROT_WRITE, MAP_SHARED, m_fdIONThumbJpegBuffer, 0)); + if (m_pIONThumbJpegBuffer == MAP_FAILED) { + ALOGERR("Failed to map thumbnail stream buffer (%zu bytes)", thumbbufsize); + + m_pIONThumbJpegBuffer = NULL; + } else { + m_szIONThumbJpegBuffer = thumbbufsize; + } + + return m_pIONThumbJpegBuffer != NULL; +} + +size_t ExynosJpegEncoderForCamera::CompressThumbnailOnly(size_t limit, int quality, + unsigned int v4l2Format, int src_buftype) +{ + if (!m_phwjpeg4thumb->SetImageFormat(v4l2Format, m_nThumbWidth, m_nThumbHeight)) { + ALOGE("Failed to configure thumbnail source image format to %#010x, %ux%u", + v4l2Format, m_nThumbWidth, m_nThumbHeight); + return 0; + } + + unsigned int num_buffers = 1; + switch (v4l2Format) { + case V4L2_PIX_FMT_YUV420M: + [[fallthrough]]; + case V4L2_PIX_FMT_YVU420M: + num_buffers++; + [[fallthrough]]; + case V4L2_PIX_FMT_NV12M: + [[fallthrough]]; + case V4L2_PIX_FMT_NV21M: + num_buffers++; + break; + } + + if (src_buftype == JPEG_BUF_TYPE_USER_PTR) { + if (!m_phwjpeg4thumb->SetImageBuffer(m_pThumbnailImageBuffer, + m_szThumbnailImageLen, num_buffers)) { + ALOGE("Failed to configure thumbnail buffers(userptr) for thumbnail"); + return 0; + } + } else { // JPEG_BUF_TYPE_DMA_BUF + if (!m_phwjpeg4thumb->SetImageBuffer(m_fdThumbnailImageBuffer, + m_szThumbnailImageLen, num_buffers)) { + ALOGE("Failed to configure thumbnail buffers(dmabuf) for thumbnail"); + return 0; + } + } + + if (!m_phwjpeg4thumb->SetJpegBuffer(m_fdIONThumbJpegBuffer, m_szIONThumbJpegBuffer)) { + ALOGE("Failed to configure thumbnail stream buffer (fd %d, size %zu)", + m_fdIONThumbJpegBuffer, m_szIONThumbJpegBuffer); + return 0; + } + + // Since the compressed stream of the thumbnail image is to be embedded in + // APP1 segment, at the end of Exif metadata, the length of the stream should + // not exceed the maximum length of a segment, 64KB minus the length of Exif + // metadata. If the stream length is too large, repeat the compression until + // the length become proper to embed. + do { + if (!m_phwjpeg4thumb->SetQuality(quality)) { + ALOGE("Failed to configure thumbnail quality factor %u", quality); + return 0; + } + + ssize_t thumbsize = m_phwjpeg4thumb->Compress(); + if (thumbsize < 0) { + ALOGE("Failed to compress thumbnail"); + return 0; + } + + thumbsize = RemoveTrailingDummies(m_pIONThumbJpegBuffer, thumbsize); + if (static_cast<size_t>(thumbsize) > limit) { + quality = min(50, quality - 10); + ALOGI_IF(quality >= 20, + "Too large thumbnail stream size %zu. Retrying with quality factor %d...", + thumbsize, quality); + } else { + return thumbsize; + } + } while (quality >= 20); + + ALOG_ASSERT(false, "It should never reach here"); + ALOGE("Thumbnail compression finally failed"); + + return 0; +} + +int ExynosJpegEncoderForCamera::setInBuf2(int *piBuf, int *iSize) +{ + NoThumbGenerationNeeded(); + + if (!EnsureFormatIsApplied()) + return -1; + + CHWJpegCompressor &hwjpeg = GetCompressor(); + unsigned int num_buffers = 3; + if (!hwjpeg.GetImageBufferSizes(m_szThumbnailImageLen, &num_buffers)) { + ALOGE("Failed to get image buffer sizes"); + return -1; + } + + for (unsigned int i = 0; i < num_buffers; i++) { + m_szThumbnailImageLen[i] = iSize[i]; + m_fdThumbnailImageBuffer[i] = piBuf[i]; + } + + if (IsBTBCompressionSupported() && + !hwjpeg.SetImageBuffer2(m_fdThumbnailImageBuffer, m_szThumbnailImageLen, num_buffers)) { + ALOGE("Failed to configure thumbnail buffers"); + return -1; + } + + m_fThumbBufferType = JPEG_BUF_TYPE_DMA_BUF; + + return 0; +} + +int ExynosJpegEncoderForCamera::setInBuf2(char **pcBuf, int *iSize) +{ + NoThumbGenerationNeeded(); + + if (!EnsureFormatIsApplied()) + return -1; + + CHWJpegCompressor &hwjpeg = GetCompressor(); + unsigned int num_buffers = 3; + if (!hwjpeg.GetImageBufferSizes(m_szThumbnailImageLen, &num_buffers)) { + ALOGE("Failed to get image buffer sizes"); + return -1; + } + + for (unsigned int i = 0; i < num_buffers; i++) { + m_szThumbnailImageLen[i] = iSize[i]; + m_pThumbnailImageBuffer[i] = pcBuf[i]; + } + + if (IsBTBCompressionSupported() && + !hwjpeg.SetImageBuffer2(m_pThumbnailImageBuffer, m_szThumbnailImageLen, num_buffers)) { + ALOGE("Failed to configure thumbnail buffers"); + return -1; + } + + m_fThumbBufferType = JPEG_BUF_TYPE_USER_PTR; + + return 0; +} + +size_t ExynosJpegEncoderForCamera::GetThumbnailImage(char *buffer, size_t buflen) +{ + if (m_fdIONThumbImgBuffer < 0) { + ALOGE("No internal thumbnail buffer is allocated"); + return 0; + } + + size_t thumbbufsize = GetImageLength(m_nThumbWidth, m_nThumbHeight, getColorFormat()); + if (buflen < thumbbufsize) { + ALOGE("Too small buffer %zu (thumbnail image size %zu)", buflen, thumbbufsize); + return 0; + } + + ALOG_ASSERT(m_szIONThumbImgBuffer >= thumbbufsize, + "m_szIONThumbImgBuffer(%zu) is smaller than the thumbnail (%zu)", + m_szIONThumbImgBuffer, thumbbufsize); + if (m_pIONThumbImgBuffer == NULL) { + m_pIONThumbImgBuffer = reinterpret_cast<char *>(mmap( + NULL, m_szIONThumbImgBuffer, PROT_READ, MAP_SHARED, m_fdIONThumbImgBuffer, 0)); + if (m_pIONThumbImgBuffer == MAP_FAILED) { + m_pIONThumbImgBuffer = NULL; + ALOGERR("Failed to map thumbnail image buffer (%zu bytes)", m_szIONThumbImgBuffer); + return 0; + } + } + + memcpy(buffer, m_pIONThumbImgBuffer, thumbbufsize); + + ALOGD("Copied thumbnail image to %p (%zu bytes)", buffer, thumbbufsize); + + return m_szIONThumbImgBuffer; +} + +int ExynosJpegEncoderForCamera::destroy() +{ + GetCompressor().Release(); + return 0; +} |