543 lines
17 KiB
C++
543 lines
17 KiB
C++
/*
|
|
* Copyright (C) Texas Instruments - http://www.ti.com/
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
/**
|
|
* @file Encoder_libjpeg.cpp
|
|
*
|
|
* This file encodes a YUV422I buffer to a jpeg
|
|
* TODO(XXX): Need to support formats other than yuv422i
|
|
* Change interface to pre/post-proc algo framework
|
|
*
|
|
*/
|
|
|
|
#include "Encoder_libjpeg.h"
|
|
#include "NV12_resize.h"
|
|
#include "TICameraParameters.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
#include <math.h>
|
|
|
|
extern "C" {
|
|
#include "jpeglib.h"
|
|
#include "jerror.h"
|
|
}
|
|
|
|
#define ARRAY_SIZE(array) (sizeof((array)) / sizeof((array)[0]))
|
|
#define MIN(x,y) ((x < y) ? x : y)
|
|
|
|
namespace Ti {
|
|
namespace Camera {
|
|
|
|
struct integer_string_pair {
|
|
unsigned int integer;
|
|
const char* string;
|
|
};
|
|
|
|
static integer_string_pair degress_to_exif_lut [] = {
|
|
// degrees, exif_orientation
|
|
{0, "1"},
|
|
{90, "6"},
|
|
{180, "3"},
|
|
{270, "8"},
|
|
};
|
|
struct libjpeg_destination_mgr : jpeg_destination_mgr {
|
|
libjpeg_destination_mgr(uint8_t* input, int size);
|
|
|
|
uint8_t* buf;
|
|
int bufsize;
|
|
size_t jpegsize;
|
|
};
|
|
|
|
static void libjpeg_init_destination (j_compress_ptr cinfo) {
|
|
libjpeg_destination_mgr* dest = (libjpeg_destination_mgr*)cinfo->dest;
|
|
|
|
dest->next_output_byte = dest->buf;
|
|
dest->free_in_buffer = dest->bufsize;
|
|
dest->jpegsize = 0;
|
|
}
|
|
|
|
static boolean libjpeg_empty_output_buffer(j_compress_ptr cinfo) {
|
|
libjpeg_destination_mgr* dest = (libjpeg_destination_mgr*)cinfo->dest;
|
|
|
|
dest->next_output_byte = dest->buf;
|
|
dest->free_in_buffer = dest->bufsize;
|
|
return TRUE; // ?
|
|
}
|
|
|
|
static void libjpeg_term_destination (j_compress_ptr cinfo) {
|
|
libjpeg_destination_mgr* dest = (libjpeg_destination_mgr*)cinfo->dest;
|
|
dest->jpegsize = dest->bufsize - dest->free_in_buffer;
|
|
}
|
|
|
|
libjpeg_destination_mgr::libjpeg_destination_mgr(uint8_t* input, int size) {
|
|
this->init_destination = libjpeg_init_destination;
|
|
this->empty_output_buffer = libjpeg_empty_output_buffer;
|
|
this->term_destination = libjpeg_term_destination;
|
|
|
|
this->buf = input;
|
|
this->bufsize = size;
|
|
|
|
jpegsize = 0;
|
|
}
|
|
|
|
/* private static functions */
|
|
static void nv21_to_yuv(uint8_t* dst, uint8_t* y, uint8_t* uv, int width) {
|
|
if (!dst || !y || !uv) {
|
|
return;
|
|
}
|
|
|
|
while ((width--) > 0) {
|
|
uint8_t y0 = y[0];
|
|
uint8_t v0 = uv[0];
|
|
uint8_t u0 = *(uv+1);
|
|
dst[0] = y0;
|
|
dst[1] = u0;
|
|
dst[2] = v0;
|
|
dst += 3;
|
|
y++;
|
|
if(!(width % 2)) uv+=2;
|
|
}
|
|
}
|
|
|
|
static void uyvy_to_yuv(uint8_t* dst, uint32_t* src, int width) {
|
|
if (!dst || !src) {
|
|
return;
|
|
}
|
|
|
|
if (width % 2) {
|
|
return; // not supporting odd widths
|
|
}
|
|
|
|
// currently, neon routine only supports multiple of 16 width
|
|
if (width % 16) {
|
|
while ((width-=2) >= 0) {
|
|
uint8_t u0 = (src[0] >> 0) & 0xFF;
|
|
uint8_t y0 = (src[0] >> 8) & 0xFF;
|
|
uint8_t v0 = (src[0] >> 16) & 0xFF;
|
|
uint8_t y1 = (src[0] >> 24) & 0xFF;
|
|
dst[0] = y0;
|
|
dst[1] = u0;
|
|
dst[2] = v0;
|
|
dst[3] = y1;
|
|
dst[4] = u0;
|
|
dst[5] = v0;
|
|
dst += 6;
|
|
src++;
|
|
}
|
|
} else {
|
|
int n = width;
|
|
asm volatile (
|
|
" pld [%[src], %[src_stride], lsl #2] \n\t"
|
|
" cmp %[n], #16 \n\t"
|
|
" blt 5f \n\t"
|
|
"0: @ 16 pixel swap \n\t"
|
|
" vld2.8 {q0, q1} , [%[src]]! @ q0 = uv q1 = y \n\t"
|
|
" vuzp.8 q0, q2 @ d0 = u d4 = v \n\t"
|
|
" vmov d1, d0 @ q0 = u0u1u2..u0u1u2... \n\t"
|
|
" vmov d5, d4 @ q2 = v0v1v2..v0v1v2... \n\t"
|
|
" vzip.8 d0, d1 @ q0 = u0u0u1u1u2u2... \n\t"
|
|
" vzip.8 d4, d5 @ q2 = v0v0v1v1v2v2... \n\t"
|
|
" vswp q0, q1 @ now q0 = y q1 = u q2 = v \n\t"
|
|
" vst3.8 {d0,d2,d4},[%[dst]]! \n\t"
|
|
" vst3.8 {d1,d3,d5},[%[dst]]! \n\t"
|
|
" sub %[n], %[n], #16 \n\t"
|
|
" cmp %[n], #16 \n\t"
|
|
" bge 0b \n\t"
|
|
"5: @ end \n\t"
|
|
#ifdef NEEDS_ARM_ERRATA_754319_754320
|
|
" vmov s0,s0 @ add noop for errata item \n\t"
|
|
#endif
|
|
: [dst] "+r" (dst), [src] "+r" (src), [n] "+r" (n)
|
|
: [src_stride] "r" (width)
|
|
: "cc", "memory", "q0", "q1", "q2"
|
|
);
|
|
}
|
|
}
|
|
|
|
static void yuyv_to_yuv(uint8_t* dst, uint32_t* src, int width) {
|
|
if (!dst || !src) {
|
|
return;
|
|
}
|
|
|
|
if (width % 2) {
|
|
return; // not supporting odd widths
|
|
}
|
|
|
|
// currently, neon routine only supports multiple of 16 width
|
|
if (width % 16) {
|
|
while ((width-=2) >= 0) {
|
|
uint8_t y0 = (src[0] >> 0) & 0xFF;
|
|
uint8_t u0 = (src[0] >> 8) & 0xFF;
|
|
uint8_t y1 = (src[0] >> 16) & 0xFF;
|
|
uint8_t v0 = (src[0] >> 24) & 0xFF;
|
|
dst[0] = y0;
|
|
dst[1] = u0;
|
|
dst[2] = v0;
|
|
dst[3] = y1;
|
|
dst[4] = u0;
|
|
dst[5] = v0;
|
|
dst += 6;
|
|
src++;
|
|
}
|
|
} else {
|
|
int n = width;
|
|
asm volatile (
|
|
" pld [%[src], %[src_stride], lsl #2] \n\t"
|
|
" cmp %[n], #16 \n\t"
|
|
" blt 5f \n\t"
|
|
"0: @ 16 pixel swap \n\t"
|
|
" vld2.8 {q0, q1} , [%[src]]! @ q0 = yyyy.. q1 = uvuv.. \n\t"
|
|
" vuzp.8 q1, q2 @ d2 = u d4 = v \n\t"
|
|
" vmov d3, d2 @ q1 = u0u1u2..u0u1u2... \n\t"
|
|
" vmov d5, d4 @ q2 = v0v1v2..v0v1v2... \n\t"
|
|
" vzip.8 d2, d3 @ q1 = u0u0u1u1u2u2... \n\t"
|
|
" vzip.8 d4, d5 @ q2 = v0v0v1v1v2v2... \n\t"
|
|
" @ now q0 = y q1 = u q2 = v \n\t"
|
|
" vst3.8 {d0,d2,d4},[%[dst]]! \n\t"
|
|
" vst3.8 {d1,d3,d5},[%[dst]]! \n\t"
|
|
" sub %[n], %[n], #16 \n\t"
|
|
" cmp %[n], #16 \n\t"
|
|
" bge 0b \n\t"
|
|
"5: @ end \n\t"
|
|
#ifdef NEEDS_ARM_ERRATA_754319_754320
|
|
" vmov s0,s0 @ add noop for errata item \n\t"
|
|
#endif
|
|
: [dst] "+r" (dst), [src] "+r" (src), [n] "+r" (n)
|
|
: [src_stride] "r" (width)
|
|
: "cc", "memory", "q0", "q1", "q2"
|
|
);
|
|
}
|
|
}
|
|
|
|
static void resize_nv12(Encoder_libjpeg::params* params, uint8_t* dst_buffer) {
|
|
structConvImage o_img_ptr, i_img_ptr;
|
|
|
|
if (!params || !dst_buffer) {
|
|
return;
|
|
}
|
|
|
|
//input
|
|
i_img_ptr.uWidth = params->in_width;
|
|
i_img_ptr.uStride = i_img_ptr.uWidth;
|
|
i_img_ptr.uHeight = params->in_height;
|
|
i_img_ptr.eFormat = IC_FORMAT_YCbCr420_lp;
|
|
i_img_ptr.imgPtr = (uint8_t*) params->src;
|
|
i_img_ptr.clrPtr = i_img_ptr.imgPtr + (i_img_ptr.uWidth * i_img_ptr.uHeight);
|
|
i_img_ptr.uOffset = 0;
|
|
|
|
//ouput
|
|
o_img_ptr.uWidth = params->out_width;
|
|
o_img_ptr.uStride = o_img_ptr.uWidth;
|
|
o_img_ptr.uHeight = params->out_height;
|
|
o_img_ptr.eFormat = IC_FORMAT_YCbCr420_lp;
|
|
o_img_ptr.imgPtr = dst_buffer;
|
|
o_img_ptr.clrPtr = o_img_ptr.imgPtr + (o_img_ptr.uWidth * o_img_ptr.uHeight);
|
|
o_img_ptr.uOffset = 0;
|
|
|
|
VT_resizeFrame_Video_opt2_lp(&i_img_ptr, &o_img_ptr, NULL, 0);
|
|
}
|
|
|
|
/* public static functions */
|
|
const char* ExifElementsTable::degreesToExifOrientation(unsigned int degrees) {
|
|
for (unsigned int i = 0; i < ARRAY_SIZE(degress_to_exif_lut); i++) {
|
|
if (degrees == degress_to_exif_lut[i].integer) {
|
|
return degress_to_exif_lut[i].string;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void ExifElementsTable::stringToRational(const char* str, unsigned int* num, unsigned int* den) {
|
|
int len;
|
|
char * tempVal = NULL;
|
|
|
|
if (str != NULL) {
|
|
len = strlen(str);
|
|
tempVal = (char*) malloc( sizeof(char) * (len + 1));
|
|
}
|
|
|
|
if (tempVal != NULL) {
|
|
// convert the decimal string into a rational
|
|
size_t den_len;
|
|
char *ctx;
|
|
unsigned int numerator = 0;
|
|
unsigned int denominator = 0;
|
|
char* temp = NULL;
|
|
|
|
memset(tempVal, '\0', len + 1);
|
|
strncpy(tempVal, str, len);
|
|
temp = strtok_r(tempVal, ".", &ctx);
|
|
|
|
if (temp != NULL)
|
|
numerator = atoi(temp);
|
|
|
|
if (!numerator)
|
|
numerator = 1;
|
|
|
|
temp = strtok_r(NULL, ".", &ctx);
|
|
if (temp != NULL) {
|
|
den_len = strlen(temp);
|
|
if(HUGE_VAL == den_len ) {
|
|
den_len = 0;
|
|
}
|
|
|
|
denominator = static_cast<unsigned int>(pow(10, den_len));
|
|
numerator = numerator * denominator + atoi(temp);
|
|
} else {
|
|
denominator = 1;
|
|
}
|
|
|
|
free(tempVal);
|
|
|
|
*num = numerator;
|
|
*den = denominator;
|
|
}
|
|
}
|
|
|
|
bool ExifElementsTable::isAsciiTag(const char* tag) {
|
|
// TODO(XXX): Add tags as necessary
|
|
return (strcmp(tag, TAG_GPS_PROCESSING_METHOD) == 0);
|
|
}
|
|
|
|
void ExifElementsTable::insertExifToJpeg(unsigned char* jpeg, size_t jpeg_size) {
|
|
ReadMode_t read_mode = (ReadMode_t)(READ_METADATA | READ_IMAGE);
|
|
|
|
ResetJpgfile();
|
|
if (ReadJpegSectionsFromBuffer(jpeg, jpeg_size, read_mode)) {
|
|
jpeg_opened = true;
|
|
#ifdef ANDROID_API_JB_OR_LATER
|
|
create_EXIF(table, exif_tag_count, gps_tag_count, has_datetime_tag);
|
|
#else
|
|
create_EXIF(table, exif_tag_count, gps_tag_count);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
status_t ExifElementsTable::insertExifThumbnailImage(const char* thumb, int len) {
|
|
status_t ret = NO_ERROR;
|
|
|
|
if ((len > 0) && jpeg_opened) {
|
|
ret = ReplaceThumbnailFromBuffer(thumb, len) ? NO_ERROR : UNKNOWN_ERROR;
|
|
CAMHAL_LOGDB("insertExifThumbnailImage. ReplaceThumbnail(). ret=%d", ret);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void ExifElementsTable::saveJpeg(unsigned char* jpeg, size_t jpeg_size) {
|
|
if (jpeg_opened) {
|
|
WriteJpegToBuffer(jpeg, jpeg_size);
|
|
DiscardData();
|
|
jpeg_opened = false;
|
|
}
|
|
}
|
|
|
|
/* public functions */
|
|
ExifElementsTable::~ExifElementsTable() {
|
|
int num_elements = gps_tag_count + exif_tag_count;
|
|
|
|
for (int i = 0; i < num_elements; i++) {
|
|
if (table[i].Value) {
|
|
free(table[i].Value);
|
|
}
|
|
}
|
|
|
|
if (jpeg_opened) {
|
|
DiscardData();
|
|
}
|
|
}
|
|
|
|
status_t ExifElementsTable::insertElement(const char* tag, const char* value) {
|
|
unsigned int value_length = 0;
|
|
status_t ret = NO_ERROR;
|
|
|
|
if (!value || !tag) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (position >= MAX_EXIF_TAGS_SUPPORTED) {
|
|
CAMHAL_LOGEA("Max number of EXIF elements already inserted");
|
|
return NO_MEMORY;
|
|
}
|
|
|
|
if (isAsciiTag(tag)) {
|
|
value_length = sizeof(ExifAsciiPrefix) + strlen(value + sizeof(ExifAsciiPrefix));
|
|
} else {
|
|
value_length = strlen(value);
|
|
}
|
|
|
|
if (IsGpsTag(tag)) {
|
|
table[position].GpsTag = TRUE;
|
|
table[position].Tag = GpsTagNameToValue(tag);
|
|
gps_tag_count++;
|
|
} else {
|
|
table[position].GpsTag = FALSE;
|
|
table[position].Tag = TagNameToValue(tag);
|
|
exif_tag_count++;
|
|
|
|
if (strcmp(tag, TAG_DATETIME) == 0) {
|
|
#ifdef ANDROID_API_JB_OR_LATER
|
|
has_datetime_tag = true;
|
|
#else
|
|
// jhead isn't taking datetime tag...this is a WA
|
|
ImageInfo.numDateTimeTags = 1;
|
|
memcpy(ImageInfo.DateTime, value,
|
|
MIN(ARRAY_SIZE(ImageInfo.DateTime), value_length + 1));
|
|
#endif
|
|
}
|
|
}
|
|
|
|
table[position].DataLength = 0;
|
|
table[position].Value = (char*) malloc(sizeof(char) * (value_length + 1));
|
|
|
|
if (table[position].Value) {
|
|
memcpy(table[position].Value, value, value_length + 1);
|
|
table[position].DataLength = value_length + 1;
|
|
}
|
|
|
|
position++;
|
|
return ret;
|
|
}
|
|
|
|
/* private member functions */
|
|
size_t Encoder_libjpeg::encode(params* input) {
|
|
jpeg_compress_struct cinfo;
|
|
jpeg_error_mgr jerr;
|
|
jpeg_destination_mgr jdest;
|
|
uint8_t* src = NULL, *resize_src = NULL;
|
|
uint8_t* row_tmp = NULL;
|
|
uint8_t* row_src = NULL;
|
|
uint8_t* row_uv = NULL; // used only for NV12
|
|
int out_width = 0, in_width = 0;
|
|
int out_height = 0, in_height = 0;
|
|
int bpp = 2; // for uyvy
|
|
int right_crop = 0, start_offset = 0;
|
|
|
|
if (!input) {
|
|
return 0;
|
|
}
|
|
|
|
out_width = input->out_width;
|
|
in_width = input->in_width;
|
|
out_height = input->out_height;
|
|
in_height = input->in_height;
|
|
right_crop = input->right_crop;
|
|
start_offset = input->start_offset;
|
|
src = input->src;
|
|
input->jpeg_size = 0;
|
|
|
|
libjpeg_destination_mgr dest_mgr(input->dst, input->dst_size);
|
|
|
|
// param check...
|
|
if ((in_width < 2) || (out_width < 2) || (in_height < 2) || (out_height < 2) ||
|
|
(src == NULL) || (input->dst == NULL) || (input->quality < 1) || (input->src_size < 1) ||
|
|
(input->dst_size < 1) || (input->format == NULL)) {
|
|
goto exit;
|
|
}
|
|
|
|
if (strcmp(input->format, android::CameraParameters::PIXEL_FORMAT_YUV420SP) == 0) {
|
|
bpp = 1;
|
|
if ((in_width != out_width) || (in_height != out_height)) {
|
|
resize_src = (uint8_t*) malloc(input->dst_size);
|
|
resize_nv12(input, resize_src);
|
|
if (resize_src) src = resize_src;
|
|
}
|
|
} else if (strcmp(input->format, android::CameraParameters::PIXEL_FORMAT_YUV422I) &&
|
|
strcmp(input->format, TICameraParameters::PIXEL_FORMAT_YUV422I_UYVY)) {
|
|
// we currently only support yuv422i and yuv420sp
|
|
CAMHAL_LOGEB("Encoder: format not supported: %s", input->format);
|
|
goto exit;
|
|
} else if ((in_width != out_width) || (in_height != out_height)) {
|
|
CAMHAL_LOGEB("Encoder: resizing is not supported for this format: %s", input->format);
|
|
goto exit;
|
|
}
|
|
|
|
cinfo.err = jpeg_std_error(&jerr);
|
|
|
|
jpeg_create_compress(&cinfo);
|
|
|
|
CAMHAL_LOGDB("encoding... \n\t"
|
|
"width: %d \n\t"
|
|
"height:%d \n\t"
|
|
"dest %p \n\t"
|
|
"dest size:%d \n\t"
|
|
"mSrc %p \n\t"
|
|
"format: %s",
|
|
out_width, out_height, input->dst,
|
|
input->dst_size, src, input->format);
|
|
|
|
cinfo.dest = &dest_mgr;
|
|
cinfo.image_width = out_width - right_crop;
|
|
cinfo.image_height = out_height;
|
|
cinfo.input_components = 3;
|
|
cinfo.in_color_space = JCS_YCbCr;
|
|
cinfo.input_gamma = 1;
|
|
|
|
jpeg_set_defaults(&cinfo);
|
|
jpeg_set_quality(&cinfo, input->quality, TRUE);
|
|
cinfo.dct_method = JDCT_IFAST;
|
|
|
|
jpeg_start_compress(&cinfo, TRUE);
|
|
|
|
row_tmp = (uint8_t*)malloc((out_width - right_crop) * 3);
|
|
row_src = src + start_offset;
|
|
row_uv = src + out_width * out_height * bpp;
|
|
|
|
while ((cinfo.next_scanline < cinfo.image_height) && !mCancelEncoding) {
|
|
JSAMPROW row[1]; /* pointer to JSAMPLE row[s] */
|
|
|
|
// convert input yuv format to yuv444
|
|
if (strcmp(input->format, android::CameraParameters::PIXEL_FORMAT_YUV420SP) == 0) {
|
|
nv21_to_yuv(row_tmp, row_src, row_uv, out_width - right_crop);
|
|
} else if (strcmp(input->format, TICameraParameters::PIXEL_FORMAT_YUV422I_UYVY) == 0) {
|
|
uyvy_to_yuv(row_tmp, (uint32_t*)row_src, out_width - right_crop);
|
|
} else if (strcmp(input->format, android::CameraParameters::PIXEL_FORMAT_YUV422I) == 0) {
|
|
yuyv_to_yuv(row_tmp, (uint32_t*)row_src, out_width - right_crop);
|
|
}
|
|
|
|
row[0] = row_tmp;
|
|
jpeg_write_scanlines(&cinfo, row, 1);
|
|
row_src = row_src + out_width*bpp;
|
|
|
|
// move uv row if input format needs it
|
|
if (strcmp(input->format, android::CameraParameters::PIXEL_FORMAT_YUV420SP) == 0) {
|
|
if (!(cinfo.next_scanline % 2))
|
|
row_uv = row_uv + out_width * bpp;
|
|
}
|
|
}
|
|
|
|
// no need to finish encoding routine if we are prematurely stopping
|
|
// we will end up crashing in dest_mgr since data is incomplete
|
|
if (!mCancelEncoding)
|
|
jpeg_finish_compress(&cinfo);
|
|
jpeg_destroy_compress(&cinfo);
|
|
|
|
if (resize_src) free(resize_src);
|
|
if (row_tmp) free(row_tmp);
|
|
|
|
exit:
|
|
input->jpeg_size = dest_mgr.jpegsize;
|
|
return dest_mgr.jpegsize;
|
|
}
|
|
|
|
} // namespace Camera
|
|
} // namespace Ti
|