diff options
-rw-r--r-- | Android.mk | 9 | ||||
-rwxr-xr-x | jni/Android.mk | 18 | ||||
-rw-r--r-- | jni/jpegutil.cpp | 309 | ||||
-rw-r--r-- | jni/jpegutil.h | 99 | ||||
-rw-r--r-- | jni/jpegutilnative.cpp | 59 | ||||
-rw-r--r-- | project.properties | 2 | ||||
-rw-r--r-- | src/com/android/camera/exif/ExifData.java | 6 | ||||
-rw-r--r-- | src/com/android/camera/one/v2/OneCameraImpl.java | 66 | ||||
-rw-r--r-- | src/com/android/camera/util/JpegUtilNative.java | 122 |
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; + } +} |