OSDN Git Service

Implemented NDK libjpeg wrapper for YUV420 encoding.
authorPuneet Lall <puneetl@google.com>
Fri, 8 Aug 2014 00:05:38 +0000 (17:05 -0700)
committerPuneet Lall <puneetl@google.com>
Mon, 11 Aug 2014 20:02:23 +0000 (13:02 -0700)
Bug: 16676082
Change-Id: I588b8023b401d5e713762563c9265ec3d774a678

Android.mk
jni/Android.mk
jni/jpegutil.cpp [new file with mode: 0644]
jni/jpegutil.h [new file with mode: 0644]
jni/jpegutilnative.cpp [new file with mode: 0644]
project.properties
src/com/android/camera/exif/ExifData.java
src/com/android/camera/one/v2/OneCameraImpl.java
src/com/android/camera/util/JpegUtilNative.java [new file with mode: 0644]

index ca6d26a..8a50335 100644 (file)
@@ -30,14 +30,7 @@ LOCAL_SDK_VERSION := current
 
 LOCAL_PROGUARD_FLAG_FILES := proguard.flags
 
-# If this is an unbundled build (to install seprately) then include
-# the libraries in the APK, otherwise just put them in /system/lib and
-# leave them out of the APK
-ifneq (,$(TARGET_BUILD_APPS))
-  LOCAL_JNI_SHARED_LIBRARIES := libjni_tinyplanet
-else
-  LOCAL_REQUIRED_MODULES := libjni_tinyplanet
-endif
+LOCAL_JNI_SHARED_LIBRARIES := libjni_tinyplanet libjni_jpegutil
 
 include $(BUILD_PACKAGE)
 
index 603ce5f..0f95c9b 100755 (executable)
@@ -14,3 +14,21 @@ LOCAL_ARM_MODE := arm
 
 include $(BUILD_SHARED_LIBRARY)
 
