#include "CreateJavaOutputStreamAdaptor.h" #include "SkJPEGWriteUtility.h" #include "YuvToJpegEncoder.h" #include #include #include "core_jni_helpers.h" #include YuvToJpegEncoder* YuvToJpegEncoder::create(int format, int* strides) { // Only ImageFormat.NV21 and ImageFormat.YUY2 are supported // for now. if (format == HAL_PIXEL_FORMAT_YCrCb_420_SP) { return new Yuv420SpToJpegEncoder(strides); } else if (format == HAL_PIXEL_FORMAT_YCbCr_422_I) { return new Yuv422IToJpegEncoder(strides); } else { return NULL; } } YuvToJpegEncoder::YuvToJpegEncoder(int* strides) : fStrides(strides) { } struct ErrorMgr { struct jpeg_error_mgr pub; jmp_buf jmp; }; void error_exit(j_common_ptr cinfo) { ErrorMgr* err = (ErrorMgr*) cinfo->err; (*cinfo->err->output_message) (cinfo); longjmp(err->jmp, 1); } bool YuvToJpegEncoder::encode(SkWStream* stream, void* inYuv, int width, int height, int* offsets, int jpegQuality) { jpeg_compress_struct cinfo; ErrorMgr err; skjpeg_destination_mgr sk_wstream(stream); cinfo.err = jpeg_std_error(&err.pub); err.pub.error_exit = error_exit; if (setjmp(err.jmp)) { jpeg_destroy_compress(&cinfo); return false; } jpeg_create_compress(&cinfo); cinfo.dest = &sk_wstream; setJpegCompressStruct(&cinfo, width, height, jpegQuality); jpeg_start_compress(&cinfo, TRUE); compress(&cinfo, (uint8_t*) inYuv, offsets); jpeg_finish_compress(&cinfo); jpeg_destroy_compress(&cinfo); return true; } void YuvToJpegEncoder::setJpegCompressStruct(jpeg_compress_struct* cinfo, int width, int height, int quality) { cinfo->image_width = width; cinfo->image_height = height; cinfo->input_components = 3; cinfo->in_color_space = JCS_YCbCr; jpeg_set_defaults(cinfo); jpeg_set_quality(cinfo, quality, TRUE); jpeg_set_colorspace(cinfo, JCS_YCbCr); cinfo->raw_data_in = TRUE; cinfo->dct_method = JDCT_IFAST; configSamplingFactors(cinfo); } /////////////////////////////////////////////////////////////////// Yuv420SpToJpegEncoder::Yuv420SpToJpegEncoder(int* strides) : YuvToJpegEncoder(strides) { fNumPlanes = 2; } void Yuv420SpToJpegEncoder::compress(jpeg_compress_struct* cinfo, uint8_t* yuv, int* offsets) { SkDebugf("onFlyCompress"); JSAMPROW y[16]; JSAMPROW cb[8]; JSAMPROW cr[8]; JSAMPARRAY planes[3]; planes[0] = y; planes[1] = cb; planes[2] = cr; int width = cinfo->image_width; int height = cinfo->image_height; uint8_t* yPlanar = yuv + offsets[0]; uint8_t* vuPlanar = yuv + offsets[1]; //width * height; uint8_t* uRows = new uint8_t [8 * (width >> 1)]; uint8_t* vRows = new uint8_t [8 * (width >> 1)]; // process 16 lines of Y and 8 lines of U/V each time. while (cinfo->next_scanline < cinfo->image_height) { //deitnerleave u and v deinterleave(vuPlanar, uRows, vRows, cinfo->next_scanline, width, height); // Jpeg library ignores the rows whose indices are greater than height. for (int i = 0; i < 16; i++) { // y row y[i] = yPlanar + (cinfo->next_scanline + i) * fStrides[0]; // construct u row and v row if ((i & 1) == 0) { // height and width are both halved because of downsampling int offset = (i >> 1) * (width >> 1); cb[i/2] = uRows + offset; cr[i/2] = vRows + offset; } } jpeg_write_raw_data(cinfo, planes, 16); } delete [] uRows; delete [] vRows; } void Yuv420SpToJpegEncoder::deinterleave(uint8_t* vuPlanar, uint8_t* uRows, uint8_t* vRows, int rowIndex, int width, int height) { int numRows = (height - rowIndex) / 2; if (numRows > 8) numRows = 8; for (int row = 0; row < numRows; ++row) { int offset = ((rowIndex >> 1) + row) * fStrides[1]; uint8_t* vu = vuPlanar + offset; for (int i = 0; i < (width >> 1); ++i) { int index = row * (width >> 1) + i; uRows[index] = vu[1]; vRows[index] = vu[0]; vu += 2; } } } void Yuv420SpToJpegEncoder::configSamplingFactors(jpeg_compress_struct* cinfo) { // cb and cr are horizontally downsampled and vertically downsampled as well. cinfo->comp_info[0].h_samp_factor = 2; cinfo->comp_info[0].v_samp_factor = 2; cinfo->comp_info[1].h_samp_factor = 1; cinfo->comp_info[1].v_samp_factor = 1; cinfo->comp_info[2].h_samp_factor = 1; cinfo->comp_info[2].v_samp_factor = 1; } /////////////////////////////////////////////////////////////////////////////// Yuv422IToJpegEncoder::Yuv422IToJpegEncoder(int* strides) : YuvToJpegEncoder(strides) { fNumPlanes = 1; } void Yuv422IToJpegEncoder::compress(jpeg_compress_struct* cinfo, uint8_t* yuv, int* offsets) { SkDebugf("onFlyCompress_422"); JSAMPROW y[16]; JSAMPROW cb[16]; JSAMPROW cr[16]; JSAMPARRAY planes[3]; planes[0] = y; planes[1] = cb; planes[2] = cr; int width = cinfo->image_width; int height = cinfo->image_height; uint8_t* yRows = new uint8_t [16 * width]; uint8_t* uRows = new uint8_t [16 * (width >> 1)]; uint8_t* vRows = new uint8_t [16 * (width >> 1)]; uint8_t* yuvOffset = yuv + offsets[0]; // process 16 lines of Y and 16 lines of U/V each time. while (cinfo->next_scanline < cinfo->image_height) { deinterleave(yuvOffset, yRows, uRows, vRows, cinfo->next_scanline, width, height); // Jpeg library ignores the rows whose indices are greater than height. for (int i = 0; i < 16; i++) { // y row y[i] = yRows + i * width; // construct u row and v row // width is halved because of downsampling int offset = i * (width >> 1); cb[i] = uRows + offset; cr[i] = vRows + offset; } jpeg_write_raw_data(cinfo, planes, 16); } delete [] yRows; delete [] uRows; delete [] vRows; } void Yuv422IToJpegEncoder::deinterleave(uint8_t* yuv, uint8_t* yRows, uint8_t* uRows, uint8_t* vRows, int rowIndex, int width, int height) { int numRows = height - rowIndex; if (numRows > 16) numRows = 16; for (int row = 0; row < numRows; ++row) { uint8_t* yuvSeg = yuv + (rowIndex + row) * fStrides[0]; for (int i = 0; i < (width >> 1); ++i) { int indexY = row * width + (i << 1); int indexU = row * (width >> 1) + i; yRows[indexY] = yuvSeg[0]; yRows[indexY + 1] = yuvSeg[2]; uRows[indexU] = yuvSeg[1]; vRows[indexU] = yuvSeg[3]; yuvSeg += 4; } } } void Yuv422IToJpegEncoder::configSamplingFactors(jpeg_compress_struct* cinfo) { // cb and cr are horizontally downsampled and vertically downsampled as well. cinfo->comp_info[0].h_samp_factor = 2; cinfo->comp_info[0].v_samp_factor = 2; cinfo->comp_info[1].h_samp_factor = 1; cinfo->comp_info[1].v_samp_factor = 2; cinfo->comp_info[2].h_samp_factor = 1; cinfo->comp_info[2].v_samp_factor = 2; } /////////////////////////////////////////////////////////////////////////////// static jboolean YuvImage_compressToJpeg(JNIEnv* env, jobject, jbyteArray inYuv, jint format, jint width, jint height, jintArray offsets, jintArray strides, jint jpegQuality, jobject jstream, jbyteArray jstorage) { jbyte* yuv = env->GetByteArrayElements(inYuv, NULL); SkWStream* strm = CreateJavaOutputStreamAdaptor(env, jstream, jstorage); jint* imgOffsets = env->GetIntArrayElements(offsets, NULL); jint* imgStrides = env->GetIntArrayElements(strides, NULL); YuvToJpegEncoder* encoder = YuvToJpegEncoder::create(format, imgStrides); jboolean result = JNI_FALSE; if (encoder != NULL) { encoder->encode(strm, yuv, width, height, imgOffsets, jpegQuality); delete encoder; result = JNI_TRUE; } env->ReleaseByteArrayElements(inYuv, yuv, 0); env->ReleaseIntArrayElements(offsets, imgOffsets, 0); env->ReleaseIntArrayElements(strides, imgStrides, 0); delete strm; return result; } /////////////////////////////////////////////////////////////////////////////// static const JNINativeMethod gYuvImageMethods[] = { { "nativeCompressToJpeg", "([BIII[I[IILjava/io/OutputStream;[B)Z", (void*)YuvImage_compressToJpeg } }; int register_android_graphics_YuvImage(JNIEnv* env) { return android::RegisterMethodsOrDie(env, "android/graphics/YuvImage", gYuvImageMethods, NELEM(gYuvImageMethods)); }