camera.vsoc.jpeg doesn't use VNDK libraries.
camera.vsoc.jpeg was using the following two VNDK libs:
- libandroid_runtime
- libhwui
This CL:
- removes ImageMetadata
- adds Exif/Thumbnail/JpegStub/Compressor from the current
goldfish implementation
Bug: 71707530
Test: taking a photo works
Change-Id: Ib23613f242c750d8d2bbac28d04ff01da94d4325
(cherry picked from commit d6c77d61e1ac68611175b5c783835d103f8bd97b)
diff --git a/guest/hals/camera/Android.mk b/guest/hals/camera/Android.mk
index 705c933..1f690e7 100644
--- a/guest/hals/camera/Android.mk
+++ b/guest/hals/camera/Android.mk
@@ -28,6 +28,7 @@
emulator_camera_shared_libraries := \
libbase \
libbinder \
+ libexif \
liblog \
libutils \
libcutils \
@@ -49,6 +50,10 @@
emulator_camera_static_libraries += libjsoncpp
endif
+emulator_camera_static_libraries += \
+ android.hardware.camera.common@1.0-helper \
+ libyuv
+
emulator_camera_c_includes := \
device/google/cuttlefish_common \
frameworks/native/include/media/hardware \
@@ -63,6 +68,8 @@
EmulatedCameraDevice.cpp \
EmulatedFakeCamera.cpp \
EmulatedFakeCameraDevice.cpp \
+ Exif.cpp \
+ Thumbnail.cpp \
Converters.cpp \
PreviewWindow.cpp \
CallbackNotifier.cpp \
@@ -86,7 +93,7 @@
enable_emulated_camera3 = $(shell test $(PLATFORM_SDK_VERSION) -ge 23 && echo yes)
enable_emulated_camera2 = $(shell test $(PLATFORM_SDK_VERSION) -ge 19 && echo yes)
-# Emulated camera - goldfish / vbox_x86 build###################################
+# Emulated camera - cuttlefish / vbox_x86 build###################################
#
ifeq (0, $(shell test $(PLATFORM_SDK_VERSION) -ge 24; echo $$?))
emulator_camera_c_includes += external/libjpeg-turbo
@@ -128,56 +135,38 @@
-Wall \
-Werror
-ifeq (0, $(shell test $(PLATFORM_SDK_VERSION) -lt 27 ; echo $$?))
-GCE_HWUI_LIB:=libskia
-GCE_HWUI_INCLUDE:=external/skia/include/core
-else ifeq (0, $(shell test $(PLATFORM_SDK_VERSION) -eq 27 -a $(PLATFORM_PREVIEW_SDK_VERSION) -eq 0; echo $$?))
-GCE_HWUI_LIB:=libskia
-GCE_HWUI_INCLUDE:=external/skia/include/core
-else
-GCE_HWUI_LIB:=libhwui
-GCE_HWUI_INCLUDE:=
-endif
-
jpeg_shared_libraries := \
libcutils \
+ libexif \
liblog \
- $(GCE_HWUI_LIB) \
libjpeg \
- libandroid_runtime \
- cuttlefish_auto_resources
-jpeg_static_libraries := libyuv_static
-jpeg_c_includes := \
- device/google/cuttlefish_common \
- external/libyuv/files/include \
- $(GCE_HWUI_INCLUDE) \
- frameworks/base/core/jni/android/graphics \
- frameworks/native/include
-jpeg_src := \
- JpegStub.cpp \
- ExifMetadataBuilder.cpp
-# JPEG stub - goldfish build####################################################
-
+jpeg_c_includes := external/libexif \
+ frameworks/native/include
ifeq (0, $(shell test $(PLATFORM_SDK_VERSION) -ge 24; echo $$?))
jpeg_c_includes += external/libjpeg-turbo
else
jpeg_c_includes += external/jpeg
endif
-ifeq (0, $(shell test $(PLATFORM_SDK_VERSION) -ge 21; echo $$?))
-LOCAL_MODULE_RELATIVE_PATH := ${emulator_camera_module_relative_path}
-else
-LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/${emulator_camera_module_relative_path}
-endif
+jpeg_src := \
+ Compressor.cpp \
+ JpegStub.cpp \
+
+# JPEG stub - cuttlefish build####################################################
+
ifeq (true, $(TARGET_TRANSLATE_2ND_ARCH))
LOCAL_MULTILIB := first
endif
+
+LOCAL_C_INCLUDES += ${jpeg_c_includes}
+LOCAL_SRC_FILES := ${jpeg_src}
+
+LOCAL_MODULE_RELATIVE_PATH := ${jpeg_module_relative_path}
LOCAL_CFLAGS += ${jpeg_cflags}
-LOCAL_CLANG_CFLAGS += ${jpeg_clangflags}
LOCAL_SHARED_LIBRARIES := ${jpeg_shared_libraries}
-LOCAL_STATIC_LIBRARIES := ${jpeg_static_libraries}
+
LOCAL_C_INCLUDES += ${jpeg_c_includes}
LOCAL_SRC_FILES := ${jpeg_src}
@@ -186,4 +175,3 @@
LOCAL_VENDOR_MODULE := true
include $(BUILD_SHARED_LIBRARY)
-
diff --git a/guest/hals/camera/CallbackNotifier.cpp b/guest/hals/camera/CallbackNotifier.cpp
index 0c98936..dc67773 100644
--- a/guest/hals/camera/CallbackNotifier.cpp
+++ b/guest/hals/camera/CallbackNotifier.cpp
@@ -25,8 +25,9 @@
#include <MetadataBufferType.h>
#include <cutils/log.h>
#include "EmulatedCameraDevice.h"
-#include "ImageMetadata.h"
#include "JpegCompressor.h"
+#include "Exif.h"
+#include "Thumbnail.h"
namespace android {
@@ -237,27 +238,49 @@
/* Compress the frame to JPEG. Note that when taking pictures, we
* have requested camera device to provide us with NV21 frames. */
NV21JpegCompressor compressor;
- struct ::ImageMetadata meta;
- status_t res = camera_dev->getImageMetadata(&meta);
- if (res == NO_ERROR) {
- res = compressor.compressRawImage(frame, &meta, mJpegQuality);
- if (res == NO_ERROR) {
- camera_memory_t* jpeg_buff =
- mGetMemoryCB(-1, compressor.getCompressedSize(), 1, mCBOpaque);
- if (NULL != jpeg_buff && NULL != jpeg_buff->data) {
- compressor.getCompressedImage(jpeg_buff->data);
- mDataCB(CAMERA_MSG_COMPRESSED_IMAGE, jpeg_buff, 0, NULL, mCBOpaque);
- jpeg_buff->release(jpeg_buff);
- } else {
- ALOGE("%s: Memory failure in CAMERA_MSG_VIDEO_FRAME", __FUNCTION__);
+ const CameraParameters* cameraParameters = camera_dev->getCameraParameters();
+ if (cameraParameters == nullptr) {
+ ALOGE("%s: Could not get camera parameters to take picture.", __FUNCTION__);
+ return;
+ }
+
+ ExifData* exifData = createExifData(*cameraParameters);
+
+ // Create a thumbnail and place the pointer and size in the EXIF
+ // data structure. This transfers ownership to the EXIF data and
+ // the memory will be deallocated in the freeExifData call below.
+ int width = camera_dev->getFrameWidth();
+ int height = camera_dev->getFrameHeight();
+ int thumbWidth = cameraParameters->getInt(
+ CameraParameters::KEY_JPEG_THUMBNAIL_WIDTH);
+ int thumbHeight = cameraParameters->getInt(
+ CameraParameters::KEY_JPEG_THUMBNAIL_HEIGHT);
+ if (thumbWidth > 0 && thumbHeight > 0) {
+ if (!createThumbnail(static_cast<const unsigned char*>(frame),
+ width, height, thumbWidth, thumbHeight,
+ mJpegQuality, exifData)) {
+ // Not really a fatal error, we'll just keep going
+ ALOGE("%s: Failed to create thumbnail for image",
+ __FUNCTION__);
}
+ }
+
+ status_t res = compressor.compressRawImage(frame, exifData, mJpegQuality, width, height);
+ if (res == NO_ERROR) {
+ camera_memory_t* jpeg_buff =
+ mGetMemoryCB(-1, compressor.getCompressedSize(), 1, mCBOpaque);
+ if (NULL != jpeg_buff && NULL != jpeg_buff->data) {
+ compressor.getCompressedImage(jpeg_buff->data);
+ mDataCB(CAMERA_MSG_COMPRESSED_IMAGE, jpeg_buff, 0, NULL, mCBOpaque);
+ jpeg_buff->release(jpeg_buff);
} else {
- ALOGE("%s: Compression failure in CAMERA_MSG_VIDEO_FRAME",
- __FUNCTION__);
+ ALOGE("%s: Memory failure in CAMERA_MSG_VIDEO_FRAME", __FUNCTION__);
}
} else {
- ALOGE("%s: Image Metadata acquisition failure.", __FUNCTION__);
+ ALOGE("%s: Compression failure in CAMERA_MSG_VIDEO_FRAME",
+ __FUNCTION__);
}
+ freeExifData(exifData);
}
}
}
diff --git a/guest/hals/camera/Compressor.cpp b/guest/hals/camera/Compressor.cpp
new file mode 100644
index 0000000..76c4a24
--- /dev/null
+++ b/guest/hals/camera/Compressor.cpp
@@ -0,0 +1,234 @@
+/*
+* Copyright (C) 2016 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 "Compressor.h"
+
+#define LOG_NDEBUG 0
+#define LOG_TAG "EmulatedCamera_JPEGStub_Compressor"
+#include <cutils/log.h>
+#include <libexif/exif-data.h>
+
+Compressor::Compressor() {
+
+}
+
+bool Compressor::compress(const unsigned char* data,
+ int width, int height, int quality,
+ ExifData* exifData) {
+ if (!configureCompressor(width, height, quality)) {
+ // The method will have logged a more detailed error message than we can
+ // provide here so just return.
+ return false;
+ }
+
+ return compressData(data, exifData);
+}
+
+const std::vector<uint8_t>& Compressor::getCompressedData() const {
+ return mDestManager.mBuffer;
+}
+
+bool Compressor::configureCompressor(int width, int height, int quality) {
+ mCompressInfo.err = jpeg_std_error(&mErrorManager);
+ // NOTE! DANGER! Do not construct any non-trivial objects below setjmp!
+ // The compiler will not generate code to destroy them during the return
+ // below so they will leak. Additionally, do not place any calls to libjpeg
+ // that can fail above this line or any error will cause undefined behavior.
+ if (setjmp(mErrorManager.mJumpBuffer)) {
+ // This is where the error handler will jump in case setup fails
+ // The error manager will ALOG an appropriate error message
+ return false;
+ }
+
+ jpeg_create_compress(&mCompressInfo);
+
+ mCompressInfo.image_width = width;
+ mCompressInfo.image_height = height;
+ mCompressInfo.input_components = 3;
+ mCompressInfo.in_color_space = JCS_YCbCr;
+ jpeg_set_defaults(&mCompressInfo);
+
+ jpeg_set_quality(&mCompressInfo, quality, TRUE);
+ // It may seem weird to set color space here again but this will also set
+ // other fields. These fields might be overwritten by jpeg_set_defaults
+ jpeg_set_colorspace(&mCompressInfo, JCS_YCbCr);
+ mCompressInfo.raw_data_in = TRUE;
+ mCompressInfo.dct_method = JDCT_IFAST;
+ // Set sampling factors
+ mCompressInfo.comp_info[0].h_samp_factor = 2;
+ mCompressInfo.comp_info[0].v_samp_factor = 2;
+ mCompressInfo.comp_info[1].h_samp_factor = 1;
+ mCompressInfo.comp_info[1].v_samp_factor = 1;
+ mCompressInfo.comp_info[2].h_samp_factor = 1;
+ mCompressInfo.comp_info[2].v_samp_factor = 1;
+
+ mCompressInfo.dest = &mDestManager;
+
+ return true;
+}
+
+static void deinterleave(const uint8_t* vuPlanar, std::vector<uint8_t>& uRows,
+ std::vector<uint8_t>& vRows, int rowIndex, int width,
+ int height, int stride) {
+ int numRows = (height - rowIndex) / 2;
+ if (numRows > 8) numRows = 8;
+ for (int row = 0; row < numRows; ++row) {
+ int offset = ((rowIndex >> 1) + row) * stride;
+ const 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;
+ }
+ }
+}
+
+
+bool Compressor::compressData(const unsigned char* data, ExifData* exifData) {
+ const uint8_t* y[16];
+ const uint8_t* cb[8];
+ const uint8_t* cr[8];
+ const uint8_t** planes[3] = { y, cb, cr };
+
+ int i, offset;
+ int width = mCompressInfo.image_width;
+ int height = mCompressInfo.image_height;
+ const uint8_t* yPlanar = data;
+ const uint8_t* vuPlanar = data + (width * height);
+ std::vector<uint8_t> uRows(8 * (width >> 1));
+ std::vector<uint8_t> vRows(8 * (width >> 1));
+
+ // NOTE! DANGER! Do not construct any non-trivial objects below setjmp!
+ // The compiler will not generate code to destroy them during the return
+ // below so they will leak. Additionally, do not place any calls to libjpeg
+ // that can fail above this line or any error will cause undefined behavior.
+ if (setjmp(mErrorManager.mJumpBuffer)) {
+ // This is where the error handler will jump in case compression fails
+ // The error manager will ALOG an appropriate error message
+ return false;
+ }
+
+ jpeg_start_compress(&mCompressInfo, TRUE);
+
+ attachExifData(exifData);
+
+ // process 16 lines of Y and 8 lines of U/V each time.
+ while (mCompressInfo.next_scanline < mCompressInfo.image_height) {
+ //deinterleave u and v
+ deinterleave(vuPlanar, uRows, vRows, mCompressInfo.next_scanline,
+ width, height, width);
+
+ // Jpeg library ignores the rows whose indices are greater than height.
+ for (i = 0; i < 16; i++) {
+ // y row
+ y[i] = yPlanar + (mCompressInfo.next_scanline + i) * width;
+
+ // construct u row and v row
+ if ((i & 1) == 0) {
+ // height and width are both halved because of downsampling
+ offset = (i >> 1) * (width >> 1);
+ cb[i/2] = &uRows[offset];
+ cr[i/2] = &vRows[offset];
+ }
+ }
+ jpeg_write_raw_data(&mCompressInfo, const_cast<JSAMPIMAGE>(planes), 16);
+ }
+
+ jpeg_finish_compress(&mCompressInfo);
+ jpeg_destroy_compress(&mCompressInfo);
+
+ return true;
+}
+
+bool Compressor::attachExifData(ExifData* exifData) {
+ if (exifData == nullptr) {
+ // This is not an error, we don't require EXIF data
+ return true;
+ }
+
+ // Save the EXIF data to memory
+ unsigned char* rawData = nullptr;
+ unsigned int size = 0;
+ exif_data_save_data(exifData, &rawData, &size);
+ if (rawData == nullptr) {
+ ALOGE("Failed to create EXIF data block");
+ return false;
+ }
+
+ jpeg_write_marker(&mCompressInfo, JPEG_APP0 + 1, rawData, size);
+ free(rawData);
+ return true;
+}
+
+Compressor::ErrorManager::ErrorManager() {
+ error_exit = &onJpegError;
+}
+
+void Compressor::ErrorManager::onJpegError(j_common_ptr cinfo) {
+ // NOTE! Do not construct any non-trivial objects in this method at the top
+ // scope. Their destructors will not be called. If you do need such an
+ // object create a local scope that does not include the longjmp call,
+ // that ensures the object is destroyed before longjmp is called.
+ ErrorManager* errorManager = reinterpret_cast<ErrorManager*>(cinfo->err);
+
+ // Format and log error message
+ char errorMessage[JMSG_LENGTH_MAX];
+ (*errorManager->format_message)(cinfo, errorMessage);
+ errorMessage[sizeof(errorMessage) - 1] = '\0';
+ ALOGE("JPEG compression error: %s", errorMessage);
+ jpeg_destroy(cinfo);
+
+ // And through the looking glass we go
+ longjmp(errorManager->mJumpBuffer, 1);
+}
+
+Compressor::DestinationManager::DestinationManager() {
+ init_destination = &initDestination;
+ empty_output_buffer = &emptyOutputBuffer;
+ term_destination = &termDestination;
+}
+
+void Compressor::DestinationManager::initDestination(j_compress_ptr cinfo) {
+ auto manager = reinterpret_cast<DestinationManager*>(cinfo->dest);
+
+ // Start out with some arbitrary but not too large buffer size
+ manager->mBuffer.resize(16 * 1024);
+ manager->next_output_byte = &manager->mBuffer[0];
+ manager->free_in_buffer = manager->mBuffer.size();
+}
+
+boolean Compressor::DestinationManager::emptyOutputBuffer(
+ j_compress_ptr cinfo) {
+ auto manager = reinterpret_cast<DestinationManager*>(cinfo->dest);
+
+ // Keep doubling the size of the buffer for a very low, amortized
+ // performance cost of the allocations
+ size_t oldSize = manager->mBuffer.size();
+ manager->mBuffer.resize(oldSize * 2);
+ manager->next_output_byte = &manager->mBuffer[oldSize];
+ manager->free_in_buffer = manager->mBuffer.size() - oldSize;
+ return manager->free_in_buffer != 0;
+}
+
+void Compressor::DestinationManager::termDestination(j_compress_ptr cinfo) {
+ auto manager = reinterpret_cast<DestinationManager*>(cinfo->dest);
+
+ // Resize down to the exact size of the output, that is remove as many
+ // bytes as there are left in the buffer
+ manager->mBuffer.resize(manager->mBuffer.size() - manager->free_in_buffer);
+}
+
diff --git a/guest/hals/camera/Compressor.h b/guest/hals/camera/Compressor.h
new file mode 100644
index 0000000..10f5e80
--- /dev/null
+++ b/guest/hals/camera/Compressor.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#ifndef CUTTLEFISH_CAMERA_JPEG_STUB_COMPRESSOR_H
+#define CUTTLEFISH_CAMERA_JPEG_STUB_COMPRESSOR_H
+
+#include <setjmp.h>
+#include <stdlib.h>
+extern "C" {
+#include <jpeglib.h>
+#include <jerror.h>
+}
+
+#include <vector>
+
+struct _ExifData;
+typedef _ExifData ExifData;
+
+class Compressor {
+public:
+ Compressor();
+
+ /* Compress |data| which represents raw NV21 encoded data of dimensions
+ * |width| * |height|. |exifData| is optional EXIF data that will be
+ * attached to the compressed data if present, set to null if not needed.
+ */
+ bool compress(const unsigned char* data,
+ int width, int height, int quality,
+ ExifData* exifData);
+
+ /* Get a reference to the compressed data, this will return an empty vector
+ * if compress has not been called yet
+ */
+ const std::vector<unsigned char>& getCompressedData() const;
+
+private:
+ struct DestinationManager : jpeg_destination_mgr {
+ DestinationManager();
+
+ static void initDestination(j_compress_ptr cinfo);
+ static boolean emptyOutputBuffer(j_compress_ptr cinfo);
+ static void termDestination(j_compress_ptr cinfo);
+
+ std::vector<unsigned char> mBuffer;
+ };
+ struct ErrorManager : jpeg_error_mgr {
+ ErrorManager();
+
+ static void onJpegError(j_common_ptr cinfo);
+
+ jmp_buf mJumpBuffer;
+ };
+
+ jpeg_compress_struct mCompressInfo;
+ DestinationManager mDestManager;
+ ErrorManager mErrorManager;
+
+ bool configureCompressor(int width, int height, int quality);
+ bool compressData(const unsigned char* data, ExifData* exifData);
+ bool attachExifData(ExifData* exifData);
+};
+
+#endif // CUTTLEFISH_CAMERA_JPEG_STUB_COMPRESSOR_H
+
diff --git a/guest/hals/camera/EmulatedBaseCamera.h b/guest/hals/camera/EmulatedBaseCamera.h
index a59ae23..740f334 100644
--- a/guest/hals/camera/EmulatedBaseCamera.h
+++ b/guest/hals/camera/EmulatedBaseCamera.h
@@ -20,8 +20,10 @@
#include <hardware/camera_common.h>
#include <utils/Errors.h>
#include "CameraConfiguration.h"
-#include "ImageMetadata.h"
#include "guest/libs/platform_support/api_level_fixes.h"
+#include <CameraParameters.h>
+
+using ::android::hardware::camera::common::V1_0::helper::CameraParameters;
namespace android {
@@ -96,10 +98,12 @@
*/
virtual status_t getCameraInfo(struct camera_info* info) = 0;
- /* Gets image metadata.
+ /* Gets camera parameters.
* This method is called to collect metadata for (currently) taken picture.
*/
- virtual status_t getImageMetadata(struct ImageMetadata* meta) = 0;
+ virtual const CameraParameters* getCameraParameters() {
+ return NULL;
+ }
/* Set torch mode.
* This method is called in response to camera_module_t::set_torch_mode
diff --git a/guest/hals/camera/EmulatedCamera.cpp b/guest/hals/camera/EmulatedCamera.cpp
index 923b0b4..24d6158 100644
--- a/guest/hals/camera/EmulatedCamera.cpp
+++ b/guest/hals/camera/EmulatedCamera.cpp
@@ -268,24 +268,8 @@
return EmulatedBaseCamera::getCameraInfo(info);
}
-status_t EmulatedCamera::getImageMetadata(struct ImageMetadata* meta) {
- meta->mLensFocalLength =
- mParameters.getFloat(CameraParameters::KEY_FOCAL_LENGTH);
- meta->mGpsLatitude = mParameters.getFloat(CameraParameters::KEY_GPS_LATITUDE);
- meta->mGpsLongitude =
- mParameters.getFloat(CameraParameters::KEY_GPS_LONGITUDE);
- meta->mGpsAltitude = mParameters.getFloat(CameraParameters::KEY_GPS_ALTITUDE);
- meta->mGpsTimestamp = mParameters.getInt(CameraParameters::KEY_GPS_TIMESTAMP);
- meta->mThumbnailWidth =
- mParameters.getInt(CameraParameters::KEY_JPEG_THUMBNAIL_WIDTH);
- meta->mThumbnailHeight =
- mParameters.getInt(CameraParameters::KEY_JPEG_THUMBNAIL_HEIGHT);
- const char* temp =
- mParameters.get(CameraParameters::KEY_GPS_PROCESSING_METHOD);
- if (temp) {
- meta->mGpsProcessingMethod = temp;
- }
- return NO_ERROR;
+const CameraParameters* EmulatedCamera::getCameraParameters() {
+ return &mParameters;
}
status_t EmulatedCamera::setPreviewWindow(struct preview_stream_ops* window) {
diff --git a/guest/hals/camera/EmulatedCamera.h b/guest/hals/camera/EmulatedCamera.h
index 24cd863..9da675d 100644
--- a/guest/hals/camera/EmulatedCamera.h
+++ b/guest/hals/camera/EmulatedCamera.h
@@ -127,7 +127,7 @@
virtual status_t getCameraInfo(struct camera_info* info);
/** Override of base class method */
- virtual status_t getImageMetadata(struct ImageMetadata* meta);
+ virtual const CameraParameters* getCameraParameters() override;
/****************************************************************************
* Camera API implementation.
diff --git a/guest/hals/camera/EmulatedCamera2.h b/guest/hals/camera/EmulatedCamera2.h
index 48b1924..ad8b113 100644
--- a/guest/hals/camera/EmulatedCamera2.h
+++ b/guest/hals/camera/EmulatedCamera2.h
@@ -77,11 +77,6 @@
virtual status_t getCameraInfo(struct camera_info *info) = 0;
- virtual status_t getImageMetadata(struct ImageMetadata * /*meta*/) {
- // TODO(ender): fill in Image metadata structure.
- return ENOSYS;
- }
-
/****************************************************************************
* Camera API implementation.
* These methods are called from the camera API callback routines.
diff --git a/guest/hals/camera/EmulatedCamera3.h b/guest/hals/camera/EmulatedCamera3.h
index e8d2442..92fbb4d 100644
--- a/guest/hals/camera/EmulatedCamera3.h
+++ b/guest/hals/camera/EmulatedCamera3.h
@@ -97,11 +97,6 @@
virtual status_t getCameraInfo(struct camera_info *info);
- virtual status_t getImageMetadata(struct ImageMetadata * /*meta*/) {
- // TODO(ender): fill in Image metadata structure.
- return ENOSYS;
- }
-
/****************************************************************************
* Camera API implementation.
* These methods are called from the camera API callback routines.
diff --git a/guest/hals/camera/EmulatedCameraDevice.cpp b/guest/hals/camera/EmulatedCameraDevice.cpp
index eafbe05..c16f1c8 100644
--- a/guest/hals/camera/EmulatedCameraDevice.cpp
+++ b/guest/hals/camera/EmulatedCameraDevice.cpp
@@ -246,10 +246,8 @@
}
}
-status_t EmulatedCameraDevice::getImageMetadata(struct ImageMetadata* meta) {
- meta->mWidth = mFrameWidth;
- meta->mHeight = mFrameHeight;
- return mCameraHAL->getImageMetadata(meta);
+const CameraParameters* EmulatedCameraDevice::getCameraParameters() {
+ return mCameraHAL->getCameraParameters();
}
/****************************************************************************
diff --git a/guest/hals/camera/EmulatedCameraDevice.h b/guest/hals/camera/EmulatedCameraDevice.h
index ccd786f..ab654eb 100644
--- a/guest/hals/camera/EmulatedCameraDevice.h
+++ b/guest/hals/camera/EmulatedCameraDevice.h
@@ -31,7 +31,10 @@
#include <utils/threads.h>
#include "Converters.h"
#include "EmulatedCameraCommon.h"
-#include "ImageMetadata.h"
+
+#include <CameraParameters.h>
+
+using ::android::hardware::camera::common::V1_0::helper::CameraParameters;
namespace android {
@@ -260,7 +263,7 @@
* Return:
* Filled in ImageMetadata structure (in/out parameter).
*/
- status_t getImageMetadata(struct ::ImageMetadata* meta);
+ const CameraParameters* getCameraParameters();
/*
* State checkers.
diff --git a/guest/hals/camera/Exif.cpp b/guest/hals/camera/Exif.cpp
new file mode 100644
index 0000000..afdcf4e
--- /dev/null
+++ b/guest/hals/camera/Exif.cpp
@@ -0,0 +1,413 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#define LOG_NDEBUG 0
+#define LOG_TAG "EmulatedCamera_Exif"
+#include <cutils/log.h>
+
+#include <inttypes.h>
+#include <math.h>
+#include <stdint.h>
+
+#include <CameraParameters.h>
+
+using ::android::hardware::camera::common::V1_0::helper::CameraParameters;
+using ::android::hardware::camera::common::V1_0::helper::Size;
+
+#include "Exif.h"
+
+#include <libexif/exif-data.h>
+#include <libexif/exif-entry.h>
+#include <libexif/exif-ifd.h>
+#include <libexif/exif-tag.h>
+
+#include <string>
+#include <vector>
+
+// For GPS timestamping we want to ensure we use a 64-bit time_t, 32-bit
+// platforms have time64_t but 64-bit platforms do not.
+#if defined(__LP64__)
+#include <time.h>
+using Timestamp = time_t;
+#define TIMESTAMP_TO_TM(timestamp, tm) gmtime_r(timestamp, tm)
+#else
+#include <time64.h>
+using Timestamp = time64_t;
+#define TIMESTAMP_TO_TM(timestamp, tm) gmtime64_r(timestamp, tm)
+#endif
+
+namespace android {
+
+// A prefix that is used for tags with the "undefined" format to indicate that
+// the contents are ASCII encoded. See the user comment section of the EXIF spec
+// for more details http://www.exif.org/Exif2-2.PDF
+static const unsigned char kAsciiPrefix[] = {
+ 0x41, 0x53, 0x43, 0x49, 0x49, 0x00, 0x00, 0x00 // "ASCII\0\0\0"
+};
+
+// Remove an existing EXIF entry from |exifData| if it exists. This is useful
+// when replacing existing data, it's easier to just remove the data and
+// re-allocate it than to adjust the amount of allocated data.
+static void removeExistingEntry(ExifData* exifData, ExifIfd ifd, int tag) {
+ ExifEntry* entry = exif_content_get_entry(exifData->ifd[ifd],
+ static_cast<ExifTag>(tag));
+ if (entry) {
+ exif_content_remove_entry(exifData->ifd[ifd], entry);
+ }
+}
+
+static ExifEntry* allocateEntry(int tag,
+ ExifFormat format,
+ unsigned int numComponents) {
+ ExifMem* mem = exif_mem_new_default();
+ ExifEntry* entry = exif_entry_new_mem(mem);
+
+ unsigned int size = numComponents * exif_format_get_size(format);
+ entry->data = reinterpret_cast<unsigned char*>(exif_mem_alloc(mem, size));
+ entry->size = size;
+ entry->tag = static_cast<ExifTag>(tag);
+ entry->components = numComponents;
+ entry->format = format;
+
+ exif_mem_unref(mem);
+ return entry;
+}
+
+// Create an entry and place it in |exifData|, the entry is initialized with an
+// array of floats from |values|
+template<size_t N>
+static bool createEntry(ExifData* exifData,
+ ExifIfd ifd,
+ int tag,
+ const float (&values)[N],
+ float denominator = 1000.0) {
+ removeExistingEntry(exifData, ifd, tag);
+ ExifByteOrder byteOrder = exif_data_get_byte_order(exifData);
+ ExifEntry* entry = allocateEntry(tag, EXIF_FORMAT_RATIONAL, N);
+ exif_content_add_entry(exifData->ifd[ifd], entry);
+ unsigned int rationalSize = exif_format_get_size(EXIF_FORMAT_RATIONAL);
+ for (size_t i = 0; i < N; ++i) {
+ ExifRational rational = {
+ static_cast<uint32_t>(values[i] * denominator),
+ static_cast<uint32_t>(denominator)
+ };
+
+ exif_set_rational(&entry->data[i * rationalSize], byteOrder, rational);
+ }
+
+ // Unref entry after changing owner to the ExifData struct
+ exif_entry_unref(entry);
+ return true;
+}
+
+// Create an entry with a single float |value| in it and place it in |exifData|
+static bool createEntry(ExifData* exifData,
+ ExifIfd ifd,
+ int tag,
+ const float value,
+ float denominator = 1000.0) {
+ float values[1] = { value };
+ // Recycling functions is good for the environment
+ return createEntry(exifData, ifd, tag, values, denominator);
+}
+
+// Create an entry and place it in |exifData|, the entry contains the raw data
+// pointed to by |data| of length |size|.
+static bool createEntry(ExifData* exifData,
+ ExifIfd ifd,
+ int tag,
+ const unsigned char* data,
+ size_t size,
+ ExifFormat format = EXIF_FORMAT_UNDEFINED) {
+ removeExistingEntry(exifData, ifd, tag);
+ ExifEntry* entry = allocateEntry(tag, format, size);
+ memcpy(entry->data, data, size);
+ exif_content_add_entry(exifData->ifd[ifd], entry);
+ // Unref entry after changing owner to the ExifData struct
+ exif_entry_unref(entry);
+ return true;
+}
+
+// Create an entry and place it in |exifData|, the entry is initialized with
+// the string provided in |value|
+static bool createEntry(ExifData* exifData,
+ ExifIfd ifd,
+ int tag,
+ const char* value) {
+ unsigned int length = strlen(value) + 1;
+ const unsigned char* data = reinterpret_cast<const unsigned char*>(value);
+ return createEntry(exifData, ifd, tag, data, length, EXIF_FORMAT_ASCII);
+}
+
+// Create an entry and place it in |exifData|, the entry is initialized with a
+// single byte in |value|
+static bool createEntry(ExifData* exifData,
+ ExifIfd ifd,
+ int tag,
+ uint8_t value) {
+ return createEntry(exifData, ifd, tag, &value, 1, EXIF_FORMAT_BYTE);
+}
+
+// Create an entry and place it in |exifData|, the entry is default initialized
+// by the exif library based on |tag|
+static bool createEntry(ExifData* exifData,
+ ExifIfd ifd,
+ int tag) {
+ removeExistingEntry(exifData, ifd, tag);
+ ExifEntry* entry = exif_entry_new();
+ exif_content_add_entry(exifData->ifd[ifd], entry);
+ exif_entry_initialize(entry, static_cast<ExifTag>(tag));
+ // Unref entry after changing owner to the ExifData struct
+ exif_entry_unref(entry);
+ return true;
+}
+
+// Create an entry with a single EXIF LONG (32-bit value) and place it in
+// |exifData|.
+static bool createEntry(ExifData* exifData,
+ ExifIfd ifd,
+ int tag,
+ int value) {
+ removeExistingEntry(exifData, ifd, tag);
+ ExifByteOrder byteOrder = exif_data_get_byte_order(exifData);
+ ExifEntry* entry = allocateEntry(tag, EXIF_FORMAT_LONG, 1);
+ exif_content_add_entry(exifData->ifd[ifd], entry);
+ exif_set_long(entry->data, byteOrder, value);
+
+ // Unref entry after changing owner to the ExifData struct
+ exif_entry_unref(entry);
+ return true;
+}
+
+static bool getCameraParam(const CameraParameters& parameters,
+ const char* parameterKey,
+ const char** outValue) {
+ const char* value = parameters.get(parameterKey);
+ if (value) {
+ *outValue = value;
+ return true;
+ }
+ return false;
+}
+
+static bool getCameraParam(const CameraParameters& parameters,
+ const char* parameterKey,
+ float* outValue) {
+ const char* value = parameters.get(parameterKey);
+ if (value) {
+ *outValue = parameters.getFloat(parameterKey);
+ return true;
+ }
+ return false;
+}
+
+static bool getCameraParam(const CameraParameters& parameters,
+ const char* parameterKey,
+ int64_t* outValue) {
+ const char* value = parameters.get(parameterKey);
+ if (value) {
+ char dummy = 0;
+ // Attempt to scan an extra character and then make sure it was not
+ // scanned by checking that the return value indicates only one item.
+ // This way we fail on any trailing characters
+ if (sscanf(value, "%" SCNd64 "%c", outValue, &dummy) == 1) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// Convert a GPS coordinate represented as a decimal degree value to sexagesimal
+// GPS coordinates comprised of <degrees> <minutes>' <seconds>"
+static void convertGpsCoordinate(float degrees, float (*result)[3]) {
+ float absDegrees = fabs(degrees);
+ // First value is degrees without any decimal digits
+ (*result)[0] = floor(absDegrees);
+
+ // Subtract degrees so we only have the fraction left, then multiply by
+ // 60 to get the minutes
+ float minutes = (absDegrees - (*result)[0]) * 60.0f;
+ (*result)[1] = floor(minutes);
+
+ // Same thing for seconds but here we store seconds with the fraction
+ float seconds = (minutes - (*result)[1]) * 60.0f;
+ (*result)[2] = seconds;
+}
+
+// Convert a UNIX epoch timestamp to a timestamp comprised of three floats for
+// hour, minute and second, and a date part that is represented as a string.
+static bool convertTimestampToTimeAndDate(int64_t timestamp,
+ float (*timeValues)[3],
+ std::string* date) {
+ Timestamp time = timestamp;
+ struct tm utcTime;
+ if (TIMESTAMP_TO_TM(&time, &utcTime) == nullptr) {
+ ALOGE("Could not decompose timestamp into components");
+ return false;
+ }
+ (*timeValues)[0] = utcTime.tm_hour;
+ (*timeValues)[1] = utcTime.tm_min;
+ (*timeValues)[2] = utcTime.tm_sec;
+
+ char buffer[64] = {};
+ if (strftime(buffer, sizeof(buffer), "%Y:%m:%d", &utcTime) == 0) {
+ ALOGE("Could not construct date string from timestamp");
+ return false;
+ }
+ *date = buffer;
+ return true;
+}
+
+ExifData* createExifData(const CameraParameters& params) {
+ ExifData* exifData = exif_data_new();
+
+ exif_data_set_option(exifData, EXIF_DATA_OPTION_FOLLOW_SPECIFICATION);
+ exif_data_set_data_type(exifData, EXIF_DATA_TYPE_COMPRESSED);
+ exif_data_set_byte_order(exifData, EXIF_BYTE_ORDER_INTEL);
+
+ // Create mandatory exif fields and set their default values
+ exif_data_fix(exifData);
+
+ float triplet[3];
+ float floatValue = 0.0f;
+ const char* stringValue;
+ int64_t degrees;
+
+ // Datetime, creating and initializing a datetime tag will automatically
+ // set the current date and time in the tag so just do that.
+ createEntry(exifData, EXIF_IFD_0, EXIF_TAG_DATE_TIME);
+
+ // Make and model
+ createEntry(exifData, EXIF_IFD_0, EXIF_TAG_MAKE, "Emulator-Cuttlefish");
+ createEntry(exifData, EXIF_IFD_0, EXIF_TAG_MODEL, "Emulator-Cuttlefish");
+
+ // Picture size
+ int width = -1, height = -1;
+ params.getPictureSize(&width, &height);
+ if (width >= 0 && height >= 0) {
+ createEntry(exifData, EXIF_IFD_EXIF,
+ EXIF_TAG_PIXEL_X_DIMENSION, width);
+ createEntry(exifData, EXIF_IFD_EXIF,
+ EXIF_TAG_PIXEL_Y_DIMENSION, height);
+ }
+ // Orientation
+ if (getCameraParam(params,
+ CameraParameters::KEY_ROTATION,
+ °rees)) {
+ // Exif orientation values, please refer to
+ // http://www.exif.org/Exif2-2.PDF, Section 4.6.4-A-Orientation
+ // Or these websites:
+ // http://sylvana.net/jpegcrop/exif_orientation.html
+ // http://www.impulseadventure.com/photo/exif-orientation.html
+ enum {
+ EXIF_ROTATE_CAMERA_CW0 = 1,
+ EXIF_ROTATE_CAMERA_CW90 = 6,
+ EXIF_ROTATE_CAMERA_CW180 = 3,
+ EXIF_ROTATE_CAMERA_CW270 = 8,
+ };
+ uint16_t exifOrien = 1;
+ switch (degrees) {
+ case 0:
+ exifOrien = EXIF_ROTATE_CAMERA_CW0;
+ break;
+ case 90:
+ exifOrien = EXIF_ROTATE_CAMERA_CW90;
+ break;
+ case 180:
+ exifOrien = EXIF_ROTATE_CAMERA_CW180;
+ break;
+ case 270:
+ exifOrien = EXIF_ROTATE_CAMERA_CW270;
+ break;
+ }
+ createEntry(exifData, EXIF_IFD_0, EXIF_TAG_ORIENTATION, exifOrien);
+ }
+ // Focal length
+ if (getCameraParam(params,
+ CameraParameters::KEY_FOCAL_LENGTH,
+ &floatValue)) {
+ createEntry(exifData, EXIF_IFD_EXIF, EXIF_TAG_FOCAL_LENGTH, floatValue);
+ }
+ // GPS latitude and reference, reference indicates sign, store unsigned
+ if (getCameraParam(params,
+ CameraParameters::KEY_GPS_LATITUDE,
+ &floatValue)) {
+ convertGpsCoordinate(floatValue, &triplet);
+ createEntry(exifData, EXIF_IFD_GPS, EXIF_TAG_GPS_LATITUDE, triplet);
+
+ const char* ref = floatValue < 0.0f ? "S" : "N";
+ createEntry(exifData, EXIF_IFD_GPS, EXIF_TAG_GPS_LATITUDE_REF, ref);
+ }
+ // GPS longitude and reference, reference indicates sign, store unsigned
+ if (getCameraParam(params,
+ CameraParameters::KEY_GPS_LONGITUDE,
+ &floatValue)) {
+ convertGpsCoordinate(floatValue, &triplet);
+ createEntry(exifData, EXIF_IFD_GPS, EXIF_TAG_GPS_LONGITUDE, triplet);
+
+ const char* ref = floatValue < 0.0f ? "W" : "E";
+ createEntry(exifData, EXIF_IFD_GPS, EXIF_TAG_GPS_LONGITUDE_REF, ref);
+ }
+ // GPS altitude and reference, reference indicates sign, store unsigned
+ if (getCameraParam(params,
+ CameraParameters::KEY_GPS_ALTITUDE,
+ &floatValue)) {
+ createEntry(exifData, EXIF_IFD_GPS, EXIF_TAG_GPS_ALTITUDE,
+ static_cast<float>(fabs(floatValue)));
+
+ // 1 indicated below sea level, 0 indicates above sea level
+ uint8_t ref = floatValue < 0.0f ? 1 : 0;
+ createEntry(exifData, EXIF_IFD_GPS, EXIF_TAG_GPS_ALTITUDE_REF, ref);
+ }
+ // GPS timestamp and datestamp
+ int64_t timestamp = 0;
+ if (getCameraParam(params,
+ CameraParameters::KEY_GPS_TIMESTAMP,
+ ×tamp)) {
+ std::string date;
+ if (convertTimestampToTimeAndDate(timestamp, &triplet, &date)) {
+ createEntry(exifData, EXIF_IFD_GPS, EXIF_TAG_GPS_TIME_STAMP,
+ triplet, 1.0f);
+ createEntry(exifData, EXIF_IFD_GPS, EXIF_TAG_GPS_DATE_STAMP,
+ date.c_str());
+ }
+ }
+
+ // GPS processing method
+ if (getCameraParam(params,
+ CameraParameters::KEY_GPS_PROCESSING_METHOD,
+ &stringValue)) {
+ std::vector<unsigned char> data;
+ // Because this is a tag with an undefined format it has to be prefixed
+ // with the encoding type. Insert an ASCII prefix first, then the
+ // actual string. Undefined tags do not have to be null terminated.
+ data.insert(data.end(),
+ std::begin(kAsciiPrefix),
+ std::end(kAsciiPrefix));
+ data.insert(data.end(), stringValue, stringValue + strlen(stringValue));
+ createEntry(exifData, EXIF_IFD_GPS, EXIF_TAG_GPS_PROCESSING_METHOD,
+ &data[0], data.size());
+ }
+
+ return exifData;
+}
+
+void freeExifData(ExifData* exifData) {
+ exif_data_free(exifData);
+}
+
+} // namespace android
+
diff --git a/guest/hals/camera/Exif.h b/guest/hals/camera/Exif.h
new file mode 100644
index 0000000..b4f0963
--- /dev/null
+++ b/guest/hals/camera/Exif.h
@@ -0,0 +1,41 @@
+
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#ifndef CUTTLEFISH_CAMERA_EXIF_H
+#define CUTTLEFISH_CAMERA_EXIF_H
+
+#include <CameraParameters.h>
+
+struct _ExifData;
+typedef struct _ExifData ExifData;
+
+using ::android::hardware::camera::common::V1_0::helper::CameraParameters;
+
+namespace android {
+
+/* Create an EXIF data structure based on camera parameters. This includes
+ * things like GPS information that has been set by the camera client.
+ */
+ExifData* createExifData(const CameraParameters& parameters);
+
+/* Free EXIF data created in the createExifData call */
+void freeExifData(ExifData* exifData);
+
+} // namespace android
+
+#endif // CUTTLEFISH_CAMERA_EXIF_H
+
diff --git a/guest/hals/camera/ExifMetadataBuilder.cpp b/guest/hals/camera/ExifMetadataBuilder.cpp
deleted file mode 100644
index 17cc75b..0000000
--- a/guest/hals/camera/ExifMetadataBuilder.cpp
+++ /dev/null
@@ -1,515 +0,0 @@
-#include "ExifMetadataBuilder.h"
-
-#define LOG_NDEBUG 0
-#define LOG_TAG "ExifMetadataBuilder"
-#include <cutils/log.h>
-
-#include <stdlib.h>
-#include <cmath>
-
-namespace android {
-// All supported EXIF data types.
-enum ExifDataType {
- ExifUInt8 = 1,
- ExifString = 2,
- ExifUInt16 = 3,
- ExifUInt32 = 4,
- ExifRational = 5,
- ExifUndefined = 7,
- ExifSInt16 = 8,
- ExifSInt32 = 9,
- ExifFloat = 11,
- ExifDouble = 12,
-};
-
-enum ExifTagId {
- kExifTagGpsLatitudeRef = 0x1,
- kExifTagGpsLatitude = 0x2,
- kExifTagGpsLongitudeRef = 0x3,
- kExifTagGpsLongitude = 0x4,
- kExifTagGpsAltitudeRef = 0x5,
- kExifTagGpsAltitude = 0x6,
- kExifTagGpsTimestamp = 0x7,
- kExifTagGpsProcessingMethod = 0x1b,
- kExifTagGpsDatestamp = 0x1d,
- kExifTagImageWidth = 0x100,
- kExifTagImageHeight = 0x101,
- kExifTagImageDateTime = 0x132,
- kExifTagJpegData = 0x201,
- kExifTagJpegLength = 0x202,
- kExifTagCameraSubIFD = 0x8769,
- kExifTagGpsSubIFD = 0x8825,
- kExifTagCameraFocalLength = 0x920a,
-};
-
-const char kExifCharArrayAscii[8] = "ASCII";
-// const char kExifCharArrayUnicode[8] = "UNICODE";
-
-// Structure of an individual EXIF tag.
-struct ExifTagInfo {
- uint16_t tag; // Describes unique tag type.
- uint16_t type; // Describes data type (see ExifDataType).
- uint32_t count; // Number of data elements.
- uint32_t value; // Value (for shorter items) or offset (for longer).
-};
-
-// Interface describing individual EXIF tag.
-class ExifTag {
- public:
- virtual ~ExifTag() {}
- virtual size_t DataSize() { return 0; }
- virtual void AppendTag(ExifTagInfo* info, size_t data_offset) = 0;
- virtual void AppendData(uint8_t* /*target*/) {}
-};
-
-// EXIF structure.
-// This structure describes all types of tags:
-// - Main image,
-// - Thumbnail image,
-// - Sub-IFDs.
-class ExifStructure {
- public:
- typedef std::vector<ExifTag*> TagMap;
-
- ExifStructure() {}
-
- ~ExifStructure() {
- for (TagMap::iterator it = mTags.begin(); it != mTags.end(); ++it) {
- delete *it;
- }
- }
-
- size_t TagSize() {
- // Target tag structure:
- // - uint16_t: mTags.size();
- // - ExifTagInfo[mTags.size()]
- // - uint32_t: next_structure_available ? self_offset + Size() : NULL
- return sizeof(uint16_t) // mTags.size()
- + mTags.size() * sizeof(ExifTagInfo) // [tags]
- + sizeof(uint32_t); // next offset
- }
-
- size_t DataSize() {
- size_t data_size = 0;
- for (TagMap::iterator it = mTags.begin(); it != mTags.end(); ++it) {
- data_size += (*it)->DataSize();
- }
- return data_size;
- }
-
- size_t Size() { return TagSize() + DataSize(); }
-
- uint32_t Build(uint8_t* buffer, const uint32_t self_offset,
- const bool next_structure_available) {
- // Write number of items.
- uint16_t num_elements = mTags.size();
- memcpy(buffer, &num_elements, sizeof(num_elements));
- buffer += sizeof(num_elements);
-
- // Offset describes where tag data will be placed.
- uint32_t offset = self_offset + TagSize();
-
- // Combine EXIF for main image.
- for (TagMap::iterator it = mTags.begin(); it != mTags.end(); ++it) {
- // Each tag is exactly 12 bytes long, but data length can be anything.
- // We supply the data offset to anyone who wants to use data.
- (*it)->AppendTag(reinterpret_cast<ExifTagInfo*>(buffer), offset);
- offset += (*it)->DataSize();
- buffer += sizeof(ExifTagInfo);
- }
-
- // Append information about the second tag offset.
- // |offset| holds exactly the right position:
- // self_offset + TagSize() + DataSize().
- uint32_t next_tag_offset = next_structure_available ? offset : 0;
- memcpy(buffer, &next_tag_offset, sizeof(next_tag_offset));
- buffer += sizeof(next_tag_offset);
-
- // Combine EXIF data for main image.
- for (TagMap::iterator it = mTags.begin(); it != mTags.end(); ++it) {
- (*it)->AppendData(buffer);
- buffer += (*it)->DataSize();
- }
-
- return offset;
- }
-
- void PushTag(ExifTag* tag) { mTags.push_back(tag); }
-
- private:
- TagMap mTags;
-};
-
-// EXIF tags.
-namespace {
-// Tag with 8-bit unsigned integer.
-class ExifUInt8Tag : public ExifTag {
- public:
- ExifUInt8Tag(uint16_t tag, size_t value) : mTag(tag), mValue(value) {}
-
- void AppendTag(ExifTagInfo* info, size_t /*data_offset*/) {
- info->tag = mTag;
- info->type = ExifUInt8;
- info->count = 1;
- info->value = mValue << 24;
- }
-
- private:
- uint16_t mTag;
- uint32_t mValue;
-};
-
-// Tag with 32-bit unsigned integer.
-class ExifUInt32Tag : public ExifTag {
- public:
- ExifUInt32Tag(uint16_t tag, size_t value) : mTag(tag), mValue(value) {}
-
- void AppendTag(ExifTagInfo* info, size_t /*data_offset*/) {
- info->tag = mTag;
- info->type = ExifUInt32;
- info->count = 1;
- info->value = mValue;
- }
-
- private:
- uint16_t mTag;
- uint32_t mValue;
-};
-
-// Char array tag.
-class ExifCharArrayTag : public ExifTag {
- public:
- ExifCharArrayTag(uint16_t tag, const char (&type)[8], const std::string& str)
- : mTag(tag), mType(type), mString(str) {}
-
- void AppendTag(ExifTagInfo* info, size_t data_offset) {
- info->tag = mTag;
- info->type = ExifUndefined;
- info->count = DataSize();
- info->value = data_offset;
- }
-
- size_t DataSize() { return sizeof(mType) + mString.size(); }
-
- void AppendData(uint8_t* data) {
- memcpy(data, mType, sizeof(mType));
- data += sizeof(mType);
- memcpy(data, mString.data(), mString.size());
- }
-
- private:
- uint16_t mTag;
- const char (&mType)[8];
- std::string mString;
-};
-
-// Data tag; writes LONG (pointer) and appends data.
-class ExifPointerTag : public ExifTag {
- public:
- ExifPointerTag(uint16_t tag, void* data, int size)
- : mTag(tag), mData(data), mSize(size) {}
-
- ~ExifPointerTag() { free(mData); }
-
- void AppendTag(ExifTagInfo* info, size_t data_offset) {
- info->tag = mTag;
- info->type = ExifUInt32;
- info->count = 1;
- info->value = data_offset;
- }
-
- size_t DataSize() { return mSize; }
-
- void AppendData(uint8_t* data) { memcpy(data, mData, mSize); }
-
- private:
- uint16_t mTag;
- void* mData;
- int mSize;
-};
-
-// String tag.
-class ExifStringTag : public ExifTag {
- public:
- ExifStringTag(uint16_t tag, const std::string& str)
- : mTag(tag), mString(str) {}
-
- void AppendTag(ExifTagInfo* info, size_t data_offset) {
- info->tag = mTag;
- info->type = ExifString;
- info->count = DataSize();
- info->value = data_offset;
- }
-
- size_t DataSize() {
- // Include padding \0.
- return mString.size() + 1;
- }
-
- void AppendData(uint8_t* data) { memcpy(data, mString.data(), DataSize()); }
-
- private:
- uint16_t mTag;
- std::string mString;
-};
-
-// SubIFD: sub-tags.
-class ExifSubIfdTag : public ExifTag {
- public:
- ExifSubIfdTag(uint16_t tag) : mTag(tag), mSubStructure(new ExifStructure) {}
-
- ~ExifSubIfdTag() { delete mSubStructure; }
-
- void AppendTag(ExifTagInfo* info, size_t data_offset) {
- info->tag = mTag;
- info->type = ExifUInt32;
- info->count = 1;
- info->value = data_offset;
- mDataOffset = data_offset;
- }
-
- size_t DataSize() { return mSubStructure->Size(); }
-
- void AppendData(uint8_t* data) {
- mSubStructure->Build(data, mDataOffset, false);
- }
-
- ExifStructure* GetSubStructure() { return mSubStructure; }
-
- private:
- uint16_t mTag;
- ExifStructure* mSubStructure;
- size_t mDataOffset;
-};
-
-// Unsigned rational tag.
-class ExifURationalTag : public ExifTag {
- public:
- ExifURationalTag(uint16_t tag, double value) : mTag(tag), mCount(1) {
- DoubleToRational(value, &mRationals[0].mNumerator,
- &mRationals[0].mDenominator);
- }
-
- ExifURationalTag(uint16_t tag, double value1, double value2, double value3)
- : mTag(tag), mCount(3) {
- DoubleToRational(value1, &mRationals[0].mNumerator,
- &mRationals[0].mDenominator);
- DoubleToRational(value2, &mRationals[1].mNumerator,
- &mRationals[1].mDenominator);
- DoubleToRational(value3, &mRationals[2].mNumerator,
- &mRationals[2].mDenominator);
- }
-
- void DoubleToRational(double value, int32_t* numerator,
- int32_t* denominator) {
- int sign = 1;
- if (value < 0) {
- sign = -sign;
- value = -value;
- }
- // Take shortcuts. Plenty.
- *numerator = value;
- *denominator = 1;
-
- // Set some (arbitrary) threshold beyond which we do not proceed.
- while (*numerator < (1 << 24)) {
- if (std::fabs((*numerator / *denominator) - value) <= 0.0001) break;
- *denominator *= 10;
- *numerator = value * (*denominator);
- }
- *numerator *= sign;
- }
-
- void AppendTag(ExifTagInfo* info, size_t data_offset) {
- info->tag = mTag;
- info->type = ExifRational;
- info->count = mCount;
- info->value = data_offset;
- }
-
- size_t DataSize() { return sizeof(mRationals[0]) * mCount; }
-
- void AppendData(uint8_t* data) { memcpy(data, &mRationals[0], DataSize()); }
-
- private:
- static const int kMaxSupportedRationals = 3;
- uint16_t mTag;
- uint16_t mCount;
- struct {
- int32_t mNumerator;
- int32_t mDenominator;
- } mRationals[kMaxSupportedRationals];
-};
-
-std::string ToAsciiDate(time_t time) {
- struct tm loc;
- char res[12]; // YYYY:MM:DD\0\0
- localtime_r(&time, &loc);
- strftime(res, sizeof(res), "%Y:%m:%d", &loc);
- return res;
-}
-
-std::string ToAsciiTime(time_t time) {
- struct tm loc;
- char res[10]; // HH:MM:SS\0\0
- localtime_r(&time, &loc);
- strftime(res, sizeof(res), "%H:%M:%S", &loc);
- return res;
-}
-
-} // namespace
-
-ExifMetadataBuilder::ExifMetadataBuilder()
- : mImageIfd(new ExifStructure), mThumbnailIfd(new ExifStructure) {
- // Mandatory tag: camera details.
- ExifSubIfdTag* sub_ifd = new ExifSubIfdTag(kExifTagCameraSubIFD);
- // Pass ownership to mImageIfd.
- mImageIfd->PushTag(sub_ifd);
- mCameraSubIfd = sub_ifd->GetSubStructure();
-
- // Optional (yet, required) tag: GPS data.
- sub_ifd = new ExifSubIfdTag(kExifTagGpsSubIFD);
- // Pass ownership to mImageIfd.
- mImageIfd->PushTag(sub_ifd);
- mGpsSubIfd = sub_ifd->GetSubStructure();
-}
-
-ExifMetadataBuilder::~ExifMetadataBuilder() {
- delete mImageIfd;
- delete mThumbnailIfd;
-}
-
-void ExifMetadataBuilder::SetWidth(int width) {
- mImageIfd->PushTag(new ExifUInt32Tag(kExifTagImageWidth, width));
-}
-
-void ExifMetadataBuilder::SetHeight(int height) {
- mImageIfd->PushTag(new ExifUInt32Tag(kExifTagImageHeight, height));
-}
-
-void ExifMetadataBuilder::SetThumbnailWidth(int width) {
- mThumbnailIfd->PushTag(new ExifUInt32Tag(kExifTagImageWidth, width));
-}
-
-void ExifMetadataBuilder::SetThumbnailHeight(int height) {
- mThumbnailIfd->PushTag(new ExifUInt32Tag(kExifTagImageHeight, height));
-}
-
-void ExifMetadataBuilder::SetThumbnail(void* thumbnail, int size) {
- mThumbnailIfd->PushTag(new ExifUInt32Tag(kExifTagJpegLength, size));
- mThumbnailIfd->PushTag(new ExifPointerTag(kExifTagJpegData, thumbnail, size));
-}
-
-void ExifMetadataBuilder::SetDateTime(time_t date_time) {
- std::string res = ToAsciiDate(date_time) + " " + ToAsciiTime(date_time);
- mImageIfd->PushTag(new ExifStringTag(kExifTagImageDateTime, res));
-}
-
-void ExifMetadataBuilder::SetGpsLatitude(double latitude) {
- if (latitude < 0) {
- mGpsSubIfd->PushTag(new ExifStringTag(kExifTagGpsLatitudeRef, "S"));
- } else {
- mGpsSubIfd->PushTag(new ExifStringTag(kExifTagGpsLatitudeRef, "N"));
- }
- int degrees = latitude;
- latitude = (latitude - degrees) * 60.;
- int minutes = latitude;
- latitude = (latitude - minutes) * 60.;
- double seconds = latitude;
- mGpsSubIfd->PushTag(
- new ExifURationalTag(kExifTagGpsLatitude, degrees, minutes, seconds));
-}
-
-void ExifMetadataBuilder::SetGpsLongitude(double longitude) {
- if (longitude < 0) {
- mGpsSubIfd->PushTag(new ExifStringTag(kExifTagGpsLongitudeRef, "W"));
- } else {
- mGpsSubIfd->PushTag(new ExifStringTag(kExifTagGpsLongitudeRef, "E"));
- }
- int32_t degrees = longitude;
- longitude = (longitude - degrees) * 60.;
- int32_t minutes = longitude;
- longitude = (longitude - minutes) * 60.;
- double seconds = longitude;
- mGpsSubIfd->PushTag(
- new ExifURationalTag(kExifTagGpsLongitude, degrees, minutes, seconds));
-}
-
-void ExifMetadataBuilder::SetGpsAltitude(double altitude) {
- mGpsSubIfd->PushTag(new ExifUInt8Tag(kExifTagGpsAltitudeRef, 0));
- mGpsSubIfd->PushTag(new ExifURationalTag(kExifTagGpsAltitude, altitude));
-}
-
-void ExifMetadataBuilder::SetGpsProcessingMethod(const std::string& method) {
- mGpsSubIfd->PushTag(new ExifCharArrayTag(kExifTagGpsProcessingMethod,
- kExifCharArrayAscii, method));
-}
-
-void ExifMetadataBuilder::SetGpsDateTime(time_t timestamp) {
- std::string date = ToAsciiDate(timestamp);
- int32_t seconds = (timestamp % 60);
- timestamp /= 60;
- int32_t minutes = (timestamp % 60);
- timestamp /= 60;
- mGpsSubIfd->PushTag(new ExifURationalTag(kExifTagGpsTimestamp, timestamp % 24,
- minutes, seconds));
- mGpsSubIfd->PushTag(new ExifStringTag(kExifTagGpsDatestamp, date));
-}
-
-void ExifMetadataBuilder::SetLensFocalLength(double length) {
- mCameraSubIfd->PushTag(
- new ExifURationalTag(kExifTagCameraFocalLength, length));
-}
-
-void ExifMetadataBuilder::Build() {
- const uint8_t exif_header[] = {
- 'E', 'x', 'i', 'f', 0, 0, // EXIF header.
- };
-
- const uint8_t tiff_header[] = {
- 'I', 'I', 0x2a, 0x00, // TIFF Little endian header.
- };
-
- // EXIF data should be exactly this much.
- size_t exif_size = sizeof(exif_header) + sizeof(tiff_header) +
- sizeof(uint32_t) + // Offset of the following descriptors.
- mImageIfd->Size() + mThumbnailIfd->Size();
-
- const uint8_t marker[] = {
- 0xff,
- 0xd8,
- 0xff,
- 0xe1, // EXIF marker.
- uint8_t((exif_size + 2) >>
- 8), // Data length (including the length field)
- uint8_t((exif_size + 2) & 0xff),
- };
-
- // Reserve data for our exif info.
- mData.Resize(sizeof(marker) + exif_size);
- uint8_t* b = (uint8_t*)(mData.data());
-
- // Initialize marker & headers.
- memcpy(b, &marker, sizeof(marker));
- b += sizeof(marker);
- memcpy(b, &exif_header, sizeof(exif_header));
- b += sizeof(exif_header);
- memcpy(b, &tiff_header, sizeof(tiff_header));
- uint32_t data_offset = sizeof(tiff_header);
-
- // Write offset of the first IFD item.
- uint32_t first_tag_offset = data_offset + sizeof(uint32_t);
- memcpy(&b[data_offset], &first_tag_offset, sizeof(first_tag_offset));
- data_offset += sizeof(first_tag_offset);
-
- // At this moment |b| points to EXIF structure.
- // TIFF header is part of the structure itself and explains endianness of the
- // embedded data.
- ALOGI("%s: Building Main image EXIF tags", __FUNCTION__);
- data_offset = mImageIfd->Build(&b[data_offset], data_offset, true);
- ALOGI("%s: Building Thumbnail image EXIF tags", __FUNCTION__);
- data_offset = mThumbnailIfd->Build(&b[data_offset], data_offset, false);
- ALOGI("%s: EXIF metadata constructed (%d bytes).", __FUNCTION__, data_offset);
-}
-
-} // namespace android
diff --git a/guest/hals/camera/ExifMetadataBuilder.h b/guest/hals/camera/ExifMetadataBuilder.h
deleted file mode 100644
index 57986ba..0000000
--- a/guest/hals/camera/ExifMetadataBuilder.h
+++ /dev/null
@@ -1,55 +0,0 @@
-#ifndef EXIFMETADATAWRITER_H_
-#define EXIFMETADATAWRITER_H_
-
-#include <stddef.h>
-#include <stdint.h>
-#include <time.h>
-
-#include <string>
-#include <vector>
-
-#include "common/libs/auto_resources/auto_resources.h"
-
-namespace android {
-class ExifStructure;
-
-// Simplistic EXIF metadata builder.
-// www.exif.org/Exif2-2.PDF
-//
-// TODO(ender): Revisit using libexif after we drop support for JB.
-class ExifMetadataBuilder {
- public:
- ExifMetadataBuilder();
- ~ExifMetadataBuilder();
-
- void SetWidth(int width);
- void SetHeight(int height);
- void SetThumbnailWidth(int width);
- void SetThumbnailHeight(int height);
- void SetThumbnail(void* thumbnail, int size);
- void SetGpsLatitude(double degrees);
- void SetGpsLongitude(double degrees);
- void SetGpsAltitude(double altitude);
- void SetGpsDateTime(time_t timestamp);
- void SetGpsProcessingMethod(const std::string& method);
- void SetDateTime(time_t t);
- void SetLensFocalLength(double length);
- void Build();
-
- const AutoFreeBuffer& Buffer() { return mData; }
-
- private:
- ExifStructure* mImageIfd;
- ExifStructure* mThumbnailIfd;
- ExifStructure* mCameraSubIfd;
- ExifStructure* mGpsSubIfd;
-
- AutoFreeBuffer mData;
-
- ExifMetadataBuilder(const ExifMetadataBuilder&);
- ExifMetadataBuilder& operator=(const ExifMetadataBuilder&);
-};
-
-} // namespace android
-
-#endif // EXIFMETADATAWRITER_H_
diff --git a/guest/hals/camera/ImageMetadata.h b/guest/hals/camera/ImageMetadata.h
deleted file mode 100644
index b7bf7d4..0000000
--- a/guest/hals/camera/ImageMetadata.h
+++ /dev/null
@@ -1,26 +0,0 @@
-#ifndef IMAGEMETADATA_H_
-#define IMAGEMETADATA_H_
-
-// min and max macros make the C++11 support break on nyc-mr1
-#undef min
-#undef max
-#include <stdint.h>
-#include <string>
-
-extern "C" {
-/* Describes various attributes of the picture. */
-struct ImageMetadata {
- int mWidth;
- int mHeight;
- int mThumbnailWidth;
- int mThumbnailHeight;
- double mLensFocalLength;
- double mGpsLatitude;
- double mGpsLongitude;
- double mGpsAltitude;
- time_t mGpsTimestamp;
- std::string mGpsProcessingMethod;
-};
-}
-
-#endif // IMAGEMETADATA_H_
diff --git a/guest/hals/camera/JpegCompressor.cpp b/guest/hals/camera/JpegCompressor.cpp
index d8a318c..d2d796d 100644
--- a/guest/hals/camera/JpegCompressor.cpp
+++ b/guest/hals/camera/JpegCompressor.cpp
@@ -37,10 +37,10 @@
return res;
}
-typedef void (*InitFunc)(JpegStub* stub, int* strides);
+typedef void (*InitFunc)(JpegStub* stub);
typedef void (*CleanupFunc)(JpegStub* stub);
-typedef int (*CompressFunc)(JpegStub* stub, const void* image, int quality,
- const ImageMetadata* meta);
+typedef int (*CompressFunc)(JpegStub* stub, const void* image, int width,
+ int height, int quality, ExifData* exifData);
typedef void (*GetCompressedImageFunc)(JpegStub* stub, void* buff);
typedef size_t (*GetCompressedSizeFunc)(JpegStub* stub);
@@ -54,7 +54,7 @@
assert(mDl != NULL);
InitFunc f = (InitFunc)getSymbol(mDl, "JpegStub_init");
- (*f)(&mStub, mStrides);
+ (*f)(&mStub);
}
NV21JpegCompressor::~NV21JpegCompressor() {
@@ -67,12 +67,14 @@
***************************************************************************/
status_t NV21JpegCompressor::compressRawImage(const void* image,
- const ImageMetadata* meta,
- int quality) {
- mStrides[0] = meta->mWidth;
- mStrides[1] = meta->mWidth;
+ ExifData* exifData,
+ int quality,
+ int width,
+ int height) {
+ mStrides[0] = width;
+ mStrides[1] = width;
CompressFunc f = (CompressFunc)getSymbol(mDl, "JpegStub_compress");
- return (status_t)(*f)(&mStub, image, quality, meta);
+ return (status_t)(*f)(&mStub, image, width, height, quality, exifData);
}
size_t NV21JpegCompressor::getCompressedSize() {
diff --git a/guest/hals/camera/JpegCompressor.h b/guest/hals/camera/JpegCompressor.h
index 736d9b5..6d93c46 100644
--- a/guest/hals/camera/JpegCompressor.h
+++ b/guest/hals/camera/JpegCompressor.h
@@ -53,8 +53,8 @@
* NO_ERROR on success, or an appropriate error status.
*
*/
- status_t compressRawImage(const void* image, const ImageMetadata* metadata,
- int quality);
+ status_t compressRawImage(const void* image, ExifData* exifData,
+ int quality, int width, int height);
/* Get size of the compressed JPEG buffer.
* This method must be called only after a successful completion of
diff --git a/guest/hals/camera/JpegStub.cpp b/guest/hals/camera/JpegStub.cpp
index f186e03..1eef9e2 100644
--- a/guest/hals/camera/JpegStub.cpp
+++ b/guest/hals/camera/JpegStub.cpp
@@ -14,170 +14,54 @@
* limitations under the License.
*/
-#define LOG_NDEBUG 0
-#define LOG_TAG "EmulatedCamera_JPEGStub"
-#include <YuvToJpegEncoder.h>
-#include <cutils/log.h>
-#include <errno.h>
-#include <libyuv.h>
-
-#include "ExifMetadataBuilder.h"
#include "JpegStub.h"
-namespace {
-bool GenerateThumbnail(const uint8_t* source_nv21, int source_width,
- int source_height, int thumbnail_width,
- int thumbnail_height, SkDynamicMemoryWStream* target) {
- // We need to convert the image to Y'UV420SP to I420, which seems to be the
- // only scalable format by the LibYUV.
- // These formats are similar in their memory occupancy (both use about 3/2 of
- // the total pixels).
- int temp_y_size = source_width * source_height;
- int temp_uv_size = temp_y_size / 4;
- uint8_t* temp_y = (uint8_t*)malloc(temp_y_size + temp_uv_size + temp_uv_size);
- uint8_t* temp_u = temp_y + temp_y_size;
- uint8_t* temp_v = temp_u + temp_uv_size;
+#define LOG_NDEBUG 0
+#define LOG_TAG "EmulatedCamera_JPEGStub"
+#include <errno.h>
+#include <cutils/log.h>
+#include <stdlib.h>
- libyuv::NV12ToI420(source_nv21, source_width, source_nv21 + temp_y_size,
- source_width, temp_y, source_width, temp_u,
- source_width / 2, temp_v, source_width / 2, source_width,
- source_height);
+#include "Compressor.h"
- // Compute and allocate memory for thumbnail I420.
- int thumb_y_size = thumbnail_width * thumbnail_height;
- int thumb_uv_size = thumb_y_size / 4;
- uint8_t* thumb_y =
- (uint8_t*)malloc(thumb_y_size + thumb_uv_size + thumb_uv_size);
- uint8_t* thumb_u = thumb_y + thumb_y_size;
- uint8_t* thumb_v = thumb_u + thumb_uv_size;
-
- libyuv::I420Scale(temp_y, source_width, temp_u, source_width / 2, temp_v,
- source_width / 2, source_width, source_height, thumb_y,
- thumbnail_width, thumb_u, thumbnail_width / 2, thumb_v,
- thumbnail_width / 2, thumbnail_width, thumbnail_height,
- libyuv::kFilterBilinear);
-
- // Combine U and V components back to NV21 format.
- // We can re-use temp_y buffer for our needs at this point.
- for (int pix = 0; pix < thumb_uv_size; ++pix) {
- temp_y[2 * pix] = thumb_v[pix];
- temp_y[2 * pix + 1] = thumb_u[pix];
- }
-
- // Put the memory back. After this, the thumb_y points to beginning of NV21
- // image which we can compress.
- memcpy(thumb_u, temp_y, thumb_uv_size * 2);
-
- // Compress image.
- int strides[2] = {thumbnail_width, thumbnail_width};
- int offsets[2] = {0, thumb_y_size};
- Yuv420SpToJpegEncoder* encoder = new Yuv420SpToJpegEncoder(strides);
-
- bool result = encoder->encode(target, thumb_y, thumbnail_width,
- thumbnail_height, offsets, 90);
-
- if (!result) {
- ALOGE("%s: Thumbnail compression failed", __FUNCTION__);
- }
-
- delete (encoder);
- free(thumb_y);
- free(temp_y);
-
- return result;
-}
-} // namespace
-
-extern "C" void JpegStub_init(JpegStub* stub, int* strides) {
- stub->mInternalEncoder = (void*)new Yuv420SpToJpegEncoder(strides);
- stub->mInternalStream = (void*)new SkDynamicMemoryWStream();
- stub->mExifBuilder = (void*)new android::ExifMetadataBuilder();
+extern "C" void JpegStub_init(JpegStub* stub) {
+ stub->mCompressor = static_cast<void*>(new Compressor());
}
extern "C" void JpegStub_cleanup(JpegStub* stub) {
- delete ((Yuv420SpToJpegEncoder*)stub->mInternalEncoder);
- delete ((SkDynamicMemoryWStream*)stub->mInternalStream);
- delete ((android::ExifMetadataBuilder*)stub->mExifBuilder);
+ delete reinterpret_cast<Compressor*>(stub->mCompressor);
+ stub->mCompressor = nullptr;
}
-extern "C" int JpegStub_compress(JpegStub* stub, const void* image, int quality,
- const ImageMetadata* meta) {
- void* pY = const_cast<void*>(image);
+extern "C" int JpegStub_compress(JpegStub* stub,
+ const void* buffer,
+ int width,
+ int height,
+ int quality,
+ ExifData* exifData)
+{
+ Compressor* compressor = reinterpret_cast<Compressor*>(stub->mCompressor);
- int offsets[2];
- offsets[0] = 0;
- offsets[1] = meta->mWidth * meta->mHeight;
-
- Yuv420SpToJpegEncoder* encoder =
- (Yuv420SpToJpegEncoder*)stub->mInternalEncoder;
- SkDynamicMemoryWStream* stream =
- (SkDynamicMemoryWStream*)stub->mInternalStream;
- android::ExifMetadataBuilder* exif =
- (android::ExifMetadataBuilder*)stub->mExifBuilder;
-
- exif->SetWidth(meta->mWidth);
- exif->SetHeight(meta->mHeight);
- exif->SetDateTime(time(NULL));
- if (meta->mLensFocalLength != -1)
- exif->SetLensFocalLength(meta->mLensFocalLength);
- if (meta->mGpsTimestamp != -1) {
- exif->SetGpsLatitude(meta->mGpsLatitude);
- exif->SetGpsLongitude(meta->mGpsLongitude);
- exif->SetGpsAltitude(meta->mGpsAltitude);
- exif->SetGpsDateTime(meta->mGpsTimestamp);
- exif->SetGpsProcessingMethod(meta->mGpsProcessingMethod);
- }
-
- ALOGV("%s: Requested thumbnail size: %dx%d", __FUNCTION__,
- meta->mThumbnailWidth, meta->mThumbnailHeight);
-
- // Thumbnail requested?
- if (meta->mThumbnailWidth > 0 && meta->mThumbnailHeight > 0) {
- exif->SetThumbnailWidth(meta->mThumbnailWidth);
- exif->SetThumbnailHeight(meta->mThumbnailHeight);
- SkDynamicMemoryWStream* thumbnail = new SkDynamicMemoryWStream();
- GenerateThumbnail((uint8_t*)pY, meta->mWidth, meta->mHeight,
- meta->mThumbnailWidth, meta->mThumbnailHeight, thumbnail);
-
- int thumbnail_size = thumbnail->bytesWritten();
- void* thumbnail_data = malloc(thumbnail_size);
- thumbnail->read(thumbnail_data, 0, thumbnail_size);
- // Pass ownership to EXIF builder.
- exif->SetThumbnail(thumbnail_data, thumbnail_size);
- delete thumbnail;
- }
-
- exif->Build();
-
- if (encoder->encode(stream, pY, meta->mWidth, meta->mHeight, offsets,
- quality)) {
- ALOGI("%s: Compressed JPEG: %d[%dx%d] -> %zu bytes", __FUNCTION__,
- (meta->mWidth * meta->mHeight * 12) / 8, meta->mWidth, meta->mHeight,
- stream->bytesWritten());
- return 0;
- } else {
+ if (compressor->compress(reinterpret_cast<const unsigned char*>(buffer),
+ width, height, quality, exifData)) {
+ ALOGV("%s: Compressed JPEG: %d[%dx%d] -> %zu bytes",
+ __FUNCTION__, (width * height * 12) / 8,
+ width, height, compressor->getCompressedData().size());
+ return 0;
+ }
ALOGE("%s: JPEG compression failed", __FUNCTION__);
return errno ? errno : EINVAL;
- }
}
extern "C" void JpegStub_getCompressedImage(JpegStub* stub, void* buff) {
- SkDynamicMemoryWStream* stream =
- (SkDynamicMemoryWStream*)stub->mInternalStream;
- android::ExifMetadataBuilder* exif =
- (android::ExifMetadataBuilder*)stub->mExifBuilder;
- char* target = (char*)buff;
- memcpy(buff, exif->Buffer().data(), exif->Buffer().size());
- target += exif->Buffer().size();
+ Compressor* compressor = reinterpret_cast<Compressor*>(stub->mCompressor);
- // Skip 0xFFD8 marker. This marker has already been included in Metadata.
- stream->read(target, 2, stream->bytesWritten() - 2);
+ const std::vector<unsigned char>& data = compressor->getCompressedData();
+ memcpy(buff, &data[0], data.size());
}
extern "C" size_t JpegStub_getCompressedSize(JpegStub* stub) {
- SkDynamicMemoryWStream* stream =
- (SkDynamicMemoryWStream*)stub->mInternalStream;
- android::ExifMetadataBuilder* exif =
- (android::ExifMetadataBuilder*)stub->mExifBuilder;
- return stream->bytesWritten() + exif->Buffer().size() - 2;
+ Compressor* compressor = reinterpret_cast<Compressor*>(stub->mCompressor);
+
+ return compressor->getCompressedData().size();
}
diff --git a/guest/hals/camera/JpegStub.h b/guest/hals/camera/JpegStub.h
index 639f906..2e62182 100644
--- a/guest/hals/camera/JpegStub.h
+++ b/guest/hals/camera/JpegStub.h
@@ -17,21 +17,27 @@
#ifndef JPEGSTUB_H_
#define JPEGSTUB_H_
-#include "ImageMetadata.h"
+#include <stddef.h>
+
+struct _ExifData;
+typedef _ExifData ExifData;
extern "C" {
struct JpegStub {
- void* mInternalEncoder;
- void* mInternalStream;
- void* mExifBuilder;
+ void* mCompressor;
};
-void JpegStub_init(JpegStub* stub, int* strides);
+void JpegStub_init(JpegStub* stub);
void JpegStub_cleanup(JpegStub* stub);
-int JpegStub_compress(JpegStub* stub, const void* image, int quality,
- const ImageMetadata* metadata);
+int JpegStub_compress(JpegStub* stub,
+ const void* image,
+ int width,
+ int height,
+ int quality,
+ ExifData* exifData);
void JpegStub_getCompressedImage(JpegStub* stub, void* buff);
size_t JpegStub_getCompressedSize(JpegStub* stub);
+
};
-#endif // JPEGSTUB_H_
+#endif // JPEGSTUB_H_
diff --git a/guest/hals/camera/Thumbnail.cpp b/guest/hals/camera/Thumbnail.cpp
new file mode 100644
index 0000000..4b431bb
--- /dev/null
+++ b/guest/hals/camera/Thumbnail.cpp
@@ -0,0 +1,170 @@
+/*
+* Copyright (C) 2016 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 "Thumbnail.h"
+
+#define LOG_NDEBUG 0
+#define LOG_TAG "EmulatedCamera_Thumbnail"
+#include <cutils/log.h>
+#include <libexif/exif-data.h>
+#include <libyuv.h>
+
+#include "JpegCompressor.h"
+
+#include <vector>
+
+/*
+ * The NV21 format is a YUV format with an 8-bit Y-component and the U and V
+ * components are stored as 8 bits each but they are shared between a block of
+ * 2x2 pixels. So when calculating bits per pixel the 16 bits of U and V are
+ * shared between 4 pixels leading to 4 bits of U and V per pixel. Together
+ * with the 8 bits of Y this gives us 12 bits per pixel..
+ *
+ * The components are not grouped by pixels but separated into one Y-plane and
+ * one interleaved U and V-plane. The first half of the byte sequence is all of
+ * the Y data laid out in a linear fashion. After that the interleaved U and V-
+ * plane starts with one byte of V followed by one byte of U followed by one
+ * byte of V and so on. Each byte of U or V is associated with a 2x2 pixel block
+ * in a linear fashion.
+ *
+ * For an 8 by 4 pixel image the layout would be:
+ *
+ * +-----+-----+-----+-----+-----+-----+-----+-----+
+ * | Y0 | Y1 | Y2 | Y3 | Y4 | Y5 | Y6 | Y7 |
+ * +-----+-----+-----+-----+-----+-----+-----+-----+
+ * | Y8 | Y9 | Y10 | Y11 | Y12 | Y13 | Y14 | Y15 |
+ * +-----+-----+-----+-----+-----+-----+-----+-----+
+ * | Y16 | Y17 | Y18 | Y19 | Y20 | Y21 | Y22 | Y23 |
+ * +-----+-----+-----+-----+-----+-----+-----+-----+
+ * | Y24 | Y25 | Y26 | Y27 | Y28 | Y29 | Y30 | Y31 |
+ * +-----+-----+-----+-----+-----+-----+-----+-----+
+ * | V0 | U0 | V1 | U1 | V2 | U2 | V3 | U3 |
+ * +-----+-----+-----+-----+-----+-----+-----+-----+
+ * | V4 | U4 | V5 | U5 | V6 | U6 | V7 | U7 |
+ * +-----+-----+-----+-----+-----+-----+-----+-----+
+ *
+ * In this image V0 and U0 are the V and U components for the 2x2 block of
+ * pixels whose Y components are Y0, Y1, Y8 and Y9. V1 and U1 are matched with
+ * the Y components Y2, Y3, Y10, Y11, and so on for that row. For the next row
+ * of V and U the V4 and U4 components would be paired with Y16, Y17, Y24 and
+ * Y25.
+ */
+
+namespace android {
+
+static bool createRawThumbnail(const unsigned char* sourceImage,
+ int sourceWidth, int sourceHeight,
+ int thumbnailWidth, int thumbnailHeight,
+ std::vector<unsigned char>* thumbnail) {
+ // Deinterleave the U and V planes into separate planes, this is because
+ // libyuv requires the planes to be separate when scaling
+ const size_t sourceUVPlaneSize = (sourceWidth * sourceHeight) / 4;
+ // Put both U and V planes in one buffer, one after the other, to reduce
+ // memory fragmentation and number of allocations
+ std::vector<unsigned char> sourcePlanes(sourceUVPlaneSize * 2);
+ const unsigned char* ySourcePlane = sourceImage;
+ unsigned char* uSourcePlane = &sourcePlanes[0];
+ unsigned char* vSourcePlane = &sourcePlanes[sourceUVPlaneSize];
+
+ for (size_t i = 0; i < sourceUVPlaneSize; ++i) {
+ vSourcePlane[i] = sourceImage[sourceWidth * sourceHeight + i * 2 + 0];
+ uSourcePlane[i] = sourceImage[sourceWidth * sourceHeight + i * 2 + 1];
+ }
+
+ // Create enough space in the output vector for the result
+ thumbnail->resize((thumbnailWidth * thumbnailHeight * 12) / 8);
+
+ // The downscaled U and V planes will also be linear instead of interleaved,
+ // allocate space for them here
+ const size_t destUVPlaneSize = (thumbnailWidth * thumbnailHeight) / 4;
+ std::vector<unsigned char> destPlanes(destUVPlaneSize * 2);
+ unsigned char* yDestPlane = &(*thumbnail)[0];
+ unsigned char* uDestPlane = &destPlanes[0];
+ unsigned char* vDestPlane = &destPlanes[destUVPlaneSize];
+
+ // The strides for the U and V planes are half the width because the U and V
+ // components are common to 2x2 pixel blocks
+ int result = libyuv::I420Scale(ySourcePlane, sourceWidth,
+ uSourcePlane, sourceWidth / 2,
+ vSourcePlane, sourceWidth / 2,
+ sourceWidth, sourceHeight,
+ yDestPlane, thumbnailWidth,
+ uDestPlane, thumbnailWidth / 2,
+ vDestPlane, thumbnailWidth / 2,
+ thumbnailWidth, thumbnailHeight,
+ libyuv::kFilterBilinear);
+ if (result != 0) {
+ ALOGE("Unable to create thumbnail, downscaling failed with error: %d",
+ result);
+ return false;
+ }
+
+ // Now we need to interleave the downscaled U and V planes into the
+ // output buffer to make it NV21 encoded
+ const size_t uvPlanesOffset = thumbnailWidth * thumbnailHeight;
+ for (size_t i = 0; i < destUVPlaneSize; ++i) {
+ (*thumbnail)[uvPlanesOffset + i * 2 + 0] = vDestPlane[i];
+ (*thumbnail)[uvPlanesOffset + i * 2 + 1] = uDestPlane[i];
+ }
+
+ return true;
+}
+
+bool createThumbnail(const unsigned char* sourceImage,
+ int sourceWidth, int sourceHeight,
+ int thumbWidth, int thumbHeight, int quality,
+ ExifData* exifData) {
+ if (thumbWidth <= 0 || thumbHeight <= 0) {
+ ALOGE("%s: Invalid thumbnail width=%d or height=%d, must be > 0",
+ __FUNCTION__, thumbWidth, thumbHeight);
+ return false;
+ }
+
+ // First downscale the source image into a thumbnail-sized raw image
+ std::vector<unsigned char> rawThumbnail;
+ if (!createRawThumbnail(sourceImage, sourceWidth, sourceHeight,
+ thumbWidth, thumbHeight, &rawThumbnail)) {
+ // The thumbnail function will log an appropriate error if needed
+ return false;
+ }
+
+ // And then compress it into JPEG format without any EXIF data
+ NV21JpegCompressor compressor;
+ status_t result = compressor.compressRawImage(&rawThumbnail[0],
+ nullptr /* EXIF */,
+ quality, thumbWidth, thumbHeight);
+ if (result != NO_ERROR) {
+ ALOGE("%s: Unable to compress thumbnail", __FUNCTION__);
+ return false;
+ }
+
+ // And finally put it in the EXIF data. This transfers ownership of the
+ // malloc'd memory to the EXIF data structure. As long as the EXIF data
+ // structure is free'd using the EXIF library this memory will be free'd.
+ exifData->size = compressor.getCompressedSize();
+ exifData->data = reinterpret_cast<unsigned char*>(malloc(exifData->size));
+ if (exifData->data == nullptr) {
+ ALOGE("%s: Unable to allocate %u bytes of memory for thumbnail",
+ __FUNCTION__, exifData->size);
+ exifData->size = 0;
+ return false;
+ }
+ compressor.getCompressedImage(exifData->data);
+ return true;
+}
+
+} // namespace android
+
diff --git a/guest/hals/camera/Thumbnail.h b/guest/hals/camera/Thumbnail.h
new file mode 100644
index 0000000..b27636c
--- /dev/null
+++ b/guest/hals/camera/Thumbnail.h
@@ -0,0 +1,37 @@
+/*
+* Copyright (C) 2016 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.
+ */
+
+#ifndef GOLDFISH_CAMERA_THUMBNAIL_H
+#define GOLDFISH_CAMERA_THUMBNAIL_H
+
+struct _ExifData;
+typedef struct _ExifData ExifData;
+
+namespace android {
+
+/* Create a thumbnail from NV21 source data in |sourceImage| with the given
+ * dimensions. The resulting thumbnail is JPEG compressed and a pointer and size
+ * is placed in |exifData| which takes ownership of the allocated memory.
+ */
+bool createThumbnail(const unsigned char* sourceImage,
+ int sourceWidth, int sourceHeight,
+ int thumbnailWidth, int thumbnailHeight, int quality,
+ ExifData* exifData);
+
+} // namespace android
+
+#endif // GOLDFISH_CAMERA_THUMBNAIL_H
+