+# JpegUtil
+include $(CLEAR_VARS)
+
+LOCAL_CFLAGS := -std=c++11
+LOCAL_NDK_STL_VARIANT := c++_static
+LOCAL_LDFLAGS   := -llog -ldl
+LOCAL_SDK_VERSION := 9
+LOCAL_MODULE    := libjni_jpegutil
+LOCAL_SRC_FILES := jpegutil.cpp jpegutilnative.cpp
+
+LOCAL_C_INCLUDES += external/jpeg
+
+LOCAL_SHARED_LIBRARIES := libjpeg
+
+LOCAL_CFLAGS    += -ffast-math -O3 -funroll-loops
+LOCAL_ARM_MODE := arm
+
+include $(BUILD_SHARED_LIBRARY)
\ No newline at end of file
diff --git a/jni/jpegutil.cpp b/jni/jpegutil.cpp
new file mode 100644 (file)
index 0000000..9bb65af
--- /dev/null
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "jpegutil.h"
+#include <memory.h>
+#include <array>
+#include <vector>
+#include <cstring>
+#include <cstdio>
+
+#include <setjmp.h>
+
+extern "C" {
+#include "jpeglib.h"
+}
+
+using namespace std;
+
+template <typename T>
+void safeDelete(T& t) {
+  if (t != nullptr) {
+    delete t;
+    t = nullptr;
+  }
+}
+
+template <typename T>
+void safeDeleteArray(T& t) {
+  if (t != nullptr) {
+    delete[] t;
+    t = nullptr;
+  }
+}
+
+jpegutil::Plane::RowIterator::RowIterator(const Plane* plane) : plane_(plane) {
+  // We must be able to supply up to 8 * 2 lines at a time to libjpeg.
+  // 8 = vertical size of blocks transformed with DCT.
+  // 2 = scaling factor for Y vs UV planes.
+  bufRowCount_ = 16;
+
+  // Rows must be padded to the next multiple of 16
+  // TODO OPTIMIZE Cb and Cr components only need to be padded to a multiple of
+  // 8.
+  rowPadding_ = (16 - (plane_->planeWidth_ % 16)) % 16;
+  bufRowStride_ = plane_->planeWidth_ + rowPadding_;
+
+  // Round up to the nearest multiple of 64 for cache alignment
+  bufRowStride_ = (bufRowStride_ + 63) & ~63;
+
+  // Allocate an extra 64 bytes to allow for cache alignment
+  size_t bufSize = bufRowStride_ * bufRowCount_ + 64;
+
+  // TODO OPTIMIZE if the underlying data has a pixel-stride of 1, and an image
+  // width which is a multiple of 16, we can avoid this allocation and simply
+  // return pointers into the underlying data in operator()(int) instead of
+  // copying the data.
+  buffer_ = unique_ptr<unsigned char[]>(new unsigned char[bufSize]);
+
+  // Find the start of the 64-byte aligned buffer we allocated.
+  size_t bufStart = reinterpret_cast<size_t>(&buffer_[0]);
+  size_t alignedBufStart = (bufStart + 63) & ~63;
+  alignedBuffer_ = reinterpret_cast<unsigned char*>(alignedBufStart);
+
+  bufCurRow_ = 0;
+}
+
+unsigned char* jpegutil::Plane::RowIterator::operator()(int y) {
+  unsigned char* bufCurRowPtr = alignedBuffer_ + bufRowStride_ * bufCurRow_;
+
+  unsigned char* srcPtr = &plane_->data_[y * plane_->rowStride_];
+  unsigned char* dstPtr = bufCurRowPtr;
+
+  // Use memcpy when possible.
+  if (plane_->pixelStride_ == 1) {
+    memcpy(dstPtr, srcPtr, plane_->planeWidth_);
+  } else {
+    int pixelStride = plane_->pixelStride_;
+
+    for (int i = 0; i < plane_->planeWidth_; i++) {
+      *dstPtr = *srcPtr;
+
+      srcPtr += pixelStride;
+      dstPtr++;
+    }
+  }
+
+  // Add padding to the right side by replicating the rightmost column of
+  // (actual) image values into the padding bytes.
+  memset(&bufCurRowPtr[plane_->planeWidth_],
+         bufCurRowPtr[plane_->planeWidth_ - 1], rowPadding_);
+
+  bufCurRow_++;
+  // Wrap within ring buffer.
+  bufCurRow_ %= bufRowCount_;
+
+  return bufCurRowPtr;
+}
+
+jpegutil::Plane::Plane(int imgWidth, int imgHeight, int planeWidth,
+                       int planeHeight, unsigned char* data, int pixelStride,
+                       int rowStride)
+    : imgWidth_(imgWidth),
+      imgHeight_(imgHeight),
+      planeWidth_(planeWidth),
+      planeHeight_(planeHeight),
+      data_(data),
+      rowStride_(rowStride),
+      pixelStride_(pixelStride) {}
+
+int jpegutil::compress(const Plane& yPlane, const Plane& cbPlane,
+                       const Plane& crPlane, unsigned char* outBuf,
+                       size_t outBufCapacity, std::function<void(size_t)> flush,
+                       int quality) {
+  int imgWidth = yPlane.imgWidth();
+  int imgHeight = yPlane.imgHeight();
+
+  // libjpeg requires the use of setjmp/longjmp to recover from errors.  Since
+  // this doesn't play well with RAII, we must use pointers and manually call
+  // delete. See POSIX documentation for longjmp() for details on why the
+  // volatile keyword is necessary.
+  volatile jpeg_compress_struct cinfov;
+
+  jpeg_compress_struct& cinfo =
+      *const_cast<struct jpeg_compress_struct*>(&cinfov);
+
+  JSAMPROW* volatile yArr = nullptr;
+  JSAMPROW* volatile cbArr = nullptr;
+  JSAMPROW* volatile crArr = nullptr;
+
+  Plane::RowIterator* volatile yRowGenerator = nullptr;
+  Plane::RowIterator* volatile cbRowGenerator = nullptr;
+  Plane::RowIterator* volatile crRowGenerator = nullptr;
+
+  JSAMPARRAY imgArr[3];
+
+  // Error handling
+
+  struct my_error_mgr {
+    struct jpeg_error_mgr pub;
+    jmp_buf setjmp_buffer;
+  } err;
+
+  cinfo.err = jpeg_std_error(&err.pub);
+
+  // Default error_exit will call exit(), so override
+  // to return control via setjmp/longjmp.
+  err.pub.error_exit = [](j_common_ptr cinfo) {
+    my_error_mgr* myerr = reinterpret_cast<my_error_mgr*>(cinfo->err);
+
+    (*cinfo->err->output_message)(cinfo);
+
+    // Return control to the setjmp point (see call to setjmp()).
+    longjmp(myerr->setjmp_buffer, 1);
+  };
+
+  cinfo.err = (struct jpeg_error_mgr*)&err;
+
+  // Set the setjmp point to return to in case of error.
+  if (setjmp(err.setjmp_buffer)) {
+    // If libjpeg hits an error, control will jump to this point (see call to
+    // longjmp()).
+    jpeg_destroy_compress(&cinfo);
+
+    safeDeleteArray(yArr);
+    safeDeleteArray(cbArr);
+    safeDeleteArray(crArr);
+    safeDelete(yRowGenerator);
+    safeDelete(cbRowGenerator);
+    safeDelete(crRowGenerator);
+
+    return -1;
+  }
+
+  // Create jpeg compression context
+  jpeg_create_compress(&cinfo);
+
+  // Stores data needed by our c-style callbacks into libjpeg
+  struct ClientData {
+    unsigned char* outBuf;
+    size_t outBufCapacity;
+    std::function<void(size_t)> flush;
+    int totalOutputBytes;
+  } clientData{outBuf, outBufCapacity, flush, 0};
+
+  cinfo.client_data = &clientData;
+
+  // Initialize destination manager
+  jpeg_destination_mgr dest;
+
+  dest.init_destination = [](j_compress_ptr cinfo) {
+    ClientData& cdata = *reinterpret_cast<ClientData*>(cinfo->client_data);
+
+    cinfo->dest->next_output_byte = cdata.outBuf;
+    cinfo->dest->free_in_buffer = cdata.outBufCapacity;
+  };
+
+  dest.empty_output_buffer = [](j_compress_ptr cinfo) -> boolean {
+    ClientData& cdata = *reinterpret_cast<ClientData*>(cinfo->client_data);
+
+    size_t numBytesInBuffer = cdata.outBufCapacity;
+    cdata.flush(numBytesInBuffer);
+    cdata.totalOutputBytes += numBytesInBuffer;
+
+    // Reset the buffer
+    cinfo->dest->next_output_byte = cdata.outBuf;
+    cinfo->dest->free_in_buffer = cdata.outBufCapacity;
+
+    return true;
+  };
+
+  dest.term_destination = [](j_compress_ptr cinfo) {
+    // do nothing to terminate the output buffer
+  };
+
+  cinfo.dest = &dest;
+
+  // Set jpeg parameters
+  cinfo.image_width = imgWidth;
+  cinfo.image_height = imgHeight;
+  cinfo.input_components = 3;
+
+  // Set defaults based on the above values
+  jpeg_set_defaults(&cinfo);
+
+  jpeg_set_quality(&cinfo, quality, true);
+
+  cinfo.dct_method = JDCT_IFAST;
+
+  cinfo.raw_data_in = true;
+
+  jpeg_set_colorspace(&cinfo, JCS_YCbCr);
+
+  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;
+
+  jpeg_start_compress(&cinfo, true);
+
+  yArr = new JSAMPROW[cinfo.comp_info[0].v_samp_factor * DCTSIZE];
+  cbArr = new JSAMPROW[cinfo.comp_info[1].v_samp_factor * DCTSIZE];
+  crArr = new JSAMPROW[cinfo.comp_info[2].v_samp_factor * DCTSIZE];
+
+  imgArr[0] = const_cast<JSAMPARRAY>(yArr);
+  imgArr[1] = const_cast<JSAMPARRAY>(cbArr);
+  imgArr[2] = const_cast<JSAMPARRAY>(crArr);
+
+  yRowGenerator = new Plane::RowIterator(&yPlane);
+  cbRowGenerator = new Plane::RowIterator(&cbPlane);
+  crRowGenerator = new Plane::RowIterator(&crPlane);
+
+  Plane::RowIterator& yRG = *const_cast<Plane::RowIterator*>(yRowGenerator);
+  Plane::RowIterator& cbRG = *const_cast<Plane::RowIterator*>(cbRowGenerator);
+  Plane::RowIterator& crRG = *const_cast<Plane::RowIterator*>(crRowGenerator);
+
+  for (int y = 0; y < imgHeight; y += DCTSIZE * 2) {
+    for (int row = 0; row < DCTSIZE * 2; row++) {
+      yArr[row] = yRG(y + row);
+    }
+
+    for (int row = 0; row < DCTSIZE; row++) {
+      // The y-index within the subsampled chroma planes to send to libjpeg.
+      const int chY = y / 2 + row;
+
+      if (chY < imgHeight / 2) {
+        cbArr[row] = cbRG(chY);
+        crArr[row] = crRG(chY);
+      } else {
+        // When we have run out of rows in the chroma planes to compress, send
+        // the last row as padding.
+        cbArr[row] = cbRG(imgHeight / 2 - 1);
+        crArr[row] = crRG(imgHeight / 2 - 1);
+      }
+    }
+
+    jpeg_write_raw_data(&cinfo, imgArr, DCTSIZE * 2);
+  }
+
+  jpeg_finish_compress(&cinfo);
+
+  int numBytesInBuffer = cinfo.dest->next_output_byte - outBuf;
+
+  flush(numBytesInBuffer);
+
+  clientData.totalOutputBytes += numBytesInBuffer;
+
+  safeDeleteArray(yArr);
+  safeDeleteArray(cbArr);
+  safeDeleteArray(crArr);
+  safeDelete(yRowGenerator);
+  safeDelete(cbRowGenerator);
+  safeDelete(crRowGenerator);
+
+  return clientData.totalOutputBytes;
+}
diff --git a/jni/jpegutil.h b/jni/jpegutil.h
new file mode 100644 (file)
index 0000000..6082898
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <memory>
+#include <functional>
+
+/*
+ * Provides a wrapper around libjpeg.
+ */
+namespace jpegutil {
+
+/**
+ * Represents a model for accessing pixel data for a single plane of an image.
+ * Note that the actual data is not owned by this class, and the underlying
+ * data does not need to be stored in separate planes.
+ */
+class Plane {
+ public:
+  /**
+   * Provides access to several rows of planar data at a time, copied into an
+   * intermediate buffer with pixel data packed in contiguous rows which can be
+   * passed to libjpeg.
+   */
+  class RowIterator {
+   public:
+    RowIterator(const Plane* plane);
+
+    /**
+     * Retrieves the y-th row, copying it into a buffer with as much padding
+     * as is necessary for use with libjpeg.
+     */
+    unsigned char* operator()(int y);
+
+   private:
+    const Plane* plane_;
+
+    // Stores a ring-buffer of cache-aligned buffers for storing contiguous
+    // pixel data for rows of the image to be sent to libjpeg.
+    std::unique_ptr<unsigned char[]> buffer_;
+    // The cache-aligned start index of buffer_
+    unsigned char* alignedBuffer_;
+    // The total number of rows in the ring-buffer
+    int bufRowCount_;
+    // The current ring-buffer row being used
+    int bufCurRow_;
+    // The number of bytes between consecutive rows in the buffer
+    int bufRowStride_;
+
+    // The number of bytes of padding-pixels which must be appended to each row
+    // to reach the multiple of 16-bytes required by libjpeg.
+    int rowPadding_;
+  };
+
+  Plane(int imgWidth, int imgHeight, int planeWidth, int planeHeight,
+        unsigned char* data, int pixelStride, int rowStride);
+
+  int imgWidth() const { return imgWidth_; }
+  int imgHeight() const { return imgHeight_; }
+
+ private:
+  // The dimensions of the entire image
+  int imgWidth_;
+  int imgHeight_;
+  // The dimensions of this plane of the image
+  int planeWidth_;
+  int planeHeight_;
+
+  // A pointer to raw pixel data
+  unsigned char* data_;
+  // The difference in address between the start of consecutive rows
+  int rowStride_;
+  // The difference in address between consecutive pixels in the same row
+  int pixelStride_;
+};
+
+/**
+ * Compresses an image from YUV 420p to JPEG. Output is buffered in outBuf until
+ * capacity is reached, at which point flush(size_t) is called to write
+ * out the specified number of bytes from outBuf.  Returns the number of bytes
+ * written, or -1 in case of an error.
+ */
+int compress(const Plane& yPlane, const Plane& cbPlane, const Plane& crPlane,
+             unsigned char* outBuf, size_t outBufCapacity,
+             std::function<void(size_t)> flush, int quality);
+}
diff --git a/jni/jpegutilnative.cpp b/jni/jpegutilnative.cpp
new file mode 100644 (file)
index 0000000..cd5a2b3
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <jni.h>
+#include <math.h>
+#include <android/bitmap.h>
+
+#include "jpegutil.h"
+
+/**
+ * @param env the JNI environment
+ * @param yBuf the buffer containing the Y component of the image
+ * @param yPStride the stride between adjacent pixels in the same row in yBuf
+ * @param yRStride the stride between adjacent rows in yBuf
+ * @param cbBuf the buffer containing the Cb component of the image
+ * @param cbPStride the stride between adjacent pixels in the same row in cbBuf
+ * @param cbRStride the stride between adjacent rows in cbBuf
+ * @param crBuf the buffer containing the Cr component of the image
+ * @param crPStride the stride between adjacent pixels in the same row in crBuf
+ * @param crRStride the stride between adjacent rows in crBuf
+ */
+extern "C" JNIEXPORT jint JNICALL
+    Java_com_android_camera_util_JpegUtilNative_compressJpegFromYUV420pNative(
+        JNIEnv* env, jclass clazz, jint width, jint height, jobject yBuf,
+        jint yPStride, jint yRStride, jobject cbBuf, jint cbPStride,
+        jint cbRStride, jobject crBuf, jint crPStride, jint crRStride,
+        jobject outBuf, jint outBufCapacity, jint quality) {
+  jbyte* y = (jbyte*)env->GetDirectBufferAddress(yBuf);
+  jbyte* cb = (jbyte*)env->GetDirectBufferAddress(cbBuf);
+  jbyte* cr = (jbyte*)env->GetDirectBufferAddress(crBuf);
+  jbyte* out = (jbyte*)env->GetDirectBufferAddress(outBuf);
+
+  jpegutil::Plane yP(width, height, width, height, (unsigned char*)y, yPStride,
+                     yRStride);
+  jpegutil::Plane cbP(width, height, width / 2, height / 2, (unsigned char*)cb,
+                      cbPStride, cbRStride);
+  jpegutil::Plane crP(width, height, width / 2, height / 2, (unsigned char*)cr,
+                      crPStride, crRStride);
+
+  auto flush = [](size_t numBytes) {
+    // do nothing
+  };
+
+  return jpegutil::compress(yP, cbP, crP, (unsigned char*)out, outBufCapacity,
+                            flush, quality);
+}
index 4ab1256..2f095e1 100644 (file)
@@ -11,4 +11,4 @@
 #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
 
 # Project target.
