/* Copyright (c) 2012-2016, The Linux Foundation. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* * Neither the name of The Linux Foundation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#define LOG_TAG "QCamera3PostProc"
// To remove
#include <cutils/properties.h>
// System dependencies
#include <stdio.h>
// Camera dependencies
#include "QCamera3Channel.h"
#include "QCamera3HWI.h"
#include "QCamera3PostProc.h"
#include "QCamera3Stream.h"
#include "QCameraTrace.h"
extern "C" {
#include "mm_camera_dbg.h"
}
#define ENABLE_MODEL_INFO_EXIF
namespace qcamera {
static const char ExifAsciiPrefix[] =
{ 0x41, 0x53, 0x43, 0x49, 0x49, 0x0, 0x0, 0x0 }; // "ASCII\0\0\0"
__unused
static const char ExifUndefinedPrefix[] =
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; // "\0\0\0\0\0\0\0\0"
#define EXIF_ASCII_PREFIX_SIZE 8 //(sizeof(ExifAsciiPrefix))
#define FOCAL_LENGTH_DECIMAL_PRECISION 1000
/*===========================================================================
* FUNCTION : QCamera3PostProcessor
*
* DESCRIPTION: constructor of QCamera3PostProcessor.
*
* PARAMETERS :
* @cam_ctrl : ptr to HWI object
*
* RETURN : None
*==========================================================================*/
QCamera3PostProcessor::QCamera3PostProcessor(QCamera3ProcessingChannel* ch_ctrl)
: m_parent(ch_ctrl),
mJpegCB(NULL),
mJpegUserData(NULL),
mJpegClientHandle(0),
mJpegSessionId(0),
m_bThumbnailNeeded(TRUE),
m_pReprocChannel(NULL),
m_inputPPQ(releasePPInputData, this),
m_inputFWKPPQ(NULL, this),
m_ongoingPPQ(releaseOngoingPPData, this),
m_inputJpegQ(releaseJpegData, this),
m_ongoingJpegQ(releaseJpegData, this),
m_inputMetaQ(releaseMetadata, this),
m_jpegSettingsQ(NULL, this)
{
memset(&mJpegHandle, 0, sizeof(mJpegHandle));
pthread_mutex_init(&mReprocJobLock, NULL);
}
/*===========================================================================
* FUNCTION : ~QCamera3PostProcessor
*
* DESCRIPTION: deconstructor of QCamera3PostProcessor.
*
* PARAMETERS : None
*
* RETURN : None
*==========================================================================*/
QCamera3PostProcessor::~QCamera3PostProcessor()
{
pthread_mutex_destroy(&mReprocJobLock);
}
/*===========================================================================
* FUNCTION : init
*
* DESCRIPTION: initialization of postprocessor
*
* PARAMETERS :
* @memory : output buffer memory
* @postprocess_mask : postprocess mask for the buffer
*
* RETURN : int32_t type of status
* NO_ERROR -- success
* none-zero failure code
*==========================================================================*/
int32_t QCamera3PostProcessor::init(QCamera3StreamMem *memory,
uint32_t postprocess_mask)
{
ATRACE_CALL();
mOutputMem = memory;
mPostProcMask = postprocess_mask;
m_dataProcTh.launch(dataProcessRoutine, this);
return NO_ERROR;
}
/*===========================================================================
* FUNCTION : deinit
*
* DESCRIPTION: de-initialization of postprocessor
*
* PARAMETERS : None
*
* RETURN : int32_t type of status
* NO_ERROR -- success
* none-zero failure code
*==========================================================================*/
int32_t QCamera3PostProcessor::deinit()
{
int rc = NO_ERROR;
m_dataProcTh.exit();
if (m_pReprocChannel != NULL) {
m_pReprocChannel->stop();
delete m_pReprocChannel;
m_pReprocChannel = NULL;
}
if(mJpegClientHandle > 0) {
rc = mJpegHandle.close(mJpegClientHandle);
LOGH("Jpeg closed, rc = %d, mJpegClientHandle = %x",
rc, mJpegClientHandle);
mJpegClientHandle = 0;
memset(&mJpegHandle, 0, sizeof(mJpegHandle));
}
mOutputMem = NULL;
return rc;
}
/*===========================================================================
* FUNCTION : initJpeg
*
* DESCRIPTION: initialization of jpeg through postprocessor
*
* PARAMETERS :
* @jpeg_cb : callback to handle jpeg event from mm-camera-interface
* @max_pic_dim : max picture dimensions
* @user_data : user data ptr for jpeg callback
*
* RETURN : int32_t type of status
* NO_ERROR -- success
* none-zero failure code
*==========================================================================*/
int32_t QCamera3PostProcessor::initJpeg(jpeg_encode_callback_t jpeg_cb,
cam_dimension_t* max_pic_dim,
void *user_data)
{
ATRACE_CALL();
mJpegCB = jpeg_cb;
mJpegUserData = user_data;
mm_dimension max_size;
if ((0 > max_pic_dim->width) || (0 > max_pic_dim->height)) {
LOGE("Negative dimension %dx%d",
max_pic_dim->width, max_pic_dim->height);
return BAD_VALUE;
}
//set max pic size
memset(&max_size, 0, sizeof(mm_dimension));
max_size.w = max_pic_dim->width;
max_size.h = max_pic_dim->height;
mJpegClientHandle = jpeg_open(&mJpegHandle, NULL, max_size, NULL);
if(!mJpegClientHandle) {
LOGE("jpeg_open did not work");
return UNKNOWN_ERROR;
}
return NO_ERROR;
}
/*===========================================================================
* FUNCTION : start
*
* DESCRIPTION: start postprocessor. Data process thread and data notify thread
* will be launched.
*
* PARAMETERS :
* @config : reprocess configuration
*
* RETURN : int32_t type of status
* NO_ERROR -- success
* none-zero failure code
*
* NOTE : if any reprocess is needed, a reprocess channel/stream
* will be started.
*==========================================================================*/
int32_t QCamera3PostProcessor::start(const reprocess_config_t &config)
{
int32_t rc = NO_ERROR;
QCamera3HardwareInterface* hal_obj = (QCamera3HardwareInterface*)m_parent->mUserData;
if (config.reprocess_type != REPROCESS_TYPE_NONE) {
if (m_pReprocChannel != NULL) {
m_pReprocChannel->stop();
delete m_pReprocChannel;
m_pReprocChannel = NULL;
}
// if reprocess is needed, start reprocess channel
LOGD("Setting input channel as pInputChannel");
m_pReprocChannel = hal_obj->addOfflineReprocChannel(config, m_parent);
if (m_pReprocChannel == NULL) {
LOGE("cannot add reprocess channel");
return UNKNOWN_ERROR;
}
/*start the reprocess channel only if buffers are already allocated, thus
only start it in an intermediate reprocess type, defer it for others*/
if (config.reprocess_type == REPROCESS_TYPE_JPEG) {
rc = m_pReprocChannel->start();
if (rc != 0) {
LOGE("cannot start reprocess channel");
delete m_pReprocChannel;
m_pReprocChannel = NULL;
return rc;
}
}
}
m_dataProcTh.sendCmd(CAMERA_CMD_TYPE_START_DATA_PROC, TRUE, FALSE);
return rc;
}
/*===========================================================================
* FUNCTION : flush
*
* DESCRIPTION: stop ongoing postprocess and jpeg jobs
*
* PARAMETERS : None
*
* RETURN : int32_t type of status
* NO_ERROR -- success
* none-zero failure code
*
*==========================================================================*/
int32_t QCamera3PostProcessor::flush()
{
int32_t rc = NO_ERROR;
qcamera_hal3_jpeg_data_t *jpeg_job =
(qcamera_hal3_jpeg_data_t *)m_ongoingJpegQ.dequeue();
while (jpeg_job != NULL) {
rc = mJpegHandle.abort_job(jpeg_job->jobId);
releaseJpegJobData(jpeg_job);
free(jpeg_job);
jpeg_job = (qcamera_hal3_jpeg_data_t *)m_ongoingJpegQ.dequeue();
}
rc = releaseOfflineBuffers(true);
return rc;
}
/*===========================================================================
* FUNCTION : stop
*
* DESCRIPTION: stop postprocessor. Data process and notify thread will be stopped.
*
* PARAMETERS : None
*
* RETURN : int32_t type of status
* NO_ERROR -- success
* none-zero failure code
*
* NOTE : reprocess channel will be stopped and deleted if there is any
*==========================================================================*/
int32_t QCamera3PostProcessor::stop()
{
m_dataProcTh.sendCmd(CAMERA_CMD_TYPE_STOP_DATA_PROC, TRUE, TRUE);
if (m_pReprocChannel != NULL) {
m_pReprocChannel->stop();
delete m_pReprocChannel;
m_pReprocChannel = NULL;
}
return NO_ERROR;
}
/*===========================================================================
* FUNCTION : getFWKJpegEncodeConfig
*
* DESCRIPTION: function to prepare encoding job information
*
* PARAMETERS :
* @encode_parm : param to be filled with encoding configuration
* @frame : framework input buffer
* @jpeg_settings : jpeg settings to be applied for encoding
*
* RETURN : int32_t type of status
* NO_ERROR -- success
* none-zero failure code
*==========================================================================*/
int32_t QCamera3PostProcessor::getFWKJpegEncodeConfig(
mm_jpeg_encode_params_t& encode_parm,
qcamera_fwk_input_pp_data_t *frame,
jpeg_settings_t *jpeg_settings)
{
LOGD("E");
int32_t ret = NO_ERROR;
if ((NULL == frame) || (NULL == jpeg_settings)) {
return BAD_VALUE;
}
ssize_t bufSize = mOutputMem->getSize(jpeg_settings->out_buf_index);
if (BAD_INDEX == bufSize) {
LOGE("cannot retrieve buffer size for buffer %u",
jpeg_settings->out_buf_index);
return BAD_VALUE;
}
encode_parm.jpeg_cb = mJpegCB;
encode_parm.userdata = mJpegUserData;
if (jpeg_settings->thumbnail_size.width > 0 &&
jpeg_settings->thumbnail_size.height > 0)
m_bThumbnailNeeded = TRUE;
else
m_bThumbnailNeeded = FALSE;
encode_parm.encode_thumbnail = m_bThumbnailNeeded;
// get color format
cam_format_t img_fmt = frame->reproc_config.stream_format;
encode_parm.color_format = getColorfmtFromImgFmt(img_fmt);
// get jpeg quality
encode_parm.quality = jpeg_settings->jpeg_quality;
if (encode_parm.quality <= 0) {
encode_parm.quality = 85;
}
// get jpeg thumbnail quality
encode_parm.thumb_quality = jpeg_settings->jpeg_thumb_quality;
cam_frame_len_offset_t main_offset =
frame->reproc_config.input_stream_plane_info.plane_info;
encode_parm.num_src_bufs = 1;
encode_parm.src_main_buf[0].index = 0;
encode_parm.src_main_buf[0].buf_size = frame->input_buffer.frame_len;
encode_parm.src_main_buf[0].buf_vaddr = (uint8_t *) frame->input_buffer.buffer;
encode_parm.src_main_buf[0].fd = frame->input_buffer.fd;
encode_parm.src_main_buf[0].format = MM_JPEG_FMT_YUV;
encode_parm.src_main_buf[0].offset = main_offset;
//Pass input thumbnail buffer info to encoder.
//Note: Use main buffer to encode thumbnail
if (m_bThumbnailNeeded == TRUE) {
encode_parm.num_tmb_bufs = 1;
encode_parm.src_thumb_buf[0] = encode_parm.src_main_buf[0];
}
//Pass output jpeg buffer info to encoder.
//mOutputMem is allocated by framework.
encode_parm.num_dst_bufs = 1;
encode_parm.dest_buf[0].index = 0;
encode_parm.dest_buf[0].buf_size = (size_t)bufSize;
encode_parm.dest_buf[0].buf_vaddr = (uint8_t *)mOutputMem->getPtr(
jpeg_settings->out_buf_index);
encode_parm.dest_buf[0].fd = mOutputMem->getFd(
jpeg_settings->out_buf_index);
encode_parm.dest_buf[0].format = MM_JPEG_FMT_YUV;
encode_parm.dest_buf[0].offset = main_offset;
LOGD("X");
return NO_ERROR;
LOGD("X with error %d", ret);
return ret;
}
/*===========================================================================
* FUNCTION : getJpegEncodeConfig
*
* DESCRIPTION: function to prepare encoding job information
*
* PARAMETERS :
* @encode_parm : param to be filled with encoding configuration
* #main_stream : stream object where the input buffer comes from
* @jpeg_settings : jpeg settings to be applied for encoding
*
* RETURN : int32_t type of status
* NO_ERROR -- success
* none-zero failure code
*==========================================================================*/
int32_t QCamera3PostProcessor::getJpegEncodeConfig(
mm_jpeg_encode_params_t& encode_parm,
QCamera3Stream *main_stream,
jpeg_settings_t *jpeg_settings)
{
LOGD("E");
int32_t ret = NO_ERROR;
ssize_t bufSize = 0;
encode_parm.jpeg_cb = mJpegCB;
encode_parm.userdata = mJpegUserData;
if (jpeg_settings->thumbnail_size.width > 0 &&
jpeg_settings->thumbnail_size.height > 0)
m_bThumbnailNeeded = TRUE;
else
m_bThumbnailNeeded = FALSE;
encode_parm.encode_thumbnail = m_bThumbnailNeeded;
// get color format
cam_format_t img_fmt = CAM_FORMAT_YUV_420_NV12; //default value
main_stream->getFormat(img_fmt);
encode_parm.color_format = getColorfmtFromImgFmt(img_fmt);
// get jpeg quality
encode_parm.quality = jpeg_settings->jpeg_quality;
if (encode_parm.quality <= 0) {
encode_parm.quality = 85;
}
// get jpeg thumbnail quality
encode_parm.thumb_quality = jpeg_settings->jpeg_thumb_quality;
cam_frame_len_offset_t main_offset;
memset(&main_offset, 0, sizeof(cam_frame_len_offset_t));
main_stream->getFrameOffset(main_offset);
// src buf config
//Pass input main image buffer info to encoder.
QCamera3StreamMem *pStreamMem = main_stream->getStreamBufs();
if (pStreamMem == NULL) {
LOGE("cannot get stream bufs from main stream");
ret = BAD_VALUE;
goto on_error;
}
encode_parm.num_src_bufs = MIN(pStreamMem->getCnt(), MM_JPEG_MAX_BUF);
for (uint32_t i = 0; i < encode_parm.num_src_bufs; i++) {
if (pStreamMem != NULL) {
encode_parm.src_main_buf[i].index = i;
bufSize = pStreamMem->getSize(i);
if (BAD_INDEX == bufSize) {
LOGE("cannot retrieve buffer size for buffer %u", i);
ret = BAD_VALUE;
goto on_error;
}
encode_parm.src_main_buf[i].buf_size = (size_t)bufSize;
encode_parm.src_main_buf[i].buf_vaddr = (uint8_t *)pStreamMem->getPtr(i);
encode_parm.src_main_buf[i].fd = pStreamMem->getFd(i);
encode_parm.src_main_buf[i].format = MM_JPEG_FMT_YUV;
encode_parm.src_main_buf[i].offset = main_offset;
}
}
//Pass input thumbnail buffer info to encoder.
//Note: Use main buffer to encode thumbnail
if (m_bThumbnailNeeded == TRUE) {
pStreamMem = main_stream->getStreamBufs();
if (pStreamMem == NULL) {
LOGE("cannot get stream bufs from thumb stream");
ret = BAD_VALUE;
goto on_error;
}
cam_frame_len_offset_t thumb_offset;
memset(&thumb_offset, 0, sizeof(cam_frame_len_offset_t));
main_stream->getFrameOffset(thumb_offset);
encode_parm.num_tmb_bufs = MIN(pStreamMem->getCnt(), MM_JPEG_MAX_BUF);
for (uint32_t i = 0; i < encode_parm.num_tmb_bufs; i++) {
if (pStreamMem != NULL) {
encode_parm.src_thumb_buf[i].index = i;
bufSize = pStreamMem->getSize(i);
if (BAD_INDEX == bufSize) {
LOGE("cannot retrieve buffer size for buffer %u", i);
ret = BAD_VALUE;
goto on_error;
}
encode_parm.src_thumb_buf[i].buf_size = (uint32_t)bufSize;
encode_parm.src_thumb_buf[i].buf_vaddr = (uint8_t *)pStreamMem->getPtr(i);
encode_parm.src_thumb_buf[i].fd = pStreamMem->getFd(i);
encode_parm.src_thumb_buf[i].format = MM_JPEG_FMT_YUV;
encode_parm.src_thumb_buf[i].offset = thumb_offset;
}
}
}
//Pass output jpeg buffer info to encoder.
//mJpegMem is allocated by framework.
bufSize = mOutputMem->getSize(jpeg_settings->out_buf_index);
if (BAD_INDEX == bufSize) {
LOGE("cannot retrieve buffer size for buffer %u",
jpeg_settings->out_buf_index);
ret = BAD_VALUE;
goto on_error;
}
encode_parm.num_dst_bufs = 1;
encode_parm.dest_buf[0].index = 0;
encode_parm.dest_buf[0].buf_size = (size_t)bufSize;
encode_parm.dest_buf[0].buf_vaddr = (uint8_t *)mOutputMem->getPtr(
jpeg_settings->out_buf_index);
encode_parm.dest_buf[0].fd = mOutputMem->getFd(
jpeg_settings->out_buf_index);
encode_parm.dest_buf[0].format = MM_JPEG_FMT_YUV;
encode_parm.dest_buf[0].offset = main_offset;
LOGD("X");
return NO_ERROR;
on_error:
LOGD("X with error %d", ret);
return ret;
}
int32_t QCamera3PostProcessor::processData(mm_camera_super_buf_t *input) {
return processData(input, NULL, 0);
}
/*===========================================================================
* FUNCTION : processData
*
* DESCRIPTION: enqueue data into dataProc thread
*
* PARAMETERS :
* @frame : process input frame
* @output : process output frame
*
* RETURN : int32_t type of status
* NO_ERROR -- success
* none-zero failure code
*
* NOTE : depends on if offline reprocess is needed, received frame will
* be sent to either input queue of postprocess or jpeg encoding
*==========================================================================*/
int32_t QCamera3PostProcessor::processData(mm_camera_super_buf_t *input,
buffer_handle_t *output, uint32_t frameNumber)
{
LOGD("E");
pthread_mutex_lock(&mReprocJobLock);
// enqueue to post proc input queue
qcamera_hal3_pp_buffer_t *pp_buffer = (qcamera_hal3_pp_buffer_t *)malloc(
sizeof(qcamera_hal3_pp_buffer_t));
if (NULL == pp_buffer) {
LOGE("out of memory");
return NO_MEMORY;
}
memset(pp_buffer, 0, sizeof(*pp_buffer));
pp_buffer->input = input;
pp_buffer->output = output;
pp_buffer->frameNumber = frameNumber;
m_inputPPQ.enqueue((void *)pp_buffer);
if (!(m_inputMetaQ.isEmpty())) {
LOGD("meta queue is not empty, do next job");
m_dataProcTh.sendCmd(CAMERA_CMD_TYPE_DO_NEXT_JOB, FALSE, FALSE);
} else
LOGD("metadata queue is empty");
pthread_mutex_unlock(&mReprocJobLock);
return NO_ERROR;
}
/*===========================================================================
* FUNCTION : processData
*
* DESCRIPTION: enqueue data into dataProc thread
*
* PARAMETERS :
* @frame : process frame
*
* RETURN : int32_t type of status
* NO_ERROR -- success
* none-zero failure code
*
* NOTE : depends on if offline reprocess is needed, received frame will
* be sent to either input queue of postprocess or jpeg encoding
*==========================================================================*/
int32_t QCamera3PostProcessor::processData(qcamera_fwk_input_pp_data_t *frame)
{
if (frame->reproc_config.reprocess_type != REPROCESS_TYPE_NONE) {
ATRACE_INT("Camera:Reprocess", 1);
pthread_mutex_lock(&mReprocJobLock);
// enqueu to post proc input queue
m_inputFWKPPQ.enqueue((void *)frame);
m_dataProcTh.sendCmd(CAMERA_CMD_TYPE_DO_NEXT_JOB, FALSE, FALSE);
pthread_mutex_unlock(&mReprocJobLock);
} else {
jpeg_settings_t *jpeg_settings = (jpeg_settings_t *)m_jpegSettingsQ.dequeue();
if (jpeg_settings == NULL) {
LOGE("Cannot find jpeg settings");
return BAD_VALUE;
}
LOGH("no need offline reprocess, sending to jpeg encoding");
qcamera_hal3_jpeg_data_t *jpeg_job =
(qcamera_hal3_jpeg_data_t *)malloc(sizeof(qcamera_hal3_jpeg_data_t));
if (jpeg_job == NULL) {
LOGE("No memory for jpeg job");
return NO_MEMORY;
}
memset(jpeg_job, 0, sizeof(qcamera_hal3_jpeg_data_t));
jpeg_job->fwk_frame = frame;
jpeg_job->jpeg_settings = jpeg_settings;
jpeg_job->metadata =
(metadata_buffer_t *) frame->metadata_buffer.buffer;
// enqueu to jpeg input queue
m_inputJpegQ.enqueue((void *)jpeg_job);
m_dataProcTh.sendCmd(CAMERA_CMD_TYPE_DO_NEXT_JOB, FALSE, FALSE);
}
return NO_ERROR;
}
/*===========================================================================
* FUNCTION : processPPMetadata
*
* DESCRIPTION: enqueue data into dataProc thread
*
* PARAMETERS :
* @frame : process metadata frame received from pic channel
*
* RETURN : int32_t type of status
* NO_ERROR -- success
* none-zero failure code
*
*==========================================================================*/
int32_t QCamera3PostProcessor::processPPMetadata(mm_camera_super_buf_t *reproc_meta)
{
LOGD("E");
pthread_mutex_lock(&mReprocJobLock);
// enqueue to metadata input queue
m_inputMetaQ.enqueue((void *)reproc_meta);
if (!(m_inputPPQ.isEmpty())) {
LOGD("pp queue is not empty, do next job");
m_dataProcTh.sendCmd(CAMERA_CMD_TYPE_DO_NEXT_JOB, FALSE, FALSE);
} else {
LOGD("pp queue is empty, not calling do next job");
}
pthread_mutex_unlock(&mReprocJobLock);
return NO_ERROR;
}
/*===========================================================================
* FUNCTION : processJpegSettingData
*
* DESCRIPTION: enqueue jpegSetting into dataProc thread
*
* PARAMETERS :
* @jpeg_settings : jpeg settings data received from pic channel
*
* RETURN : int32_t type of status
* NO_ERROR -- success
* none-zero failure code
*
*==========================================================================*/
int32_t QCamera3PostProcessor::processJpegSettingData(
jpeg_settings_t *jpeg_settings)
{
if (!jpeg_settings) {
LOGE("invalid jpeg settings pointer");
return -EINVAL;
}
return m_jpegSettingsQ.enqueue((void *)jpeg_settings);
}
/*===========================================================================
* FUNCTION : processPPData
*
* DESCRIPTION: process received frame after reprocess.
*
* PARAMETERS :
* @frame : received frame from reprocess channel.
*
* RETURN : int32_t type of status
* NO_ERROR -- success
* none-zero failure code
*
* NOTE : The frame after reprocess need to send to jpeg encoding.
*==========================================================================*/
int32_t QCamera3PostProcessor::processPPData(mm_camera_super_buf_t *frame)
{
qcamera_hal3_pp_data_t *job = (qcamera_hal3_pp_data_t *)m_ongoingPPQ.dequeue();
ATRACE_INT("Camera:Reprocess", 0);
if (job == NULL || ((NULL == job->src_frame) && (NULL == job->fwk_src_frame))) {
LOGE("Cannot find reprocess job");
return BAD_VALUE;
}
if (job->jpeg_settings == NULL) {
LOGE("Cannot find jpeg settings");
return BAD_VALUE;
}
qcamera_hal3_jpeg_data_t *jpeg_job =
(qcamera_hal3_jpeg_data_t *)malloc(sizeof(qcamera_hal3_jpeg_data_t));
if (jpeg_job == NULL) {
LOGE("No memory for jpeg job");
return NO_MEMORY;
}
memset(jpeg_job, 0, sizeof(qcamera_hal3_jpeg_data_t));
jpeg_job->src_frame = frame;
if(frame != job->src_frame)
jpeg_job->src_reproc_frame = job->src_frame;
if (NULL == job->fwk_src_frame) {
jpeg_job->metadata = job->metadata;
} else {
jpeg_job->metadata =
(metadata_buffer_t *) job->fwk_src_frame->metadata_buffer.buffer;
jpeg_job->fwk_src_buffer = job->fwk_src_frame;
}
jpeg_job->src_metadata = job->src_metadata;
jpeg_job->jpeg_settings = job->jpeg_settings;
// free pp job buf
free(job);
// enqueu reprocessed frame to jpeg input queue
m_inputJpegQ.enqueue((void *)jpeg_job);
// wait up data proc thread
m_dataProcTh.sendCmd(CAMERA_CMD_TYPE_DO_NEXT_JOB, FALSE, FALSE);
return NO_ERROR;
}
/*===========================================================================
* FUNCTION : dequeuePPJob
*
* DESCRIPTION: find a postprocessing job from ongoing pp queue by frame number
*
* PARAMETERS :
* @frameNumber : frame number for the pp job
*
* RETURN : ptr to a pp job struct. NULL if not found.
*==========================================================================*/
qcamera_hal3_pp_data_t *QCamera3PostProcessor::dequeuePPJob(uint32_t frameNumber) {
qcamera_hal3_pp_data_t *pp_job = NULL;
pp_job = (qcamera_hal3_pp_data_t *)m_ongoingPPQ.dequeue();
if (pp_job == NULL) {
LOGE("Fatal: ongoing PP queue is empty");
return NULL;
}
if (pp_job->fwk_src_frame &&
(pp_job->fwk_src_frame->frameNumber != frameNumber)) {
LOGE("head of pp queue doesn't match requested frame number");
}
return pp_job;
}
/*===========================================================================
* FUNCTION : findJpegJobByJobId
*
* DESCRIPTION: find a jpeg job from ongoing Jpeg queue by its job ID
*
* PARAMETERS :
* @jobId : job Id of the job
*
* RETURN : ptr to a jpeg job struct. NULL if not found.
*
* NOTE : Currently only one job is sending to mm-jpeg-interface for jpeg
* encoding. Therefore simply dequeue from the ongoing Jpeg Queue
* will serve the purpose to find the jpeg job.
*==========================================================================*/
qcamera_hal3_jpeg_data_t *QCamera3PostProcessor::findJpegJobByJobId(uint32_t jobId)
{
qcamera_hal3_jpeg_data_t * job = NULL;
if (jobId == 0) {
LOGE("not a valid jpeg jobId");
return NULL;
}
// currely only one jpeg job ongoing, so simply dequeue the head
job = (qcamera_hal3_jpeg_data_t *)m_ongoingJpegQ.dequeue();
return job;
}
/*===========================================================================
* FUNCTION : releasePPInputData
*
* DESCRIPTION: callback function to release post process input data node
*
* PARAMETERS :
* @data : ptr to post process input data
* @user_data : user data ptr (QCamera3Reprocessor)
*
* RETURN : None
*==========================================================================*/
void QCamera3PostProcessor::releasePPInputData(void *data, void *user_data)
{
QCamera3PostProcessor *pme = (QCamera3PostProcessor *)user_data;
if (NULL != pme) {
qcamera_hal3_pp_buffer_t *buf = (qcamera_hal3_pp_buffer_t *)data;
if (NULL != buf) {
if (buf->input) {
pme->releaseSuperBuf(buf->input);
free(buf->input);
buf->input = NULL;
}
}
}
}
/*===========================================================================
* FUNCTION : releaseMetaData
*
* DESCRIPTION: callback function to release metadata camera buffer
*
* PARAMETERS :
* @data : ptr to post process input data
* @user_data : user data ptr (QCamera3Reprocessor)
*
* RETURN : None
*==========================================================================*/
void QCamera3PostProcessor::releaseMetadata(void *data, void *user_data)
{
QCamera3PostProcessor *pme = (QCamera3PostProcessor *)user_data;
if (NULL != pme) {
pme->m_parent->metadataBufDone((mm_camera_super_buf_t *)data);
}
}
/*===========================================================================
* FUNCTION : releaseJpegData
*
* DESCRIPTION: callback function to release jpeg job node
*
* PARAMETERS :
* @data : ptr to ongoing jpeg job data
* @user_data : user data ptr (QCamera3Reprocessor)
*
* RETURN : None
*==========================================================================*/
void QCamera3PostProcessor::releaseJpegData(void *data, void *user_data)
{
QCamera3PostProcessor *pme = (QCamera3PostProcessor *)user_data;
if (NULL != pme) {
pme->releaseJpegJobData((qcamera_hal3_jpeg_data_t *)data);
}
}
/*===========================================================================
* FUNCTION : releaseOngoingPPData
*
* DESCRIPTION: callback function to release ongoing postprocess job node
*
* PARAMETERS :
* @data : ptr to onging postprocess job
* @user_data : user data ptr (QCamera3Reprocessor)
*
* RETURN : None
*==========================================================================*/
void QCamera3PostProcessor::releaseOngoingPPData(void *data, void *user_data)
{
QCamera3PostProcessor *pme = (QCamera3PostProcessor *)user_data;
if (NULL != pme) {
qcamera_hal3_pp_data_t *pp_data = (qcamera_hal3_pp_data_t *)data;
if (pp_data && pp_data->src_frame)
pme->releaseSuperBuf(pp_data->src_frame);
pme->releasePPJobData(pp_data);
}
}
/*===========================================================================
* FUNCTION : releaseSuperBuf
*
* DESCRIPTION: function to release a superbuf frame by returning back to kernel
*
* PARAMETERS :
* @super_buf : ptr to the superbuf frame
*
* RETURN : None
*==========================================================================*/
void QCamera3PostProcessor::releaseSuperBuf(mm_camera_super_buf_t *super_buf)
{
if (NULL != super_buf) {
if (m_parent != NULL) {
m_parent->bufDone(super_buf);
}
}
}
/*===========================================================================
* FUNCTION : releaseOfflineBuffers
*
* DESCRIPTION: function to release/unmap offline buffers if any
*
* PARAMETERS :
* @allBuffers : flag that asks to release all buffers or only one
*
* RETURN : int32_t type of status
* NO_ERROR -- success
* none-zero failure code
*==========================================================================*/
int32_t QCamera3PostProcessor::releaseOfflineBuffers(bool allBuffers)
{
int32_t rc = NO_ERROR;
if(NULL != m_pReprocChannel) {
rc = m_pReprocChannel->unmapOfflineBuffers(allBuffers);
}
return rc;
}
/*===========================================================================
* FUNCTION : releaseJpegJobData
*
* DESCRIPTION: function to release internal resources in jpeg job struct
*
* PARAMETERS :
* @job : ptr to jpeg job struct
*
* RETURN : None
*
* NOTE : original source frame need to be queued back to kernel for
* future use. Output buf of jpeg job need to be released since
* it's allocated for each job. Exif object need to be deleted.
*==========================================================================*/
void QCamera3PostProcessor::releaseJpegJobData(qcamera_hal3_jpeg_data_t *job)
{
ATRACE_CALL();
int32_t rc = NO_ERROR;
LOGD("E");
if (NULL != job) {
if (NULL != job->src_reproc_frame) {
free(job->src_reproc_frame);
job->src_reproc_frame = NULL;
}
if (NULL != job->src_frame) {
if (NULL != m_pReprocChannel) {
rc = m_pReprocChannel->bufDone(job->src_frame);
if (NO_ERROR != rc)
LOGE("bufDone error: %d", rc);
}
free(job->src_frame);
job->src_frame = NULL;
}
if (NULL != job->fwk_src_buffer) {
free(job->fwk_src_buffer);
job->fwk_src_buffer = NULL;
} else if (NULL != job->src_metadata) {
m_parent->metadataBufDone(job->src_metadata);
free(job->src_metadata);
job->src_metadata = NULL;
}
if (NULL != job->fwk_frame) {
free(job->fwk_frame);
job->fwk_frame = NULL;
}
if (NULL != job->pJpegExifObj) {
delete job->pJpegExifObj;
job->pJpegExifObj = NULL;
}
if (NULL != job->jpeg_settings) {
free(job->jpeg_settings);
job->jpeg_settings = NULL;
}
}
/* Additional trigger to process any pending jobs in the input queue */
m_dataProcTh.sendCmd(CAMERA_CMD_TYPE_DO_NEXT_JOB, FALSE, FALSE);
LOGD("X");
}
/*===========================================================================
* FUNCTION : releasePPJobData
*
* DESCRIPTION: function to release internal resources in p pjob struct
*
* PARAMETERS :
* @job : ptr to pp job struct
*
* RETURN : None
*
* NOTE : Original source metadata buffer needs to be released and
* queued back to kernel for future use. src_frame, src_metadata,
* and fwk_src_frame structures need to be freed.
*==========================================================================*/
void QCamera3PostProcessor::releasePPJobData(qcamera_hal3_pp_data_t *pp_job)
{
ATRACE_CALL();
LOGD("E");
if (NULL != pp_job) {
if (NULL != pp_job->src_frame) {
free(pp_job->src_frame);
if (NULL != pp_job->src_metadata) {
m_parent->metadataBufDone(pp_job->src_metadata);
free(pp_job->src_metadata);
}
pp_job->src_frame = NULL;
pp_job->metadata = NULL;
}
if (NULL != pp_job->fwk_src_frame) {
free(pp_job->fwk_src_frame);
pp_job->fwk_src_frame = NULL;
}
}
/* Additional trigger to process any pending jobs in the input queue */
m_dataProcTh.sendCmd(CAMERA_CMD_TYPE_DO_NEXT_JOB, FALSE, FALSE);
LOGD("X");
}
/*===========================================================================
* FUNCTION : getColorfmtFromImgFmt
*
* DESCRIPTION: function to return jpeg color format based on its image format
*
* PARAMETERS :
* @img_fmt : image format
*
* RETURN : jpeg color format that can be understandable by omx lib
*==========================================================================*/
mm_jpeg_color_format QCamera3PostProcessor::getColorfmtFromImgFmt(cam_format_t img_fmt)
{
switch (img_fmt) {
case CAM_FORMAT_YUV_420_NV21:
case CAM_FORMAT_YUV_420_NV21_VENUS:
return MM_JPEG_COLOR_FORMAT_YCRCBLP_H2V2;
case CAM_FORMAT_YUV_420_NV21_ADRENO:
return MM_JPEG_COLOR_FORMAT_YCRCBLP_H2V2;
case CAM_FORMAT_YUV_420_NV12:
case CAM_FORMAT_YUV_420_NV12_VENUS:
return MM_JPEG_COLOR_FORMAT_YCBCRLP_H2V2;
case CAM_FORMAT_YUV_420_YV12:
return MM_JPEG_COLOR_FORMAT_YCBCRLP_H2V2;
case CAM_FORMAT_YUV_422_NV61:
return MM_JPEG_COLOR_FORMAT_YCRCBLP_H2V1;
case CAM_FORMAT_YUV_422_NV16:
return MM_JPEG_COLOR_FORMAT_YCBCRLP_H2V1;
default:
return MM_JPEG_COLOR_FORMAT_YCRCBLP_H2V2;
}
}
/*===========================================================================
* FUNCTION : getJpegImgTypeFromImgFmt
*
* DESCRIPTION: function to return jpeg encode image type based on its image format
*
* PARAMETERS :
* @img_fmt : image format
*
* RETURN : return jpeg source image format (YUV or Bitstream)
*==========================================================================*/
mm_jpeg_format_t QCamera3PostProcessor::getJpegImgTypeFromImgFmt(cam_format_t img_fmt)
{
switch (img_fmt) {
case CAM_FORMAT_YUV_420_NV21:
case CAM_FORMAT_YUV_420_NV21_ADRENO:
case CAM_FORMAT_YUV_420_NV12:
case CAM_FORMAT_YUV_420_NV12_VENUS:
case CAM_FORMAT_YUV_420_NV21_VENUS:
case CAM_FORMAT_YUV_420_YV12:
case CAM_FORMAT_YUV_422_NV61:
case CAM_FORMAT_YUV_422_NV16:
return MM_JPEG_FMT_YUV;
default:
return MM_JPEG_FMT_YUV;
}
}
/*===========================================================================
* FUNCTION : encodeFWKData
*
* DESCRIPTION: function to prepare encoding job information and send to
* mm-jpeg-interface to do the encoding job
*
* PARAMETERS :
* @jpeg_job_data : ptr to a struct saving job related information
* @needNewSess : flag to indicate if a new jpeg encoding session need
* to be created. After creation, this flag will be toggled
*
* RETURN : int32_t type of status
* NO_ERROR -- success
* none-zero failure code
*==========================================================================*/
int32_t QCamera3PostProcessor::encodeFWKData(qcamera_hal3_jpeg_data_t *jpeg_job_data,
uint8_t &needNewSess)
{
LOGD("E");
int32_t ret = NO_ERROR;
mm_jpeg_job_t jpg_job;
uint32_t jobId = 0;
qcamera_fwk_input_pp_data_t *recvd_frame = NULL;
metadata_buffer_t *metadata = NULL;
jpeg_settings_t *jpeg_settings = NULL;
QCamera3HardwareInterface* hal_obj = NULL;
bool needJpegExifRotation = false;
if (NULL == jpeg_job_data) {
LOGE("Invalid jpeg job");
return BAD_VALUE;
}
recvd_frame = jpeg_job_data->fwk_frame;
if (NULL == recvd_frame) {
LOGE("Invalid input buffer");
return BAD_VALUE;
}
metadata = jpeg_job_data->metadata;
if (NULL == metadata) {
LOGE("Invalid metadata buffer");
return BAD_VALUE;
}
jpeg_settings = jpeg_job_data->jpeg_settings;
if (NULL == jpeg_settings) {
LOGE("Invalid jpeg settings buffer");
return BAD_VALUE;
}
if ((NULL != jpeg_job_data->src_frame) && (NULL != jpeg_job_data->src_frame)) {
LOGE("Unsupported case both framework and camera source buffers are invalid!");
return BAD_VALUE;
}
hal_obj = (QCamera3HardwareInterface*)m_parent->mUserData;
if (mJpegClientHandle <= 0) {
LOGE("Error: bug here, mJpegClientHandle is 0");
return UNKNOWN_ERROR;
}
cam_dimension_t src_dim;
memset(&src_dim, 0, sizeof(cam_dimension_t));
src_dim.width = recvd_frame->reproc_config.input_stream_dim.width;
src_dim.height = recvd_frame->reproc_config.input_stream_dim.height;
cam_dimension_t dst_dim;
memset(&dst_dim, 0, sizeof(cam_dimension_t));
dst_dim.width = recvd_frame->reproc_config.output_stream_dim.width;
dst_dim.height = recvd_frame->reproc_config.output_stream_dim.height;
needJpegExifRotation = hal_obj->needJpegExifRotation();
LOGH("Need new session?:%d", needNewSess);
if (needNewSess) {
//creating a new session, so we must destroy the old one
if ( 0 < mJpegSessionId ) {
ret = mJpegHandle.destroy_session(mJpegSessionId);
if (ret != NO_ERROR) {
LOGE("Error destroying an old jpeg encoding session, id = %d",
mJpegSessionId);
return ret;
}
mJpegSessionId = 0;
}
// create jpeg encoding session
mm_jpeg_encode_params_t encodeParam;
memset(&encodeParam, 0, sizeof(mm_jpeg_encode_params_t));
encodeParam.main_dim.src_dim = src_dim;
encodeParam.main_dim.dst_dim = dst_dim;
encodeParam.thumb_dim.src_dim = src_dim;
encodeParam.thumb_dim.dst_dim = jpeg_settings->thumbnail_size;
if (needJpegExifRotation) {
encodeParam.thumb_rotation = (uint32_t)jpeg_settings->jpeg_orientation;
}
getFWKJpegEncodeConfig(encodeParam, recvd_frame, jpeg_settings);
LOGH("#src bufs:%d # tmb bufs:%d #dst_bufs:%d",
encodeParam.num_src_bufs,encodeParam.num_tmb_bufs,encodeParam.num_dst_bufs);
ret = mJpegHandle.create_session(mJpegClientHandle, &encodeParam, &mJpegSessionId);
if (ret != NO_ERROR) {
LOGE("Error creating a new jpeg encoding session, ret = %d", ret);
return ret;
}
needNewSess = FALSE;
}
// Fill in new job
memset(&jpg_job, 0, sizeof(mm_jpeg_job_t));
jpg_job.job_type = JPEG_JOB_TYPE_ENCODE;
jpg_job.encode_job.session_id = mJpegSessionId;
jpg_job.encode_job.src_index = 0;
jpg_job.encode_job.dst_index = 0;
cam_rect_t crop;
memset(&crop, 0, sizeof(cam_rect_t));
//TBD_later - Zoom event removed in stream
//main_stream->getCropInfo(crop);
// Set main dim job parameters and handle rotation
if (!needJpegExifRotation && (jpeg_settings->jpeg_orientation == 90 ||
jpeg_settings->jpeg_orientation == 270)) {
jpg_job.encode_job.main_dim.src_dim.width = src_dim.height;
jpg_job.encode_job.main_dim.src_dim.height = src_dim.width;
jpg_job.encode_job.main_dim.dst_dim.width = dst_dim.height;
jpg_job.encode_job.main_dim.dst_dim.height = dst_dim.width;
jpg_job.encode_job.main_dim.crop.width = crop.height;
jpg_job.encode_job.main_dim.crop.height = crop.width;
jpg_job.encode_job.main_dim.crop.left = crop.top;
jpg_job.encode_job.main_dim.crop.top = crop.left;
} else {
jpg_job.encode_job.main_dim.src_dim = src_dim;
jpg_job.encode_job.main_dim.dst_dim = dst_dim;
jpg_job.encode_job.main_dim.crop = crop;
}
QCamera3HardwareInterface* obj = (QCamera3HardwareInterface*)m_parent->mUserData;
// get 3a sw version info
cam_q3a_version_t sw_version;
memset(&sw_version, 0, sizeof(sw_version));
if (obj)
obj->get3AVersion(sw_version);
// get exif data
QCamera3Exif *pJpegExifObj = getExifData(metadata, jpeg_settings);
jpeg_job_data->pJpegExifObj = pJpegExifObj;
if (pJpegExifObj != NULL) {
jpg_job.encode_job.exif_info.exif_data = pJpegExifObj->getEntries();
jpg_job.encode_job.exif_info.numOfEntries =
pJpegExifObj->getNumOfEntries();
jpg_job.encode_job.exif_info.debug_data.sw_3a_version[0] =
sw_version.major_version;
jpg_job.encode_job.exif_info.debug_data.sw_3a_version[1] =
sw_version.minor_version;
jpg_job.encode_job.exif_info.debug_data.sw_3a_version[2] =
sw_version.patch_version;
jpg_job.encode_job.exif_info.debug_data.sw_3a_version[3] =
sw_version.new_feature_des;
}
// thumbnail dim
LOGH("Thumbnail needed:%d", m_bThumbnailNeeded);
if (m_bThumbnailNeeded == TRUE) {
memset(&crop, 0, sizeof(cam_rect_t));
jpg_job.encode_job.thumb_dim.dst_dim =
jpeg_settings->thumbnail_size;
if (!needJpegExifRotation && (jpeg_settings->jpeg_orientation == 90 ||
jpeg_settings->jpeg_orientation == 270)) {
//swap the thumbnail destination width and height if it has
//already been rotated
int temp = jpg_job.encode_job.thumb_dim.dst_dim.width;
jpg_job.encode_job.thumb_dim.dst_dim.width =
jpg_job.encode_job.thumb_dim.dst_dim.height;
jpg_job.encode_job.thumb_dim.dst_dim.height = temp;
}
jpg_job.encode_job.thumb_dim.src_dim = src_dim;
jpg_job.encode_job.thumb_dim.crop = crop;
jpg_job.encode_job.thumb_index = 0;
}
if (metadata != NULL) {
//Fill in the metadata passed as parameter
jpg_job.encode_job.p_metadata = metadata;
} else {
LOGW("Metadata is null");
}
jpg_job.encode_job.hal_version = CAM_HAL_V3;
//Start jpeg encoding
ret = mJpegHandle.start_job(&jpg_job, &jobId);
if (ret == NO_ERROR) {
// remember job info
jpeg_job_data->jobId = jobId;
}
LOGD("X");
return ret;
}
/*===========================================================================
* FUNCTION : encodeData
*
* DESCRIPTION: function to prepare encoding job information and send to
* mm-jpeg-interface to do the encoding job
*
* PARAMETERS :
* @jpeg_job_data : ptr to a struct saving job related information
* @needNewSess : flag to indicate if a new jpeg encoding session need
* to be created. After creation, this flag will be toggled
*
* RETURN : int32_t type of status
* NO_ERROR -- success
* none-zero failure code
*==========================================================================*/
int32_t QCamera3PostProcessor::encodeData(qcamera_hal3_jpeg_data_t *jpeg_job_data,
uint8_t &needNewSess)
{
ATRACE_CALL();
LOGD("E");
int32_t ret = NO_ERROR;
mm_jpeg_job_t jpg_job;
uint32_t jobId = 0;
QCamera3Stream *main_stream = NULL;
mm_camera_buf_def_t *main_frame = NULL;
QCamera3Channel *srcChannel = NULL;
mm_camera_super_buf_t *recvd_frame = NULL;
metadata_buffer_t *metadata = NULL;
jpeg_settings_t *jpeg_settings = NULL;
QCamera3HardwareInterface* hal_obj = NULL;
mm_jpeg_debug_exif_params_t *exif_debug_params = NULL;
if (m_parent != NULL) {
hal_obj = (QCamera3HardwareInterface*)m_parent->mUserData;
} else {
LOGE("m_parent is NULL, Error");
return BAD_VALUE;
}
bool needJpegExifRotation = false;
recvd_frame = jpeg_job_data->src_frame;
metadata = jpeg_job_data->metadata;
jpeg_settings = jpeg_job_data->jpeg_settings;
LOGD("encoding bufIndex: %u",
jpeg_job_data->src_frame->bufs[0]->buf_idx);
QCamera3Channel *pChannel = NULL;
// first check picture channel
if (m_parent->getMyHandle() == recvd_frame->ch_id) {
pChannel = m_parent;
}
// check reprocess channel if not found
if (pChannel == NULL) {
if (m_pReprocChannel != NULL &&
m_pReprocChannel->getMyHandle() == recvd_frame->ch_id) {
pChannel = m_pReprocChannel;
}
}
srcChannel = pChannel;
if (srcChannel == NULL) {
LOGE("No corresponding channel (ch_id = %d) exist, return here",
recvd_frame->ch_id);
return BAD_VALUE;
}
// find snapshot frame and thumnail frame
//Note: In this version we will receive only snapshot frame.
for (uint32_t i = 0; i < recvd_frame->num_bufs; i++) {
QCamera3Stream *srcStream =
srcChannel->getStreamByHandle(recvd_frame->bufs[i]->stream_id);
if (srcStream != NULL) {
switch (srcStream->getMyType()) {
case CAM_STREAM_TYPE_SNAPSHOT:
case CAM_STREAM_TYPE_OFFLINE_PROC:
main_stream = srcStream;
main_frame = recvd_frame->bufs[i];
break;
default:
break;
}
}
}
if(NULL == main_frame){
LOGE("Main frame is NULL");
return BAD_VALUE;
}
QCamera3StreamMem *memObj = (QCamera3StreamMem *)main_frame->mem_info;
if (NULL == memObj) {
LOGE("Memeory Obj of main frame is NULL");
return NO_MEMORY;
}
// clean and invalidate cache ops through mem obj of the frame
memObj->cleanInvalidateCache(main_frame->buf_idx);
if (mJpegClientHandle <= 0) {
LOGE("Error: bug here, mJpegClientHandle is 0");
return UNKNOWN_ERROR;
}
cam_dimension_t src_dim;
memset(&src_dim, 0, sizeof(cam_dimension_t));
main_stream->getFrameDimension(src_dim);
cam_dimension_t dst_dim;
memset(&dst_dim, 0, sizeof(cam_dimension_t));
if (NO_ERROR != m_parent->getStreamSize(dst_dim)) {
LOGE("Failed to get size of the JPEG stream");
return UNKNOWN_ERROR;
}
needJpegExifRotation = hal_obj->needJpegExifRotation();
LOGH("Need new session?:%d", needNewSess);
if (needNewSess) {
//creating a new session, so we must destroy the old one
if ( 0 < mJpegSessionId ) {
ret = mJpegHandle.destroy_session(mJpegSessionId);
if (ret != NO_ERROR) {
LOGE("Error destroying an old jpeg encoding session, id = %d",
mJpegSessionId);
return ret;
}
mJpegSessionId = 0;
}
// create jpeg encoding session
mm_jpeg_encode_params_t encodeParam;
memset(&encodeParam, 0, sizeof(mm_jpeg_encode_params_t));
getJpegEncodeConfig(encodeParam, main_stream, jpeg_settings);
LOGH("#src bufs:%d # tmb bufs:%d #dst_bufs:%d",
encodeParam.num_src_bufs,encodeParam.num_tmb_bufs,encodeParam.num_dst_bufs);
if (!needJpegExifRotation &&
(jpeg_settings->jpeg_orientation == 90 ||
jpeg_settings->jpeg_orientation == 270)) {
//swap src width and height, stride and scanline due to rotation
encodeParam.main_dim.src_dim.width = src_dim.height;
encodeParam.main_dim.src_dim.height = src_dim.width;
encodeParam.thumb_dim.src_dim.width = src_dim.height;
encodeParam.thumb_dim.src_dim.height = src_dim.width;
int32_t temp = encodeParam.src_main_buf[0].offset.mp[0].stride;
encodeParam.src_main_buf[0].offset.mp[0].stride =
encodeParam.src_main_buf[0].offset.mp[0].scanline;
encodeParam.src_main_buf[0].offset.mp[0].scanline = temp;
temp = encodeParam.src_thumb_buf[0].offset.mp[0].stride;
encodeParam.src_thumb_buf[0].offset.mp[0].stride =
encodeParam.src_thumb_buf[0].offset.mp[0].scanline;
encodeParam.src_thumb_buf[0].offset.mp[0].scanline = temp;
} else {
encodeParam.main_dim.src_dim = src_dim;
encodeParam.thumb_dim.src_dim = src_dim;
}
encodeParam.main_dim.dst_dim = dst_dim;
encodeParam.thumb_dim.dst_dim = jpeg_settings->thumbnail_size;
if (needJpegExifRotation) {
encodeParam.thumb_rotation = (uint32_t)jpeg_settings->jpeg_orientation;
}
LOGI("Src Buffer cnt = %d, res = %dX%d len = %d rot = %d "
"src_dim = %dX%d dst_dim = %dX%d",
encodeParam.num_src_bufs,
encodeParam.src_main_buf[0].offset.mp[0].stride,
encodeParam.src_main_buf[0].offset.mp[0].scanline,
encodeParam.src_main_buf[0].offset.frame_len,
encodeParam.rotation,
src_dim.width, src_dim.height,
dst_dim.width, dst_dim.height);
LOGI("Src THUMB buf_cnt = %d, res = %dX%d len = %d rot = %d "
"src_dim = %dX%d, dst_dim = %dX%d",
encodeParam.num_tmb_bufs,
encodeParam.src_thumb_buf[0].offset.mp[0].stride,
encodeParam.src_thumb_buf[0].offset.mp[0].scanline,
encodeParam.src_thumb_buf[0].offset.frame_len,
encodeParam.thumb_rotation,
encodeParam.thumb_dim.src_dim.width,
encodeParam.thumb_dim.src_dim.height,
encodeParam.thumb_dim.dst_dim.width,
encodeParam.thumb_dim.dst_dim.height);
ret = mJpegHandle.create_session(mJpegClientHandle, &encodeParam, &mJpegSessionId);
if (ret != NO_ERROR) {
LOGE("Error creating a new jpeg encoding session, ret = %d", ret);
return ret;
}
needNewSess = FALSE;
}
// Fill in new job
memset(&jpg_job, 0, sizeof(mm_jpeg_job_t));
jpg_job.job_type = JPEG_JOB_TYPE_ENCODE;
jpg_job.encode_job.session_id = mJpegSessionId;
jpg_job.encode_job.src_index = (int32_t)main_frame->buf_idx;
jpg_job.encode_job.dst_index = 0;
cam_rect_t crop;
memset(&crop, 0, sizeof(cam_rect_t));
//TBD_later - Zoom event removed in stream
//main_stream->getCropInfo(crop);
// Set main dim job parameters and handle rotation
if (!needJpegExifRotation && (jpeg_settings->jpeg_orientation == 90 ||
jpeg_settings->jpeg_orientation == 270)) {
jpg_job.encode_job.main_dim.src_dim.width = src_dim.height;
jpg_job.encode_job.main_dim.src_dim.height = src_dim.width;
jpg_job.encode_job.main_dim.dst_dim.width = dst_dim.height;
jpg_job.encode_job.main_dim.dst_dim.height = dst_dim.width;
jpg_job.encode_job.main_dim.crop.width = crop.height;
jpg_job.encode_job.main_dim.crop.height = crop.width;
jpg_job.encode_job.main_dim.crop.left = crop.top;
jpg_job.encode_job.main_dim.crop.top = crop.left;
} else {
jpg_job.encode_job.main_dim.src_dim = src_dim;
jpg_job.encode_job.main_dim.dst_dim = dst_dim;
jpg_job.encode_job.main_dim.crop = crop;
}
QCamera3HardwareInterface* obj = (QCamera3HardwareInterface*)m_parent->mUserData;
// get 3a sw version info
cam_q3a_version_t sw_version;
memset(&sw_version, 0, sizeof(sw_version));
if (obj)
obj->get3AVersion(sw_version);
// get exif data
QCamera3Exif *pJpegExifObj = getExifData(metadata, jpeg_settings);
jpeg_job_data->pJpegExifObj = pJpegExifObj;
if (pJpegExifObj != NULL) {
jpg_job.encode_job.exif_info.exif_data = pJpegExifObj->getEntries();
jpg_job.encode_job.exif_info.numOfEntries =
pJpegExifObj->getNumOfEntries();
jpg_job.encode_job.exif_info.debug_data.sw_3a_version[0] =
sw_version.major_version;
jpg_job.encode_job.exif_info.debug_data.sw_3a_version[1] =
sw_version.minor_version;
jpg_job.encode_job.exif_info.debug_data.sw_3a_version[2] =
sw_version.patch_version;
jpg_job.encode_job.exif_info.debug_data.sw_3a_version[3] =
sw_version.new_feature_des;
}
// thumbnail dim
LOGH("Thumbnail needed:%d", m_bThumbnailNeeded);
if (m_bThumbnailNeeded == TRUE) {
memset(&crop, 0, sizeof(cam_rect_t));
jpg_job.encode_job.thumb_dim.dst_dim =
jpeg_settings->thumbnail_size;
if (!needJpegExifRotation &&
(jpeg_settings->jpeg_orientation == 90 ||
jpeg_settings->jpeg_orientation == 270)) {
//swap the thumbnail destination width and height if it has
//already been rotated
int temp = jpg_job.encode_job.thumb_dim.dst_dim.width;
jpg_job.encode_job.thumb_dim.dst_dim.width =
jpg_job.encode_job.thumb_dim.dst_dim.height;
jpg_job.encode_job.thumb_dim.dst_dim.height = temp;
jpg_job.encode_job.thumb_dim.src_dim.width = src_dim.height;
jpg_job.encode_job.thumb_dim.src_dim.height = src_dim.width;
} else {
jpg_job.encode_job.thumb_dim.src_dim = src_dim;
}
jpg_job.encode_job.thumb_dim.crop = crop;
jpg_job.encode_job.thumb_index = main_frame->buf_idx;
LOGI("Thumbnail idx = %d src w/h (%dx%d), dst w/h (%dx%d)",
jpg_job.encode_job.thumb_index,
jpg_job.encode_job.thumb_dim.src_dim.width,
jpg_job.encode_job.thumb_dim.src_dim.height,
jpg_job.encode_job.thumb_dim.dst_dim.width,
jpg_job.encode_job.thumb_dim.dst_dim.height);
}
LOGI("Main image idx = %d src w/h (%dx%d), dst w/h (%dx%d)",
jpg_job.encode_job.src_index,
jpg_job.encode_job.main_dim.src_dim.width,
jpg_job.encode_job.main_dim.src_dim.height,
jpg_job.encode_job.main_dim.dst_dim.width,
jpg_job.encode_job.main_dim.dst_dim.height);
jpg_job.encode_job.cam_exif_params = hal_obj->get3AExifParams();
exif_debug_params = jpg_job.encode_job.cam_exif_params.debug_params;
// Allocate for a local copy of debug parameters
jpg_job.encode_job.cam_exif_params.debug_params =
(mm_jpeg_debug_exif_params_t *) malloc (sizeof(mm_jpeg_debug_exif_params_t));
if (!jpg_job.encode_job.cam_exif_params.debug_params) {
LOGE("Out of Memory. Allocation failed for 3A debug exif params");
return NO_MEMORY;
}
jpg_job.encode_job.mobicat_mask = hal_obj->getMobicatMask();
if (metadata != NULL) {
//Fill in the metadata passed as parameter
jpg_job.encode_job.p_metadata = metadata;
jpg_job.encode_job.p_metadata->is_mobicat_aec_params_valid =
jpg_job.encode_job.cam_exif_params.cam_3a_params_valid;
if (jpg_job.encode_job.cam_exif_params.cam_3a_params_valid) {
jpg_job.encode_job.p_metadata->mobicat_aec_params =
jpg_job.encode_job.cam_exif_params.cam_3a_params;
}
if (exif_debug_params) {
// Copy debug parameters locally.
memcpy(jpg_job.encode_job.cam_exif_params.debug_params,
exif_debug_params, (sizeof(mm_jpeg_debug_exif_params_t)));
/* Save a copy of 3A debug params */
jpg_job.encode_job.p_metadata->is_statsdebug_ae_params_valid =
jpg_job.encode_job.cam_exif_params.debug_params->ae_debug_params_valid;
jpg_job.encode_job.p_metadata->is_statsdebug_awb_params_valid =
jpg_job.encode_job.cam_exif_params.debug_params->awb_debug_params_valid;
jpg_job.encode_job.p_metadata->is_statsdebug_af_params_valid =
jpg_job.encode_job.cam_exif_params.debug_params->af_debug_params_valid;
jpg_job.encode_job.p_metadata->is_statsdebug_asd_params_valid =
jpg_job.encode_job.cam_exif_params.debug_params->asd_debug_params_valid;
jpg_job.encode_job.p_metadata->is_statsdebug_stats_params_valid =
jpg_job.encode_job.cam_exif_params.debug_params->stats_debug_params_valid;
if (jpg_job.encode_job.cam_exif_params.debug_params->ae_debug_params_valid) {
jpg_job.encode_job.p_metadata->statsdebug_ae_data =
jpg_job.encode_job.cam_exif_params.debug_params->ae_debug_params;
}
if (jpg_job.encode_job.cam_exif_params.debug_params->awb_debug_params_valid) {
jpg_job.encode_job.p_metadata->statsdebug_awb_data =
jpg_job.encode_job.cam_exif_params.debug_params->awb_debug_params;
}
if (jpg_job.encode_job.cam_exif_params.debug_params->af_debug_params_valid) {
jpg_job.encode_job.p_metadata->statsdebug_af_data =
jpg_job.encode_job.cam_exif_params.debug_params->af_debug_params;
}
if (jpg_job.encode_job.cam_exif_params.debug_params->asd_debug_params_valid) {
jpg_job.encode_job.p_metadata->statsdebug_asd_data =
jpg_job.encode_job.cam_exif_params.debug_params->asd_debug_params;
}
if (jpg_job.encode_job.cam_exif_params.debug_params->stats_debug_params_valid) {
jpg_job.encode_job.p_metadata->statsdebug_stats_buffer_data =
jpg_job.encode_job.cam_exif_params.debug_params->stats_debug_params;
}
}
} else {
LOGW("Metadata is null");
}
jpg_job.encode_job.hal_version = CAM_HAL_V3;
//Start jpeg encoding
ret = mJpegHandle.start_job(&jpg_job, &jobId);
if (jpg_job.encode_job.cam_exif_params.debug_params) {
free(jpg_job.encode_job.cam_exif_params.debug_params);
}
if (ret == NO_ERROR) {
// remember job info
jpeg_job_data->jobId = jobId;
}
LOGD("X");
return ret;
}
/*===========================================================================
* FUNCTION : dataProcessRoutine
*
* DESCRIPTION: data process routine that handles input data either from input
* Jpeg Queue to do jpeg encoding, or from input PP Queue to do
* reprocess.
*
* PARAMETERS :
* @data : user data ptr (QCamera3PostProcessor)
*
* RETURN : None
*==========================================================================*/
void *QCamera3PostProcessor::dataProcessRoutine(void *data)
{
int running = 1;
int ret;
uint8_t is_active = FALSE;
uint8_t needNewSess = TRUE;
mm_camera_super_buf_t *meta_buffer = NULL;
LOGD("E");
QCamera3PostProcessor *pme = (QCamera3PostProcessor *)data;
QCameraCmdThread *cmdThread = &pme->m_dataProcTh;
cmdThread->setName("cam_data_proc");
do {
do {
ret = cam_sem_wait(&cmdThread->cmd_sem);
if (ret != 0 && errno != EINVAL) {
LOGE("cam_sem_wait error (%s)",
strerror(errno));
return NULL;
}
} while (ret != 0);
// we got notified about new cmd avail in cmd queue
camera_cmd_type_t cmd = cmdThread->getCmd();
switch (cmd) {
case CAMERA_CMD_TYPE_START_DATA_PROC:
LOGH("start data proc");
is_active = TRUE;
needNewSess = TRUE;
pme->m_ongoingPPQ.init();
pme->m_inputJpegQ.init();
pme->m_inputPPQ.init();
pme->m_inputFWKPPQ.init();
pme->m_inputMetaQ.init();
cam_sem_post(&cmdThread->sync_sem);
break;
case CAMERA_CMD_TYPE_STOP_DATA_PROC:
{
LOGH("stop data proc");
is_active = FALSE;
// cancel all ongoing jpeg jobs
qcamera_hal3_jpeg_data_t *jpeg_job =
(qcamera_hal3_jpeg_data_t *)pme->m_ongoingJpegQ.dequeue();
while (jpeg_job != NULL) {
pme->mJpegHandle.abort_job(jpeg_job->jobId);
pme->releaseJpegJobData(jpeg_job);
free(jpeg_job);
jpeg_job = (qcamera_hal3_jpeg_data_t *)pme->m_ongoingJpegQ.dequeue();
}
// destroy jpeg encoding session
if ( 0 < pme->mJpegSessionId ) {
pme->mJpegHandle.destroy_session(pme->mJpegSessionId);
pme->mJpegSessionId = 0;
}
needNewSess = TRUE;
// flush ongoing postproc Queue
pme->m_ongoingPPQ.flush();
// flush input jpeg Queue
pme->m_inputJpegQ.flush();
// flush input Postproc Queue
pme->m_inputPPQ.flush();
// flush framework input Postproc Queue
pme->m_inputFWKPPQ.flush();
pme->m_inputMetaQ.flush();
// signal cmd is completed
cam_sem_post(&cmdThread->sync_sem);
}
break;
case CAMERA_CMD_TYPE_DO_NEXT_JOB:
{
LOGH("Do next job, active is %d", is_active);
/* needNewSess is set to TRUE as postproc is not re-STARTed
* anymore for every captureRequest */
needNewSess = TRUE;
if (is_active == TRUE) {
// check if there is any ongoing jpeg jobs
if (pme->m_ongoingJpegQ.isEmpty()) {
LOGD("ongoing jpeg queue is empty so doing the jpeg job");
// no ongoing jpeg job, we are fine to send jpeg encoding job
qcamera_hal3_jpeg_data_t *jpeg_job =
(qcamera_hal3_jpeg_data_t *)pme->m_inputJpegQ.dequeue();
if (NULL != jpeg_job) {
// add into ongoing jpeg job Q
pme->m_ongoingJpegQ.enqueue((void *)jpeg_job);
if (jpeg_job->fwk_frame) {
ret = pme->encodeFWKData(jpeg_job, needNewSess);
} else {
ret = pme->encodeData(jpeg_job, needNewSess);
}
if (NO_ERROR != ret) {
// dequeue the last one
pme->m_ongoingJpegQ.dequeue(false);
pme->releaseJpegJobData(jpeg_job);
free(jpeg_job);
}
}
}
// check if there are any framework pp jobs
if (!pme->m_inputFWKPPQ.isEmpty()) {
qcamera_fwk_input_pp_data_t *fwk_frame =
(qcamera_fwk_input_pp_data_t *) pme->m_inputFWKPPQ.dequeue();
if (NULL != fwk_frame) {
qcamera_hal3_pp_data_t *pp_job =
(qcamera_hal3_pp_data_t *)malloc(sizeof(qcamera_hal3_pp_data_t));
jpeg_settings_t *jpeg_settings =
(jpeg_settings_t *)pme->m_jpegSettingsQ.dequeue();
if (pp_job != NULL) {
memset(pp_job, 0, sizeof(qcamera_hal3_pp_data_t));
pp_job->jpeg_settings = jpeg_settings;
if (pme->m_pReprocChannel != NULL) {
if (NO_ERROR != pme->m_pReprocChannel->overrideFwkMetadata(fwk_frame)) {
LOGE("Failed to extract output crop");
}
// add into ongoing PP job Q
pp_job->fwk_src_frame = fwk_frame;
pme->m_ongoingPPQ.enqueue((void *)pp_job);
ret = pme->m_pReprocChannel->doReprocessOffline(fwk_frame);
if (NO_ERROR != ret) {
// remove from ongoing PP job Q
pme->m_ongoingPPQ.dequeue(false);
}
} else {
LOGE("Reprocess channel is NULL");
ret = -1;
}
} else {
LOGE("no mem for qcamera_hal3_pp_data_t");
ret = -1;
}
if (0 != ret) {
// free pp_job
if (pp_job != NULL) {
free(pp_job);
}
// free frame
if (fwk_frame != NULL) {
free(fwk_frame);
}
}
}
}
LOGH("dequeuing pp frame");
pthread_mutex_lock(&pme->mReprocJobLock);
if(!pme->m_inputPPQ.isEmpty() && !pme->m_inputMetaQ.isEmpty()) {
qcamera_hal3_pp_buffer_t *pp_buffer =
(qcamera_hal3_pp_buffer_t *)pme->m_inputPPQ.dequeue();
meta_buffer =
(mm_camera_super_buf_t *)pme->m_inputMetaQ.dequeue();
jpeg_settings_t *jpeg_settings =
(jpeg_settings_t *)pme->m_jpegSettingsQ.dequeue();
pthread_mutex_unlock(&pme->mReprocJobLock);
qcamera_hal3_pp_data_t *pp_job =
(qcamera_hal3_pp_data_t *)malloc(sizeof(qcamera_hal3_pp_data_t));
if (pp_job == NULL) {
LOGE("no mem for qcamera_hal3_pp_data_t");
ret = -1;
} else if (meta_buffer == NULL) {
LOGE("failed to dequeue from m_inputMetaQ");
ret = -1;
} else if (pp_buffer == NULL) {
LOGE("failed to dequeue from m_inputPPQ");
ret = -1;
} else if (pp_buffer != NULL){
memset(pp_job, 0, sizeof(qcamera_hal3_pp_data_t));
pp_job->src_frame = pp_buffer->input;
pp_job->src_metadata = meta_buffer;
if (meta_buffer->bufs[0] != NULL) {
pp_job->metadata = (metadata_buffer_t *)
meta_buffer->bufs[0]->buffer;
}
pp_job->jpeg_settings = jpeg_settings;
pme->m_ongoingPPQ.enqueue((void *)pp_job);
if (pme->m_pReprocChannel != NULL) {
mm_camera_buf_def_t *meta_buffer_arg = NULL;
meta_buffer_arg = meta_buffer->bufs[0];
qcamera_fwk_input_pp_data_t fwk_frame;
memset(&fwk_frame, 0, sizeof(qcamera_fwk_input_pp_data_t));
fwk_frame.frameNumber = pp_buffer->frameNumber;
ret = pme->m_pReprocChannel->overrideMetadata(
pp_buffer, meta_buffer_arg,
pp_job->jpeg_settings,
fwk_frame);
if (NO_ERROR == ret) {
// add into ongoing PP job Q
ret = pme->m_pReprocChannel->doReprocessOffline(
&fwk_frame, true);
if (NO_ERROR != ret) {
// remove from ongoing PP job Q
pme->m_ongoingPPQ.dequeue(false);
}
}
} else {
LOGE("No reprocess. Calling processPPData directly");
ret = pme->processPPData(pp_buffer->input);
}
}
if (0 != ret) {
// free pp_job
if (pp_job != NULL) {
free(pp_job);
}
// free frame
if (pp_buffer != NULL) {
if (pp_buffer->input) {
pme->releaseSuperBuf(pp_buffer->input);
free(pp_buffer->input);
}
free(pp_buffer);
}
//free metadata
if (NULL != meta_buffer) {
pme->m_parent->metadataBufDone(meta_buffer);
free(meta_buffer);
}
} else {
if (pp_buffer != NULL) {
free(pp_buffer);
}
}
} else {
pthread_mutex_unlock(&pme->mReprocJobLock);
}
} else {
// not active, simply return buf and do no op
qcamera_hal3_jpeg_data_t *jpeg_job =
(qcamera_hal3_jpeg_data_t *)pme->m_inputJpegQ.dequeue();
if (NULL != jpeg_job) {
free(jpeg_job);
}
qcamera_hal3_pp_buffer_t* pp_buf =
(qcamera_hal3_pp_buffer_t *)pme->m_inputPPQ.dequeue();
if (NULL != pp_buf) {
if (pp_buf->input) {
pme->releaseSuperBuf(pp_buf->input);
free(pp_buf->input);
pp_buf->input = NULL;
}
free(pp_buf);
}
mm_camera_super_buf_t *metadata = (mm_camera_super_buf_t *)pme->m_inputMetaQ.dequeue();
if (metadata != NULL) {
pme->m_parent->metadataBufDone(metadata);
free(metadata);
}
qcamera_fwk_input_pp_data_t *fwk_frame =
(qcamera_fwk_input_pp_data_t *) pme->m_inputFWKPPQ.dequeue();
if (NULL != fwk_frame) {
free(fwk_frame);
}
}
}
break;
case CAMERA_CMD_TYPE_EXIT:
running = 0;
break;
default:
break;
}
} while (running);
LOGD("X");
return NULL;
}
/* EXIF related helper methods */
/*===========================================================================
* FUNCTION : getRational
*
* DESCRIPTION: compose rational struct
*
* PARAMETERS :
* @rat : ptr to struct to store rational info
* @num :num of the rational
* @denom : denom of the rational
*
* RETURN : int32_t type of status
* NO_ERROR -- success
* none-zero failure code
*==========================================================================*/
int32_t getRational(rat_t *rat, int num, int denom)
{
if ((0 > num) || (0 >= denom)) {
LOGE("Negative values");
return BAD_VALUE;
}
if (NULL == rat) {
LOGE("NULL rat input");
return BAD_VALUE;
}
rat->num = (uint32_t)num;
rat->denom = (uint32_t)denom;
return NO_ERROR;
}
/*===========================================================================
* FUNCTION : parseGPSCoordinate
*
* DESCRIPTION: parse GPS coordinate string
*
* PARAMETERS :
* @coord_str : [input] coordinate string
* @coord : [output] ptr to struct to store coordinate
*
* RETURN : int32_t type of status
* NO_ERROR -- success
* none-zero failure code
*==========================================================================*/
int parseGPSCoordinate(const char *coord_str, rat_t* coord)
{
if(coord == NULL) {
LOGE("error, invalid argument coord == NULL");
return BAD_VALUE;
}
double degF = atof(coord_str);
if (degF < 0) {
degF = -degF;
}
double minF = (degF - (int) degF) * 60;
double secF = (minF - (int) minF) * 60;
getRational(&coord[0], (int)degF, 1);
getRational(&coord[1], (int)minF, 1);
getRational(&coord[2], (int)(secF * 10000), 10000);
return NO_ERROR;
}
/*===========================================================================
* FUNCTION : getExifDateTime
*
* DESCRIPTION: query exif date time
*
* PARAMETERS :
* @dateTime : string to store exif date time
* @subsecTime : string to store exif subsec time
*
* RETURN : int32_t type of status
* NO_ERROR -- success
* none-zero failure code
*==========================================================================*/
int32_t getExifDateTime(String8 &dateTime, String8 &subsecTime)
{
int32_t ret = NO_ERROR;
//get time and date from system
struct timeval tv;
struct tm timeinfo_data;
int res = gettimeofday(&tv, NULL);
if (0 == res) {
struct tm *timeinfo = localtime_r(&tv.tv_sec, &timeinfo_data);
if (NULL != timeinfo) {
//Write datetime according to EXIF Spec
//"YYYY:MM:DD HH:MM:SS" (20 chars including \0)
dateTime = String8::format("%04d:%02d:%02d %02d:%02d:%02d",
timeinfo->tm_year + 1900, timeinfo->tm_mon + 1,
timeinfo->tm_mday, timeinfo->tm_hour,
timeinfo->tm_min, timeinfo->tm_sec);
//Write subsec according to EXIF Sepc
subsecTime = String8::format("%06ld", tv.tv_usec);
} else {
LOGE("localtime_r() error");
ret = UNKNOWN_ERROR;
}
} else if (-1 == res) {
LOGE("gettimeofday() error: %s", strerror(errno));
ret = UNKNOWN_ERROR;
} else {
LOGE("gettimeofday() unexpected return code: %d", res);
ret = UNKNOWN_ERROR;
}
return ret;
}
/*===========================================================================
* FUNCTION : getExifFocalLength
*
* DESCRIPTION: get exif focal length
*
* PARAMETERS :
* @focalLength : ptr to rational struct to store focal length
* @value : focal length value
*
* RETURN : int32_t type of status
* NO_ERROR -- success
* none-zero failure code
*==========================================================================*/
int32_t getExifFocalLength(rat_t *focalLength, float value)
{
int focalLengthValue =
(int)(value * FOCAL_LENGTH_DECIMAL_PRECISION);
return getRational(focalLength, focalLengthValue, FOCAL_LENGTH_DECIMAL_PRECISION);
}
/*===========================================================================
* FUNCTION : getExifExpTimeInfo
*
* DESCRIPTION: get exif exposure time information
*
* PARAMETERS :
* @expoTimeInfo : rational exposure time value
* @value : exposure time value
* RETURN : nt32_t type of status
* NO_ERROR -- success
* none-zero failure code
*==========================================================================*/
int32_t getExifExpTimeInfo(rat_t *expoTimeInfo, int64_t value)
{
int64_t cal_exposureTime;
if (value != 0)
cal_exposureTime = value;
else
cal_exposureTime = 60;
return getRational(expoTimeInfo, 1, (int)cal_exposureTime);
}
/*===========================================================================
* FUNCTION : getExifGpsProcessingMethod
*
* DESCRIPTION: get GPS processing method
*
* PARAMETERS :
* @gpsProcessingMethod : string to store GPS process method
* @count : length of the string
* @value : the value of the processing method
*
* RETURN : int32_t type of status
* NO_ERROR -- success
* none-zero failure code
*==========================================================================*/
int32_t getExifGpsProcessingMethod(char *gpsProcessingMethod,
uint32_t &count, char* value)
{
if(value != NULL) {
memcpy(gpsProcessingMethod, ExifAsciiPrefix, EXIF_ASCII_PREFIX_SIZE);
count = EXIF_ASCII_PREFIX_SIZE;
strlcpy(gpsProcessingMethod + EXIF_ASCII_PREFIX_SIZE,
value,
GPS_PROCESSING_METHOD_SIZE);
count += (uint32_t)strlen(value);
gpsProcessingMethod[count++] = '\0'; // increase 1 for the last NULL char
return NO_ERROR;
} else {
return BAD_VALUE;
}
}
/*===========================================================================
* FUNCTION : getExifLatitude
*
* DESCRIPTION: get exif latitude
*
* PARAMETERS :
* @latitude : ptr to rational struct to store latitude info
* @latRef : character to indicate latitude reference
* @value : value of the latitude
*
* RETURN : int32_t type of status
* NO_ERROR -- success
* none-zero failure code
*==========================================================================*/
int32_t getExifLatitude(rat_t *latitude, char *latRef, double value)
{
char str[30];
snprintf(str, sizeof(str), "%f", value);
if(str[0] != '\0') {
parseGPSCoordinate(str, latitude);
//set Latitude Ref
float latitudeValue = strtof(str, 0);
if(latitudeValue < 0.0f) {
latRef[0] = 'S';
} else {
latRef[0] = 'N';
}
latRef[1] = '\0';
return NO_ERROR;
}else{
return BAD_VALUE;
}
}
/*===========================================================================
* FUNCTION : getExifLongitude
*
* DESCRIPTION: get exif longitude
*
* PARAMETERS :
* @longitude : ptr to rational struct to store longitude info
* @lonRef : character to indicate longitude reference
* @value : value of the longitude
*
* RETURN : int32_t type of status
* NO_ERROR -- success
* none-zero failure code
*==========================================================================*/
int32_t getExifLongitude(rat_t *longitude, char *lonRef, double value)
{
char str[30];
snprintf(str, sizeof(str), "%f", value);
if(str[0] != '\0') {
parseGPSCoordinate(str, longitude);
//set Longitude Ref
float longitudeValue = strtof(str, 0);
if(longitudeValue < 0.0f) {
lonRef[0] = 'W';
} else {
lonRef[0] = 'E';
}
lonRef[1] = '\0';
return NO_ERROR;
}else{
return BAD_VALUE;
}
}
/*===========================================================================
* FUNCTION : getExifAltitude
*
* DESCRIPTION: get exif altitude
*
* PARAMETERS :
* @altitude : ptr to rational struct to store altitude info
* @altRef : character to indicate altitude reference
* @argValue : altitude value
*
* RETURN : int32_t type of status
* NO_ERROR -- success
* none-zero failure code
*==========================================================================*/
int32_t getExifAltitude(rat_t *altitude, char *altRef, double argValue)
{
char str[30];
snprintf(str, sizeof(str), "%f", argValue);
if (str[0] != '\0') {
double value = atof(str);
*altRef = 0;
if(value < 0){
*altRef = 1;
value = -value;
}
return getRational(altitude, (int)(value * 1000), 1000);
} else {
return BAD_VALUE;
}
}
/*===========================================================================
* FUNCTION : getExifGpsDateTimeStamp
*
* DESCRIPTION: get exif GPS date time stamp
*
* PARAMETERS :
* @gpsDateStamp : GPS date time stamp string
* @bufLen : length of the string
* @gpsTimeStamp : ptr to rational struct to store time stamp info
* @value : timestamp value
*
* RETURN : int32_t type of status
* NO_ERROR -- success
* none-zero failure code
*==========================================================================*/
int32_t getExifGpsDateTimeStamp(char *gpsDateStamp, uint32_t bufLen,
rat_t *gpsTimeStamp, int64_t value)
{
char str[30];
snprintf(str, sizeof(str), "%lld", (long long int)value);
if(str[0] != '\0') {
time_t unixTime = (time_t)atol(str);
struct tm *UTCTimestamp = gmtime(&unixTime);
if (UTCTimestamp != NULL && gpsDateStamp != NULL
&& gpsTimeStamp != NULL) {
strftime(gpsDateStamp, bufLen, "%Y:%m:%d", UTCTimestamp);
getRational(&gpsTimeStamp[0], UTCTimestamp->tm_hour, 1);
getRational(&gpsTimeStamp[1], UTCTimestamp->tm_min, 1);
getRational(&gpsTimeStamp[2], UTCTimestamp->tm_sec, 1);
return NO_ERROR;
} else {
LOGE("Could not get the timestamp");
return BAD_VALUE;
}
} else {
return BAD_VALUE;
}
}
/*===========================================================================
* FUNCTION : getExifExposureValue
*
* DESCRIPTION: get exif GPS date time stamp
*
* PARAMETERS :
* @exposure_val : rational exposure value
* @exposure_comp : exposure compensation
* @step : exposure step
*
* RETURN : int32_t type of status
* NO_ERROR -- success
* none-zero failure code
*==========================================================================*/
int32_t getExifExposureValue(srat_t* exposure_val, int32_t exposure_comp,
cam_rational_type_t step)
{
exposure_val->num = exposure_comp * step.numerator;
exposure_val->denom = step.denominator;
return 0;
}
/*===========================================================================
* FUNCTION : getExifData
*
* DESCRIPTION: get exif data to be passed into jpeg encoding
*
* PARAMETERS :
* @metadata : metadata of the encoding request
* @jpeg_settings : jpeg_settings for encoding
*
* RETURN : exif data from user setting and GPS
*==========================================================================*/
QCamera3Exif *QCamera3PostProcessor::getExifData(metadata_buffer_t *metadata,
jpeg_settings_t *jpeg_settings)
{
QCamera3Exif *exif = new QCamera3Exif();
if (exif == NULL) {
LOGE("No memory for QCamera3Exif");
return NULL;
}
QCamera3HardwareInterface* hal_obj = NULL;
if (m_parent != NULL) {
hal_obj = (QCamera3HardwareInterface*)m_parent->mUserData;
} else {
LOGE("m_parent is NULL, Error");
return NULL;
}
int32_t rc = NO_ERROR;
uint32_t count = 0;
// add exif entries
String8 dateTime;
String8 subsecTime;
rc = getExifDateTime(dateTime, subsecTime);
if (rc == NO_ERROR) {
exif->addEntry(EXIFTAGID_DATE_TIME, EXIF_ASCII,
(uint32_t)(dateTime.length() + 1), (void *)dateTime.string());
exif->addEntry(EXIFTAGID_EXIF_DATE_TIME_ORIGINAL, EXIF_ASCII,
(uint32_t)(dateTime.length() + 1), (void *)dateTime.string());
exif->addEntry(EXIFTAGID_EXIF_DATE_TIME_DIGITIZED, EXIF_ASCII,
(uint32_t)(dateTime.length() + 1), (void *)dateTime.string());
exif->addEntry(EXIFTAGID_SUBSEC_TIME, EXIF_ASCII,
(uint32_t)(subsecTime.length() + 1), (void *)subsecTime.string());
exif->addEntry(EXIFTAGID_SUBSEC_TIME_ORIGINAL, EXIF_ASCII,
(uint32_t)(subsecTime.length() + 1), (void *)subsecTime.string());
exif->addEntry(EXIFTAGID_SUBSEC_TIME_DIGITIZED, EXIF_ASCII,
(uint32_t)(subsecTime.length() + 1), (void *)subsecTime.string());
} else {
LOGW("getExifDateTime failed");
}
if (metadata != NULL) {
IF_META_AVAILABLE(float, focal_length, CAM_INTF_META_LENS_FOCAL_LENGTH, metadata) {
rat_t focalLength;
rc = getExifFocalLength(&focalLength, *focal_length);
if (rc == NO_ERROR) {
exif->addEntry(EXIFTAGID_FOCAL_LENGTH,
EXIF_RATIONAL,
1,
(void *)&(focalLength));
} else {
LOGW("getExifFocalLength failed");
}
}
IF_META_AVAILABLE(int32_t, isoSpeed, CAM_INTF_META_SENSOR_SENSITIVITY, metadata) {
int16_t fwk_isoSpeed = (int16_t) *isoSpeed;
exif->addEntry(EXIFTAGID_ISO_SPEED_RATING, EXIF_SHORT, 1, (void *) &(fwk_isoSpeed));
}
IF_META_AVAILABLE(int64_t, sensor_exposure_time,
CAM_INTF_META_SENSOR_EXPOSURE_TIME, metadata) {
rat_t sensorExpTime;
rc = getExifExpTimeInfo(&sensorExpTime, *sensor_exposure_time);
if (rc == NO_ERROR){
exif->addEntry(EXIFTAGID_EXPOSURE_TIME,
EXIF_RATIONAL,
1,
(void *)&(sensorExpTime));
} else {
LOGW("getExifExpTimeInfo failed");
}
}
char* jpeg_gps_processing_method = jpeg_settings->gps_processing_method;
if (strlen(jpeg_gps_processing_method) > 0) {
char gpsProcessingMethod[EXIF_ASCII_PREFIX_SIZE +
GPS_PROCESSING_METHOD_SIZE];
count = 0;
rc = getExifGpsProcessingMethod(gpsProcessingMethod,
count,
jpeg_gps_processing_method);
if(rc == NO_ERROR) {
exif->addEntry(EXIFTAGID_GPS_PROCESSINGMETHOD,
EXIF_ASCII,
count,
(void *)gpsProcessingMethod);
} else {
LOGW("getExifGpsProcessingMethod failed");
}
}
if (jpeg_settings->gps_coordinates_valid) {
//latitude
rat_t latitude[3];
char latRef[2];
rc = getExifLatitude(latitude, latRef,
jpeg_settings->gps_coordinates[0]);
if(rc == NO_ERROR) {
exif->addEntry(EXIFTAGID_GPS_LATITUDE,
EXIF_RATIONAL,
3,
(void *)latitude);
exif->addEntry(EXIFTAGID_GPS_LATITUDE_REF,
EXIF_ASCII,
2,
(void *)latRef);
} else {
LOGW("getExifLatitude failed");
}
//longitude
rat_t longitude[3];
char lonRef[2];
rc = getExifLongitude(longitude, lonRef,
jpeg_settings->gps_coordinates[1]);
if(rc == NO_ERROR) {
exif->addEntry(EXIFTAGID_GPS_LONGITUDE,
EXIF_RATIONAL,
3,
(void *)longitude);
exif->addEntry(EXIFTAGID_GPS_LONGITUDE_REF,
EXIF_ASCII,
2,
(void *)lonRef);
} else {
LOGW("getExifLongitude failed");
}
//altitude
rat_t altitude;
char altRef;
rc = getExifAltitude(&altitude, &altRef,
jpeg_settings->gps_coordinates[2]);
if(rc == NO_ERROR) {
exif->addEntry(EXIFTAGID_GPS_ALTITUDE,
EXIF_RATIONAL,
1,
(void *)&(altitude));
exif->addEntry(EXIFTAGID_GPS_ALTITUDE_REF,
EXIF_BYTE,
1,
(void *)&altRef);
} else {
LOGW("getExifAltitude failed");
}
}
if (jpeg_settings->gps_timestamp_valid) {
char gpsDateStamp[20];
rat_t gpsTimeStamp[3];
rc = getExifGpsDateTimeStamp(gpsDateStamp, 20, gpsTimeStamp,
jpeg_settings->gps_timestamp);
if(rc == NO_ERROR) {
exif->addEntry(EXIFTAGID_GPS_DATESTAMP, EXIF_ASCII,
(uint32_t)(strlen(gpsDateStamp) + 1),
(void *)gpsDateStamp);
exif->addEntry(EXIFTAGID_GPS_TIMESTAMP,
EXIF_RATIONAL,
3,
(void *)gpsTimeStamp);
} else {
LOGW("getExifGpsDataTimeStamp failed");
}
}
IF_META_AVAILABLE(int32_t, exposure_comp, CAM_INTF_PARM_EXPOSURE_COMPENSATION, metadata) {
IF_META_AVAILABLE(cam_rational_type_t, comp_step, CAM_INTF_PARM_EV_STEP, metadata) {
srat_t exposure_val;
rc = getExifExposureValue(&exposure_val, *exposure_comp, *comp_step);
if(rc == NO_ERROR) {
exif->addEntry(EXIFTAGID_EXPOSURE_BIAS_VALUE,
EXIF_SRATIONAL,
1,
(void *)(&exposure_val));
} else {
LOGW("getExifExposureValue failed ");
}
}
}
} else {
LOGW("no metadata provided ");
}
#ifdef ENABLE_MODEL_INFO_EXIF
char value[PROPERTY_VALUE_MAX];
if (property_get("ro.product.manufacturer", value, "QCOM-AA") > 0) {
exif->addEntry(EXIFTAGID_MAKE, EXIF_ASCII,
(uint32_t)(strlen(value) + 1), (void *)value);
} else {
LOGW("getExifMaker failed");
}
if (property_get("ro.product.model", value, "QCAM-AA") > 0) {
exif->addEntry(EXIFTAGID_MODEL, EXIF_ASCII,
(uint32_t)(strlen(value) + 1), (void *)value);
} else {
LOGW("getExifModel failed");
}
if (property_get("ro.build.description", value, "QCAM-AA") > 0) {
exif->addEntry(EXIFTAGID_SOFTWARE, EXIF_ASCII,
(uint32_t)(strlen(value) + 1), (void *)value);
} else {
LOGW("getExifSoftware failed");
}
#endif
if (jpeg_settings->image_desc_valid) {
if (exif->addEntry(EXIFTAGID_IMAGE_DESCRIPTION, EXIF_ASCII,
strlen(jpeg_settings->image_desc)+1,
(void *)jpeg_settings->image_desc)) {
LOGW("Adding IMAGE_DESCRIPTION tag failed");
}
}
if( hal_obj->needJpegExifRotation()) {
int16_t orientation;
switch (jpeg_settings->jpeg_orientation) {
case 0:
orientation = 1;
break;
case 90:
orientation = 6;
break;
case 180:
orientation = 3;
break;
case 270:
orientation = 8;
break;
default:
orientation = 1;
break;
}
exif->addEntry(EXIFTAGID_ORIENTATION,
EXIF_SHORT,
1,
(void *)&orientation);
exif->addEntry(EXIFTAGID_TN_ORIENTATION,
EXIF_SHORT,
1,
(void *)&orientation);
}
return exif;
}
/*===========================================================================
* FUNCTION : QCamera3Exif
*
* DESCRIPTION: constructor of QCamera3Exif
*
* PARAMETERS : None
*
* RETURN : None
*==========================================================================*/
QCamera3Exif::QCamera3Exif()
: m_nNumEntries(0)
{
memset(m_Entries, 0, sizeof(m_Entries));
}
/*===========================================================================
* FUNCTION : ~QCamera3Exif
*
* DESCRIPTION: deconstructor of QCamera3Exif. Will release internal memory ptr.
*
* PARAMETERS : None
*
* RETURN : None
*==========================================================================*/
QCamera3Exif::~QCamera3Exif()
{
for (uint32_t i = 0; i < m_nNumEntries; i++) {
switch (m_Entries[i].tag_entry.type) {
case EXIF_BYTE:
{
if (m_Entries[i].tag_entry.count > 1 &&
m_Entries[i].tag_entry.data._bytes != NULL) {
free(m_Entries[i].tag_entry.data._bytes);
m_Entries[i].tag_entry.data._bytes = NULL;
}
}
break;
case EXIF_ASCII:
{
if (m_Entries[i].tag_entry.data._ascii != NULL) {
free(m_Entries[i].tag_entry.data._ascii);
m_Entries[i].tag_entry.data._ascii = NULL;
}
}
break;
case EXIF_SHORT:
{
if (m_Entries[i].tag_entry.count > 1 &&
m_Entries[i].tag_entry.data._shorts != NULL) {
free(m_Entries[i].tag_entry.data._shorts);
m_Entries[i].tag_entry.data._shorts = NULL;
}
}
break;
case EXIF_LONG:
{
if (m_Entries[i].tag_entry.count > 1 &&
m_Entries[i].tag_entry.data._longs != NULL) {
free(m_Entries[i].tag_entry.data._longs);
m_Entries[i].tag_entry.data._longs = NULL;
}
}
break;
case EXIF_RATIONAL:
{
if (m_Entries[i].tag_entry.count > 1 &&
m_Entries[i].tag_entry.data._rats != NULL) {
free(m_Entries[i].tag_entry.data._rats);
m_Entries[i].tag_entry.data._rats = NULL;
}
}
break;
case EXIF_UNDEFINED:
{
if (m_Entries[i].tag_entry.data._undefined != NULL) {
free(m_Entries[i].tag_entry.data._undefined);
m_Entries[i].tag_entry.data._undefined = NULL;
}
}
break;
case EXIF_SLONG:
{
if (m_Entries[i].tag_entry.count > 1 &&
m_Entries[i].tag_entry.data._slongs != NULL) {
free(m_Entries[i].tag_entry.data._slongs);
m_Entries[i].tag_entry.data._slongs = NULL;
}
}
break;
case EXIF_SRATIONAL:
{
if (m_Entries[i].tag_entry.count > 1 &&
m_Entries[i].tag_entry.data._srats != NULL) {
free(m_Entries[i].tag_entry.data._srats);
m_Entries[i].tag_entry.data._srats = NULL;
}
}
break;
default:
LOGW("Error, Unknown type");
break;
}
}
}
/*===========================================================================
* FUNCTION : addEntry
*
* DESCRIPTION: function to add an entry to exif data
*
* PARAMETERS :
* @tagid : exif tag ID
* @type : data type
* @count : number of data in uint of its type
* @data : input data ptr
*
* RETURN : int32_t type of status
* NO_ERROR -- success
* none-zero failure code
*==========================================================================*/
int32_t QCamera3Exif::addEntry(exif_tag_id_t tagid,
exif_tag_type_t type,
uint32_t count,
void *data)
{
int32_t rc = NO_ERROR;
if(m_nNumEntries >= MAX_HAL3_EXIF_TABLE_ENTRIES) {
LOGE("Number of entries exceeded limit");
return NO_MEMORY;
}
m_Entries[m_nNumEntries].tag_id = tagid;
m_Entries[m_nNumEntries].tag_entry.type = type;
m_Entries[m_nNumEntries].tag_entry.count = count;
m_Entries[m_nNumEntries].tag_entry.copy = 1;
switch (type) {
case EXIF_BYTE:
{
if (count > 1) {
uint8_t *values = (uint8_t *)malloc(count);
if (values == NULL) {
LOGE("No memory for byte array");
rc = NO_MEMORY;
} else {
memcpy(values, data, count);
m_Entries[m_nNumEntries].tag_entry.data._bytes = values;
}
} else {
m_Entries[m_nNumEntries].tag_entry.data._byte =
*(uint8_t *)data;
}
}
break;
case EXIF_ASCII:
{
char *str = NULL;
str = (char *)malloc(count + 1);
if (str == NULL) {
LOGE("No memory for ascii string");
rc = NO_MEMORY;
} else {
memset(str, 0, count + 1);
memcpy(str, data, count);
m_Entries[m_nNumEntries].tag_entry.data._ascii = str;
}
}
break;
case EXIF_SHORT:
{
uint16_t *exif_data = (uint16_t *)data;
if (count > 1) {
uint16_t *values =
(uint16_t *)malloc(count * sizeof(uint16_t));
if (values == NULL) {
LOGE("No memory for short array");
rc = NO_MEMORY;
} else {
memcpy(values, exif_data, count * sizeof(uint16_t));
m_Entries[m_nNumEntries].tag_entry.data._shorts = values;
}
} else {
m_Entries[m_nNumEntries].tag_entry.data._short =
*(uint16_t *)data;
}
}
break;
case EXIF_LONG:
{
uint32_t *exif_data = (uint32_t *)data;
if (count > 1) {
uint32_t *values =
(uint32_t *)malloc(count * sizeof(uint32_t));
if (values == NULL) {
LOGE("No memory for long array");
rc = NO_MEMORY;
} else {
memcpy(values, exif_data, count * sizeof(uint32_t));
m_Entries[m_nNumEntries].tag_entry.data._longs = values;
}
} else {
m_Entries[m_nNumEntries].tag_entry.data._long =
*(uint32_t *)data;
}
}
break;
case EXIF_RATIONAL:
{
rat_t *exif_data = (rat_t *)data;
if (count > 1) {
rat_t *values = (rat_t *)malloc(count * sizeof(rat_t));
if (values == NULL) {
LOGE("No memory for rational array");
rc = NO_MEMORY;
} else {
memcpy(values, exif_data, count * sizeof(rat_t));
m_Entries[m_nNumEntries].tag_entry.data._rats = values;
}
} else {
m_Entries[m_nNumEntries].tag_entry.data._rat =
*(rat_t *)data;
}
}
break;
case EXIF_UNDEFINED:
{
uint8_t *values = (uint8_t *)malloc(count);
if (values == NULL) {
LOGE("No memory for undefined array");
rc = NO_MEMORY;
} else {
memcpy(values, data, count);
m_Entries[m_nNumEntries].tag_entry.data._undefined = values;
}
}
break;
case EXIF_SLONG:
{
int32_t *exif_data = (int32_t *)data;
if (count > 1) {
int32_t *values =
(int32_t *)malloc(count * sizeof(int32_t));
if (values == NULL) {
LOGE("No memory for signed long array");
rc = NO_MEMORY;
} else {
memcpy(values, exif_data, count * sizeof(int32_t));
m_Entries[m_nNumEntries].tag_entry.data._slongs =values;
}
} else {
m_Entries[m_nNumEntries].tag_entry.data._slong =
*(int32_t *)data;
}
}
break;
case EXIF_SRATIONAL:
{
srat_t *exif_data = (srat_t *)data;
if (count > 1) {
srat_t *values = (srat_t *)malloc(count * sizeof(srat_t));
if (values == NULL) {
LOGE("No memory for sign rational array");
rc = NO_MEMORY;
} else {
memcpy(values, exif_data, count * sizeof(srat_t));
m_Entries[m_nNumEntries].tag_entry.data._srats = values;
}
} else {
m_Entries[m_nNumEntries].tag_entry.data._srat =
*(srat_t *)data;
}
}
break;
default:
LOGE("Error, Unknown type");
break;
}
// Increase number of entries
m_nNumEntries++;
return rc;
}
}; // namespace qcamera