| /* |
| * 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 "ThumbnailScaler.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); |
| |
| m_extraInfo.appInfo = m_appInfo; |
| |
| mThumbnailScaler.reset(ThumbnailScaler::createInstance()); |
| |
| 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, |
| extra_appinfo_t *extra) |
| { |
| // 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, extra); |
| |
| 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 ((!debugInfo) || (debugInfo->num_of_appmarker == 0)) { |
| extra_appinfo_t *extra = NULL; |
| return encode(size, exifInfo, fdJpegBuffer, pcJpegBuffer, extra); |
| } |
| |
| m_extraInfo.num_of_appmarker = 0; |
| memset(m_appInfo, 0, sizeof(m_appInfo)); |
| |
| ExtractDebugAttributeInfo(debugInfo, &m_extraInfo); |
| |
| return encode(size, exifInfo, fdJpegBuffer, pcJpegBuffer, &m_extraInfo); |
| } |
| |
| int ExynosJpegEncoderForCamera::encode(int *size, exif_attribute_t *exifInfo, |
| int fdJpegBuffer, char** pcJpegBuffer, |
| extra_appinfo_t *appInfo) |
| { |
| 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(!appInfo, |
| "Debugging information is not specified. Skipping writing APP4 marker"); |
| ALOGD("Given stream buffer size: %d bytes", *size); |
| |
| if (!mThumbnailScaler->available() && (exifInfo != nullptr)) { |
| exifInfo->enableThumb = false; |
| ALOGW("Thumbnail scaler is not available. No thumbnail is embedded"); |
| } |
| |
| CStopWatch stopwatch(true); |
| |
| if (!ProcessExif(jpeg_base, m_nStreamSize, exifInfo, appInfo)) |
| 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); |
| |
| if (!mThumbnailScaler) { |
| ALOGE("Thumbnail scaler is not prepared"); |
| return false; |
| } |
| |
| if (!mThumbnailScaler->SetSrcImage(main_width, main_height, v4l2Format)) { |
| ALOGE("Failed to configure the main image to the thumbnail scaler"); |
| return false; |
| } |
| |
| |
| if (!mThumbnailScaler->SetDstImage(m_nThumbWidth, m_nThumbHeight, GetThumbnailFormat(v4l2Format))) { |
| ALOGE("Failed to configure the target image to the thumbnail scaler"); |
| return false; |
| } |
| |
| bool okay = false; |
| |
| if (checkInBufType() == JPEG_BUF_TYPE_USER_PTR) { |
| char *bufs[ThumbnailScaler::SCALER_MAX_PLANES]; |
| int len_srcbufs[ThumbnailScaler::SCALER_MAX_PLANES]; |
| |
| if (getInBuf(bufs, len_srcbufs, ThumbnailScaler::SCALER_MAX_PLANES) < 0) { |
| ALOGE("Failed to retrieve the main image buffers"); |
| return false; |
| } |
| |
| okay = mThumbnailScaler->RunStream(bufs, len_srcbufs, m_fdIONThumbImgBuffer, m_szIONThumbImgBuffer); |
| } else { // mainbuftype == JPEG_BUF_TYPE_DMA_BUF |
| int bufs[ThumbnailScaler::SCALER_MAX_PLANES]; |
| int len_srcbufs[ThumbnailScaler::SCALER_MAX_PLANES]; |
| |
| if (getInBuf(bufs, len_srcbufs, ThumbnailScaler::SCALER_MAX_PLANES) < 0) { |
| ALOGE("Failed to retrieve the main image buffers"); |
| return false; |
| } |
| okay = mThumbnailScaler->RunStream(bufs, len_srcbufs, m_fdIONThumbImgBuffer, m_szIONThumbImgBuffer); |
| } |
| |
| if (!okay) { |
| ALOGE("Failed to convert the main image to thumbnail with the thumbnail scaler"); |
| 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; |
| } |