-target=android-19
+target=android-L
index 5a4d019..be9d4d2 100644 (file)
@@ -249,8 +249,7 @@ class ExifData {
     }
 
     /**
-     * Returns a list of all {@link ExifTag}s in the ExifData or null if there
-     * are none.
+     * Returns a list of all {@link ExifTag}s in the ExifData.
      */
     protected List<ExifTag> getAllTags() {
         ArrayList<ExifTag> ret = new ArrayList<ExifTag>();
@@ -264,9 +263,6 @@ class ExifData {
                 }
             }
         }
-        if (ret.size() == 0) {
-            return null;
-        }
         return ret;
     }
 
index fb14c9f..1d6753f 100644 (file)
@@ -49,8 +49,9 @@ import com.android.camera.one.AbstractOneCamera;
 import com.android.camera.one.OneCamera;
 import com.android.camera.one.OneCamera.PhotoCaptureParameters.Flash;
 import com.android.camera.session.CaptureSession;
-import com.android.camera.util.Size;
 import com.android.camera.util.CameraUtil;
+import com.android.camera.util.JpegUtilNative;
+import com.android.camera.util.Size;
 import com.android.camera.util.SystemProperties;
 
 import java.io.IOException;
@@ -87,6 +88,13 @@ public class OneCameraImpl extends AbstractOneCamera {
     /** Default JPEG encoding quality. */
     private static final Byte JPEG_QUALITY = 90;
 
+    /**
+     * Set to ImageFormat.JPEG, to use the hardware encoder, or
+     * ImageFormat.YUV_420_888 to use the software encoder.
+     * No other image formats are supported.
+     */
+    private static final int sCaptureImageFormat = ImageFormat.YUV_420_888;
+
     /** Width and height of touch metering region as fraction of longest edge. */
     private static final float METERING_REGION_EDGE = 0.1f;
     /** Metering region weight between 0 and 1. */
@@ -166,7 +174,6 @@ public class OneCameraImpl extends AbstractOneCamera {
                     super.onCaptureCompleted(session, request, result);
                 }
             };
