/* 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 * * RETURN : int32_t type of status * NO_ERROR -- success * none-zero failure code *==========================================================================*/ int32_t QCamera3PostProcessor::init(QCamera3StreamMem *memory) { ATRACE_CALL(); mOutputMem = memory; 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) { KPI_ATRACE_ASYNC_BEGIN("Camera:Reprocess", frame->frameNumber); 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(); 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 { KPI_ATRACE_ASYNC_END("Camera:Reprocess", job->fwk_src_frame->frameNumber); 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_reproc_frame)) { LOGE("Unsupported case either camera src_frame or src_reproc_frame is not NULL!"); 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; jpg_job.encode_job.p_metadata->is_statsdebug_bestats_params_valid = jpg_job.encode_job.cam_exif_params.debug_params->bestats_debug_params_valid; jpg_job.encode_job.p_metadata->is_statsdebug_bhist_params_valid = jpg_job.encode_job.cam_exif_params.debug_params->bhist_debug_params_valid; jpg_job.encode_job.p_metadata->is_statsdebug_3a_tuning_params_valid = jpg_job.encode_job.cam_exif_params.debug_params->q3a_tuning_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; } if (jpg_job.encode_job.cam_exif_params.debug_params->bestats_debug_params_valid) { jpg_job.encode_job.p_metadata->statsdebug_bestats_buffer_data = jpg_job.encode_job.cam_exif_params.debug_params->bestats_debug_params; } if (jpg_job.encode_job.cam_exif_params.debug_params->bhist_debug_params_valid) { jpg_job.encode_job.p_metadata->statsdebug_bhist_data = jpg_job.encode_job.cam_exif_params.debug_params->bhist_debug_params; } if (jpg_job.encode_job.cam_exif_params.debug_params->q3a_tuning_debug_params_valid) { jpg_job.encode_job.p_metadata->statsdebug_3a_tuning_data = jpg_job.encode_job.cam_exif_params.debug_params->q3a_tuning_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, true); 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