summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Android.mk9
-rwxr-xr-xjni/Android.mk18
-rw-r--r--jni/jpegutil.cpp309
-rw-r--r--jni/jpegutil.h99
-rw-r--r--jni/jpegutilnative.cpp59
-rw-r--r--project.properties2
-rw-r--r--src/com/android/camera/exif/ExifData.java6
-rw-r--r--src/com/android/camera/one/v2/OneCameraImpl.java66
-rw-r--r--src/com/android/camera/util/JpegUtilNative.java122
9 files changed, 658 insertions, 32 deletions
diff --git a/Android.mk b/Android.mk
index ca6d26a50..8a50335ed 100644
--- a/Android.mk
+++ b/Android.mk
@@ -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)
diff --git a/jni/Android.mk b/jni/Android.mk
index 603ce5fd0..0f95c9b9d 100755
--- a/jni/Android.mk
+++ b/jni/Android.mk
@@ -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
index 000000000..9bb65af4a
--- /dev/null
+++ b/jni/jpegutil.cpp
@@ -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
index 000000000..6082898f7
--- /dev/null
+++ b/jni/jpegutil.h
@@ -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
index 000000000..cd5a2b384
--- /dev/null
+++ b/jni/jpegutilnative.cpp
@@ -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);
+}
diff --git a/project.properties b/project.properties
index 4ab125693..2f095e129 100644
--- a/project.properties
+++ b/project.properties
@@ -11,4 +11,4 @@
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
# Project target.
-target=android-19
+target=android-L
diff --git a/src/com/android/camera/exif/ExifData.java b/src/com/android/camera/exif/ExifData.java
index 5a4d01918..be9d4d20b 100644
--- a/src/com/android/camera/exif/ExifData.java
+++ b/src/com/android/camera/exif/ExifData.java
@@ -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;
}
diff --git a/src/com/android/camera/one/v2/OneCameraImpl.java b/src/com/android/camera/one/v2/OneCameraImpl.java
index fb14c9f15..1d6753f08 100644
--- a/src/com/android/camera/one/v2/OneCameraImpl.java
+++ b/src/com/android/camera/one/v2/OneCameraImpl.java
@@ -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
index 000000000..62ac99b61
--- /dev/null
+++ b/src/com/android/camera/util/JpegUtilNative.java
@@ -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;
+ }
+}