-
     /** Thread on which the camera operations are running. */
     private final HandlerThread mCameraThread;
     /** Handler of the {@link #mCameraThread}. */
@@ -196,9 +203,9 @@ public class OneCameraImpl extends AbstractOneCamera {
     /** A callback that is called when the device is fully closed. */
     private CloseCallback mCloseCallback = null;
 
-    /** Receives the normal JPEG captured images. */
-    private final ImageReader mJpegImageReader;
-    ImageReader.OnImageAvailableListener mJpegImageListener =
+    /** Receives the normal captured images. */
+    private final ImageReader mCaptureImageReader;
+    ImageReader.OnImageAvailableListener mCaptureImageListener =
             new ImageReader.OnImageAvailableListener() {
                 @Override
                 public void onImageAvailable(ImageReader reader) {
@@ -229,9 +236,9 @@ public class OneCameraImpl extends AbstractOneCamera {
         mCameraThread.start();
         mCameraHandler = new Handler(mCameraThread.getLooper());
 
-        mJpegImageReader = ImageReader.newInstance(pictureSize.getWidth(), pictureSize.getHeight(),
-                ImageFormat.JPEG, 2);
-        mJpegImageReader.setOnImageAvailableListener(mJpegImageListener, mCameraHandler);
+        mCaptureImageReader = ImageReader.newInstance(pictureSize.getWidth(), pictureSize.getHeight(),
+                sCaptureImageFormat, 2);
+        mCaptureImageReader.setOnImageAvailableListener(mCaptureImageListener, mCameraHandler);
         Log.d(TAG, "New Camera2 based OneCameraImpl created.");
     }
 
@@ -280,6 +287,7 @@ public class OneCameraImpl extends AbstractOneCamera {
             // JPEG capture.
             CaptureRequest.Builder builder = mDevice
                     .createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
+
             // TODO: Check that these control modes are correct for AWB, AE.
             if (mLastRequestedControlAFMode == CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_PICTURE) {
                 builder.set(CaptureRequest.CONTROL_AF_MODE,
@@ -292,15 +300,19 @@ public class OneCameraImpl extends AbstractOneCamera {
                 Log.v(TAG, "CaptureRequest with AUTO.");
             }
             builder.setTag(RequestTag.CAPTURE);
-            builder.set(CaptureRequest.JPEG_QUALITY, JPEG_QUALITY);
-            builder.set(CaptureRequest.JPEG_ORIENTATION, getJpegRotation(params.orientation));
+
+            if (sCaptureImageFormat == ImageFormat.JPEG) {
+                builder.set(CaptureRequest.JPEG_QUALITY, JPEG_QUALITY);
+                builder.set(CaptureRequest.JPEG_ORIENTATION, getJpegRotation(params.orientation));
+            }
+
             builder.addTarget(mPreviewSurface);
-            builder.addTarget(mJpegImageReader.getSurface());
+            builder.addTarget(mCaptureImageReader.getSurface());
             applyFlashMode(params.flashMode, builder);
             CaptureRequest request = builder.build();
             mCaptureSession.capture(request, mAutoFocusStateListener, mCameraHandler);
         } catch (CameraAccessException e) {
-            Log.e(TAG, "Could not access camera for JPEG capture.");
+            Log.e(TAG, "Could not access camera for still image capture.");
             params.callback.onPictureTakenFailed();
             return;
         }
@@ -349,7 +361,7 @@ public class OneCameraImpl extends AbstractOneCamera {
     public Size[] getSupportedSizes() {
         StreamConfigurationMap config = mCharacteristics
                 .get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
-        return Size.convert(config.getOutputSizes(ImageFormat.JPEG));
+        return Size.convert(config.getOutputSizes(sCaptureImageFormat));
     }
 
     @Override
@@ -442,7 +454,7 @@ public class OneCameraImpl extends AbstractOneCamera {
             }
             List<Surface> outputSurfaces = new ArrayList<Surface>(2);
             outputSurfaces.add(previewSurface);
-            outputSurfaces.add(mJpegImageReader.getSurface());
+            outputSurfaces.add(mCaptureImageReader.getSurface());
 
             mDevice.createCaptureSession(outputSurfaces, new CameraCaptureSession.StateListener() {
 
@@ -659,8 +671,8 @@ public class OneCameraImpl extends AbstractOneCamera {
         x1 = CameraUtil.clamp(x1, 0, sensor.width() - 1);
         y0 = CameraUtil.clamp(y0, 0, sensor.height() - 1);
         y1 = CameraUtil.clamp(y1, 0, sensor.height() - 1);
-        int wt = (int) ((1 - METERING_REGION_WEIGHT) * (float) MeteringRectangle.METERING_WEIGHT_MIN
-                + METERING_REGION_WEIGHT * (float) MeteringRectangle.METERING_WEIGHT_MAX);
+        int wt = (int) ((1 - METERING_REGION_WEIGHT) * MeteringRectangle.METERING_WEIGHT_MIN
+                + METERING_REGION_WEIGHT * MeteringRectangle.METERING_WEIGHT_MAX);
 
         Log.v(TAG, "sensor 3A @ x0=" + x0 + " y0=" + y0 + " dx=" + (x1 - x0) + " dy=" + (y1 - y0));
         MeteringRectangle[] regions = new MeteringRectangle[]{
@@ -690,8 +702,26 @@ public class OneCameraImpl extends AbstractOneCamera {
      */
     private static byte[] acquireJpegBytesAndClose(ImageReader reader) {
         Image img = reader.acquireLatestImage();
-        Image.Plane plane0 = img.getPlanes()[0];
-        ByteBuffer buffer = plane0.getBuffer();
+
+        ByteBuffer buffer;
+
+        if (img.getFormat() == ImageFormat.JPEG) {
+            Image.Plane plane0 = img.getPlanes()[0];
+            buffer = plane0.getBuffer();
+        } else if (img.getFormat() == ImageFormat.YUV_420_888) {
+            buffer = ByteBuffer.allocateDirect(img.getWidth() * img.getHeight() * 3);
+
+            Log.v(TAG, "Compressing JPEG with software encoder.");
+            int numBytes = JpegUtilNative.compressJpegFromYUV420Image(img, buffer, JPEG_QUALITY);
+
+            if (numBytes < 0) {
+                throw new RuntimeException("Error compressing jpeg.");
+            }
+
+            buffer.limit(numBytes);
+        } else {
+            throw new RuntimeException("Unsupported image format.");
+        }
 
         byte[] imageBytes = new byte[buffer.remaining()];
         buffer.get(imageBytes);
diff --git a/src/com/android/camera/util/JpegUtilNative.java b/src/com/android/camera/util/JpegUtilNative.java
new file mode 100644 (file)
index 0000000..62ac99b
--- /dev/null
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.camera.util;
+
+import android.graphics.ImageFormat;
+import android.media.Image;
+import android.media.Image.Plane;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Provides direct access to libjpeg-turbo via the NDK.
+ */
+public class JpegUtilNative {
+    static {
+        System.loadLibrary("jni_jpegutil");
+    }
+
+    public static final int ERROR_OUT_BUF_TOO_SMALL = -1;
+
+    /**
+     * Compresses an image from YUV422 format to jpeg.
+     *
+     * @param yBuf the buffer containing the Y component of the image
+     * @param yPStride the stride between adjacent pixels in the same row in yBuf
+     * @param yRStride the stride between adjacent rows in yBuf
+     * @param cbBuf the buffer containing the Cb component of the image
+     * @param cbPStride the stride between adjacent pixels in the same row in cbBuf
+     * @param cbRStride the stride between adjacent rows in cbBuf
+     * @param crBuf the buffer containing the Cr component of the image
+     * @param crPStride the stride between adjacent pixels in the same row in crBuf
+     * @param crRStride the stride between adjacent rows in crBuf
+     * @param quality the quality level (0 to 100) to use
+     * @return The number of bytes written, or a negative value indicating an error
+     */
+    private static native int compressJpegFromYUV420pNative(
+            int width, int height,
+            Object yBuf, int yPStride, int yRStride,
+            Object cbBuf, int cbPStride, int cbRStride,
+            Object crBuf, int crPStride, int crRStride,
+            Object outBuf, int outBufCapacity, int quality);
+
+    /**
+     * @see JpegUtilNative#compressJpegFromYUV420pNative(int, int, java.lang.Object, int, int,
+     *      java.lang.Object, int, int, java.lang.Object, int, int, java.lang.Object, int, int)
+     */
+    public static int compressJpegFromYUV420p(
+            int width, int height,
+            ByteBuffer yBuf, int yPStride, int yRStride,
+            ByteBuffer cbBuf, int cbPStride, int cbRStride,
+            ByteBuffer crBuf, int crPStride, int crRStride,
+            ByteBuffer outBuf, int quality) {
+        return compressJpegFromYUV420pNative(width, height, yBuf, yPStride, yRStride, cbBuf,
+                cbPStride, cbRStride, crBuf, crPStride, crRStride, outBuf, outBuf.capacity(), quality);
+    }
+
+    /**
+     * Compresses the given image to jpeg. Note that only ImageFormat.YUV_420_888 is currently
+     * supported. Furthermore, all planes must use direct byte buffers.
+     *
+     * @param img the image to compress
+     * @param outBuf a direct byte buffer to hold the output jpeg.
+     * @return The number of bytes written to outBuf
+     */
+    public static int compressJpegFromYUV420Image(Image img, ByteBuffer outBuf, int quality) {
+        if (img.getFormat() != ImageFormat.YUV_420_888) {
+            throw new RuntimeException("Unsupported Image Format.");
+        }
+
+        final int NUM_PLANES = 3;
+
+        if (img.getPlanes().length != NUM_PLANES) {
+            throw new RuntimeException("Output buffer must be direct.");
+        }
+
+        if (!outBuf.isDirect()) {
+            throw new RuntimeException("Output buffer must be direct.");
+        }
+
+        ByteBuffer[] planeBuf = new ByteBuffer[NUM_PLANES];
+        int[] pixelStride = new int[NUM_PLANES];
+        int[] rowStride = new int[NUM_PLANES];
+
+        for (int i = 0; i < NUM_PLANES; i++) {
+            Plane plane = img.getPlanes()[i];
+
+            if (!plane.getBuffer().isDirect()) {
+                return -1;
+            }
+
+            planeBuf[i] = plane.getBuffer();
+            pixelStride[i] = plane.getPixelStride();
+            rowStride[i] = plane.getRowStride();
+        }
+
+        outBuf.clear();
+
+        int numBytesWritten = compressJpegFromYUV420p(
+                img.getWidth(), img.getHeight(),
+                planeBuf[0], pixelStride[0], rowStride[0],
+                planeBuf[1], pixelStride[1], rowStride[1],
+                planeBuf[2], pixelStride[2], rowStride[2],
+                outBuf, quality);
+
+        outBuf.limit(numBytesWritten);
+
+        return numBytesWritten;
+    }
+}