You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
912 lines
31 KiB
912 lines
31 KiB
/*
|
|
* 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());
|
|
if (!mThumbnailScaler->available())
|
|
ALOGW("Thumbnail scaler is not available.");
|
|
|
|
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);
|
|
|
|
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;
|
|
}
|