ExifInterface: add RAW input stream support
And also the following things are included:
- Remove mInputStream.
- Update javadoc accordingly.
Bug: 11224701
Change-Id: I30b4c29ac800ae396fca8f6b2c2c0f68028a44b3
diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java
index 1a387be..3fa078f 100644
--- a/media/java/android/media/ExifInterface.java
+++ b/media/java/android/media/ExifInterface.java
@@ -16,6 +16,8 @@
package android.media;
+import android.annotation.NonNull;
+import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.system.ErrnoException;
@@ -24,6 +26,7 @@
import android.util.Log;
import android.util.Pair;
+import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
@@ -145,6 +148,7 @@
private static final String TAG_HAS_THUMBNAIL = "hasThumbnail";
private static final String TAG_THUMBNAIL_OFFSET = "thumbnailOffset";
private static final String TAG_THUMBNAIL_LENGTH = "thumbnailLength";
+ private static final String TAG_THUMBNAIL_DATA = "thumbnailData";
// Constants used for the Orientation Exif tag.
public static final int ORIENTATION_UNDEFINED = 0;
@@ -163,6 +167,9 @@
public static final int WHITEBALANCE_AUTO = 0;
public static final int WHITEBALANCE_MANUAL = 1;
+ private static final byte[] JPEG_SIGNATURE = new byte[] {(byte) 0xff, (byte) 0xd8, (byte) 0xff};
+ private static final int JPEG_SIGNATURE_SIZE = 3;
+
private static SimpleDateFormat sFormatter;
// See Exchangeable image file format for digital still cameras: Exif version 2.2.
@@ -408,8 +415,8 @@
// Mappings from tag number to tag name and each item represents one IFD tag group.
private static final HashMap[] sExifTagMapsForReading = new HashMap[EXIF_TAGS.length];
// Mapping from tag name to tag number and the corresponding tag group.
- private static final HashMap<String, Pair<Integer, Integer>> sExifTagMapForWriting
- = new HashMap<>();
+ private static final HashMap<String, Pair<Integer, Integer>> sExifTagMapForWriting =
+ new HashMap<>();
// See JPEG File Interchange Format Version 1.02.
// The following values are defined for handling JPEG streams. In this implementation, we are
@@ -443,7 +450,7 @@
static {
System.loadLibrary("media_jni");
- initRawNative();
+ nativeInitRaw();
sFormatter = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss");
sFormatter.setTimeZone(TimeZone.getTimeZone("UTC"));
@@ -468,10 +475,10 @@
}
private final String mFilename;
- private final FileDescriptor mFileDescriptor;
- private final InputStream mInputStream;
- private boolean mIsRaw;
+ private final FileDescriptor mSeekableFileDescriptor;
+ private final AssetManager.AssetInputStream mAssetInputStream;
private final HashMap<String, String> mAttributes = new HashMap<>();
+ private boolean mIsRaw;
private boolean mHasThumbnail;
// The following values used for indicating a thumbnail position.
private int mThumbnailOffset;
@@ -488,23 +495,33 @@
if (filename == null) {
throw new IllegalArgumentException("filename cannot be null");
}
+ FileInputStream in = new FileInputStream(filename);
+ mAssetInputStream = null;
mFilename = filename;
- mFileDescriptor = null;
- mInputStream = new FileInputStream(filename);
- loadAttributes();
+ if (isSeekableFD(in.getFD())) {
+ mSeekableFileDescriptor = in.getFD();
+ } else {
+ mSeekableFileDescriptor = null;
+ }
+ loadAttributes(in);
}
/**
- * Reads Exif tags from the specified image file descriptor.
+ * Reads Exif tags from the specified image file descriptor. Attribute mutation is supported
+ * for seekable file descriptors only.
*/
public ExifInterface(FileDescriptor fileDescriptor) throws IOException {
if (fileDescriptor == null) {
throw new IllegalArgumentException("parcelFileDescriptor cannot be null");
}
+ mAssetInputStream = null;
mFilename = null;
- mFileDescriptor = fileDescriptor;
- mInputStream = new FileInputStream(fileDescriptor);
- loadAttributes();
+ if (isSeekableFD(fileDescriptor)) {
+ mSeekableFileDescriptor = fileDescriptor;
+ } else {
+ mSeekableFileDescriptor = null;
+ }
+ loadAttributes(new FileInputStream(fileDescriptor));
}
/**
@@ -516,9 +533,18 @@
throw new IllegalArgumentException("inputStream cannot be null");
}
mFilename = null;
- mFileDescriptor = null;
- mInputStream = inputStream;
- loadAttributes();
+ if (inputStream instanceof AssetManager.AssetInputStream) {
+ mAssetInputStream = (AssetManager.AssetInputStream) inputStream;
+ mSeekableFileDescriptor = null;
+ } else if (inputStream instanceof FileInputStream
+ && isSeekableFD(((FileInputStream) inputStream).getFD())) {
+ mAssetInputStream = null;
+ mSeekableFileDescriptor = ((FileInputStream) inputStream).getFD();
+ } else {
+ mAssetInputStream = null;
+ mSeekableFileDescriptor = null;
+ }
+ loadAttributes(inputStream);
}
/**
@@ -587,77 +613,93 @@
}
/**
- * Initialize mAttributes with the attributes from the file mFilename.
- *
- * mAttributes is a HashMap which stores the Exif attributes of the file.
- * The key is the standard tag name and the value is the tag's value: e.g.
- * Model -> Nikon. Numeric values are stored as strings.
- *
- * This function also initialize mHasThumbnail to indicate whether the
- * file has a thumbnail inside.
+ * This function decides which parser to read the image data according to the given input stream
+ * type and the content of the input stream. In each case, it reads the first three bytes to
+ * determine whether the image data format is JPEG or not.
*/
- private void loadAttributes() throws IOException {
- FileInputStream in = null;
- try {
- if (mFilename != null) {
- in = new FileInputStream(mFilename);
+ private void loadAttributes(@NonNull InputStream in) throws IOException {
+ // Process RAW input stream
+ if (mAssetInputStream != null) {
+ long asset = mAssetInputStream.getNativeAsset();
+ if (handleRawResult(nativeGetRawAttributesFromAsset(asset))) {
+ return;
}
- if (mFileDescriptor != null) {
- in = new FileInputStream(mFileDescriptor);
+ } else if (mSeekableFileDescriptor != null) {
+ if (handleRawResult(nativeGetRawAttributesFromFileDescriptor(
+ mSeekableFileDescriptor))) {
+ return;
}
- if (in != null) {
- // First test whether a given file is a one of RAW format or not.
- HashMap map = getRawAttributesNative(Os.dup(in.getFD()));
- mIsRaw = map != null;
- if (mIsRaw) {
- for (Object obj : map.entrySet()) {
- Map.Entry entry = (Map.Entry) obj;
- String attrName = (String) entry.getKey();
- String attrValue = (String) entry.getValue();
-
- switch (attrName) {
- case TAG_HAS_THUMBNAIL:
- mHasThumbnail = attrValue.equalsIgnoreCase("true");
- break;
- case TAG_THUMBNAIL_OFFSET:
- mThumbnailOffset = Integer.parseInt(attrValue);
- break;
- case TAG_THUMBNAIL_LENGTH:
- mThumbnailLength = Integer.parseInt(attrValue);
- break;
- default:
- mAttributes.put(attrName, attrValue);
- break;
- }
- }
-
- if (DEBUG) {
- printAttributes();
- }
- return;
- }
+ } else {
+ in = new BufferedInputStream(in, JPEG_SIGNATURE_SIZE);
+ if (!isJpegInputStream((BufferedInputStream) in) && handleRawResult(
+ nativeGetRawAttributesFromInputStream(in))) {
+ return;
}
- } catch (ErrnoException e) {
- e.rethrowAsIOException();
- } finally {
- IoUtils.closeQuietly(in);
}
- try {
- if (mFileDescriptor != null) {
- Os.lseek(mFileDescriptor, 0, OsConstants.SEEK_SET);
- }
+ // Process JPEG input stream
+ getJpegAttributes(in);
- getJpegAttributes(mInputStream);
- } catch (ErrnoException e) {
- e.rethrowAsIOException();
- } finally {
- IoUtils.closeQuietly(mInputStream);
+ if (DEBUG) {
+ printAttributes();
+ }
+ }
+
+ private static boolean isJpegInputStream(BufferedInputStream in) throws IOException {
+ in.mark(JPEG_SIGNATURE_SIZE);
+ byte[] signatureBytes = new byte[JPEG_SIGNATURE_SIZE];
+ if (in.read(signatureBytes) != JPEG_SIGNATURE_SIZE) {
+ throw new EOFException();
+ }
+ boolean isJpeg = Arrays.equals(JPEG_SIGNATURE, signatureBytes);
+ in.reset();
+ return isJpeg;
+ }
+
+ private boolean handleRawResult(HashMap map) {
+ if (map == null) {
+ return false;
+ }
+
+ // Mark for disabling the save feature.
+ mIsRaw = true;
+
+ for (Object obj : map.entrySet()) {
+ Map.Entry entry = (Map.Entry) obj;
+ String attrName = (String) entry.getKey();
+
+ switch (attrName) {
+ case TAG_HAS_THUMBNAIL:
+ mHasThumbnail = ((String) entry.getValue()).equalsIgnoreCase("true");
+ break;
+ case TAG_THUMBNAIL_OFFSET:
+ mThumbnailOffset = Integer.parseInt((String) entry.getValue());
+ break;
+ case TAG_THUMBNAIL_LENGTH:
+ mThumbnailLength = Integer.parseInt((String) entry.getValue());
+ break;
+ case TAG_THUMBNAIL_DATA:
+ mThumbnailBytes = (byte[]) entry.getValue();
+ break;
+ default:
+ mAttributes.put(attrName, (String) entry.getValue());
+ break;
+ }
}
if (DEBUG) {
printAttributes();
}
+ return true;
+ }
+
+ private static boolean isSeekableFD(FileDescriptor fd) throws IOException {
+ try {
+ Os.lseek(fd, 0, OsConstants.SEEK_CUR);
+ return true;
+ } catch (ErrnoException e) {
+ return false;
+ }
}
// Prints out attributes for debugging.
@@ -679,9 +721,9 @@
throw new UnsupportedOperationException(
"ExifInterface does not support saving attributes on RAW formats.");
}
- if (mFileDescriptor == null && mFilename == null) {
+ if (mSeekableFileDescriptor == null && mFilename == null) {
throw new UnsupportedOperationException(
- "ExifInterface does not support saving attributes for input streams.");
+ "ExifInterface does not support saving attributes for the current input.");
}
// Keep the thumbnail in memory
@@ -698,11 +740,10 @@
if (!originalFile.renameTo(tempFile)) {
throw new IOException("Could'nt rename to " + tempFile.getAbsolutePath());
}
- }
- if (mFileDescriptor != null) {
+ } else if (mSeekableFileDescriptor != null) {
tempFile = File.createTempFile("temp", "jpg");
- Os.lseek(mFileDescriptor, 0, OsConstants.SEEK_SET);
- in = new FileInputStream(mFileDescriptor);
+ Os.lseek(mSeekableFileDescriptor, 0, OsConstants.SEEK_SET);
+ in = new FileInputStream(mSeekableFileDescriptor);
out = new FileOutputStream(tempFile);
Streams.copy(in, out);
}
@@ -720,10 +761,9 @@
in = new FileInputStream(tempFile);
if (mFilename != null) {
out = new FileOutputStream(mFilename);
- }
- if (mFileDescriptor != null) {
- Os.lseek(mFileDescriptor, 0, OsConstants.SEEK_SET);
- out = new FileOutputStream(mFileDescriptor);
+ } else if (mSeekableFileDescriptor != null) {
+ Os.lseek(mSeekableFileDescriptor, 0, OsConstants.SEEK_SET);
+ out = new FileOutputStream(mSeekableFileDescriptor);
}
saveJpegAttributes(in, out);
} catch (ErrnoException e) {
@@ -760,13 +800,15 @@
// Read the thumbnail.
FileInputStream in = null;
- try {
- if (mFileDescriptor != null) {
- Os.lseek(mFileDescriptor, 0, OsConstants.SEEK_SET);
- in = new FileInputStream(mFileDescriptor);
- }
- if (mFilename != null) {
+ try {
+ if (mAssetInputStream != null) {
+ return nativeGetThumbnailFromAsset(
+ mAssetInputStream.getNativeAsset(), mThumbnailOffset, mThumbnailLength);
+ } else if (mFilename != null) {
in = new FileInputStream(mFilename);
+ } else if (mSeekableFileDescriptor != null) {
+ Os.lseek(mSeekableFileDescriptor, 0, OsConstants.SEEK_SET);
+ in = new FileInputStream(mSeekableFileDescriptor);
}
if (in == null) {
// Should not be reached this.
@@ -1180,8 +1222,9 @@
mThumbnailOffset = exifOffsetFromBeginning + jpegInterchangeFormat;
mThumbnailLength = jpegInterchangeFormatLength;
- // Do not store a thumbnail in memory if the given input can be re-read.
- if (mFileDescriptor == null && mFilename == null) {
+ if (mFilename == null && mAssetInputStream == null
+ && mSeekableFileDescriptor == null) {
+ // Save the thumbnail in memory if the input doesn't support reading again.
byte[] thumbnailBytes = new byte[jpegInterchangeFormatLength];
dataInputStream.seek(jpegInterchangeFormat);
dataInputStream.readFully(thumbnailBytes);
@@ -1988,6 +2031,10 @@
}
// JNI methods for RAW formats.
- private static native void initRawNative();
- private static native HashMap getRawAttributesNative(FileDescriptor fileDescriptor);
+ private static native void nativeInitRaw();
+ private static native byte[] nativeGetThumbnailFromAsset(
+ long asset, int thumbnailOffset, int thumbnailLength);
+ private static native HashMap nativeGetRawAttributesFromAsset(long asset);
+ private static native HashMap nativeGetRawAttributesFromFileDescriptor(FileDescriptor fd);
+ private static native HashMap nativeGetRawAttributesFromInputStream(InputStream in);
}
diff --git a/media/jni/android_media_ExifInterface.cpp b/media/jni/android_media_ExifInterface.cpp
index f7481af..418a3f2 100644
--- a/media/jni/android_media_ExifInterface.cpp
+++ b/media/jni/android_media_ExifInterface.cpp
@@ -19,12 +19,15 @@
#include "android_media_Utils.h"
+#include "android/graphics/CreateJavaOutputStreamAdaptor.h"
#include "src/piex_types.h"
#include "src/piex.h"
#include <jni.h>
#include <JNIHelp.h>
+#include <androidfw/Asset.h>
#include <android_runtime/AndroidRuntime.h>
+#include <android/graphics/Utils.h>
#include <nativehelper/ScopedLocalRef.h>
#include <utils/Log.h>
@@ -35,6 +38,9 @@
using namespace android;
+static const char kJpegSignatureChars[] = {(char)0xff, (char)0xd8, (char)0xff};
+static const int kJpegSignatureSize = 3;
+
#define FIND_CLASS(var, className) \
var = env->FindClass(className); \
LOG_FATAL_IF(! var, "Unable to find class " className);
@@ -82,18 +88,48 @@
"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
}
-static jobject ExifInterface_getRawMetadata(
- JNIEnv* env, jclass /* clazz */, jobject jfileDescriptor) {
- int fd = jniGetFDFromFileDescriptor(env, jfileDescriptor);
- if (fd < 0) {
- ALOGI("Invalid file descriptor");
+static bool is_asset_stream(const SkStream& stream) {
+ return stream.hasLength() && stream.hasPosition();
+}
+
+static jobject ExifInterface_getThumbnailFromAsset(
+ JNIEnv* env, jclass /* clazz */, jlong jasset, jint jthumbnailOffset,
+ jint jthumbnailLength) {
+ Asset* asset = reinterpret_cast<Asset*>(jasset);
+ std::unique_ptr<AssetStreamAdaptor> stream(new AssetStreamAdaptor(asset));
+
+ std::unique_ptr<jbyte[]> thumbnailData(new jbyte[(int)jthumbnailLength]);
+ if (thumbnailData.get() == NULL) {
+ ALOGI("No memory to get thumbnail");
return NULL;
}
- piex::PreviewImageData image_data;
- std::unique_ptr<FileStream> stream(new FileStream(fd));
+ // Do not know the current offset. So rewind it.
+ stream->rewind();
- if (!GetExifFromRawImage(stream.get(), String8("[file descriptor]"), image_data)) {
+ // Read thumbnail.
+ stream->skip((int)jthumbnailOffset);
+ stream->read((void*)thumbnailData.get(), (int)jthumbnailLength);
+
+ // Copy to the byte array.
+ jbyteArray byteArray = env->NewByteArray(jthumbnailLength);
+ env->SetByteArrayRegion(byteArray, 0, jthumbnailLength, thumbnailData.get());
+ return byteArray;
+}
+
+static jobject getRawAttributes(JNIEnv* env, SkStream* stream, bool returnThumbnail) {
+ std::unique_ptr<SkStream> streamDeleter(stream);
+
+ std::unique_ptr<::piex::StreamInterface> piexStream;
+ if (is_asset_stream(*stream)) {
+ piexStream.reset(new AssetStream(streamDeleter.release()));
+ } else {
+ piexStream.reset(new BufferedStream(streamDeleter.release()));
+ }
+
+ piex::PreviewImageData image_data;
+
+ if (!GetExifFromRawImage(piexStream.get(), String8("[piex stream]"), image_data)) {
ALOGI("Raw image not detected");
return NULL;
}
@@ -253,7 +289,117 @@
}
}
- return KeyedVectorToHashMap(env, map);
+ jobject hashMap = KeyedVectorToHashMap(env, map);
+
+ if (returnThumbnail) {
+ std::unique_ptr<jbyte[]> thumbnailData(new jbyte[image_data.thumbnail.length]);
+ if (thumbnailData.get() == NULL) {
+ ALOGE("No memory to parse a thumbnail");
+ return NULL;
+ }
+ jbyteArray jthumbnailByteArray = env->NewByteArray(image_data.thumbnail.length);
+ if (jthumbnailByteArray == NULL) {
+ ALOGE("No memory to parse a thumbnail");
+ return NULL;
+ }
+ piexStream.get()->GetData(image_data.thumbnail.offset, image_data.thumbnail.length,
+ (uint8_t*)thumbnailData.get());
+ env->SetByteArrayRegion(
+ jthumbnailByteArray, 0, image_data.thumbnail.length, thumbnailData.get());
+ jstring jkey = env->NewStringUTF(String8("thumbnailData"));
+ env->CallObjectMethod(hashMap, gFields.hashMap.put, jkey, jthumbnailByteArray);
+ env->DeleteLocalRef(jkey);
+ env->DeleteLocalRef(jthumbnailByteArray);
+ }
+ return hashMap;
+}
+
+static jobject ExifInterface_getRawAttributesFromAsset(
+ JNIEnv* env, jclass /* clazz */, jlong jasset) {
+ std::unique_ptr<char[]> jpegSignature(new char[kJpegSignatureSize]);
+ if (jpegSignature.get() == NULL) {
+ ALOGE("No enough memory to parse");
+ return NULL;
+ }
+
+ Asset* asset = reinterpret_cast<Asset*>(jasset);
+ std::unique_ptr<AssetStreamAdaptor> stream(new AssetStreamAdaptor(asset));
+
+ if (stream.get()->read(jpegSignature.get(), kJpegSignatureSize) != kJpegSignatureSize) {
+ // Rewind the stream.
+ stream.get()->rewind();
+
+ ALOGI("Corrupted image.");
+ return NULL;
+ }
+
+ // Rewind the stream.
+ stream.get()->rewind();
+
+ if (memcmp(jpegSignature.get(), kJpegSignatureChars, kJpegSignatureSize) == 0) {
+ ALOGI("Should be a JPEG stream.");
+ return NULL;
+ }
+
+ // Try to parse from the given stream.
+ jobject result = getRawAttributes(env, stream.get(), false);
+
+ // Rewind the stream for the chance to read JPEG.
+ if (result == NULL) {
+ stream.get()->rewind();
+ }
+ return result;
+}
+
+static jobject ExifInterface_getRawAttributesFromFileDescriptor(
+ JNIEnv* env, jclass /* clazz */, jobject jfileDescriptor) {
+ std::unique_ptr<char[]> jpegSignature(new char[kJpegSignatureSize]);
+ if (jpegSignature.get() == NULL) {
+ ALOGE("No enough memory to parse");
+ return NULL;
+ }
+
+ int fd = jniGetFDFromFileDescriptor(env, jfileDescriptor);
+ if (fd < 0) {
+ ALOGI("Invalid file descriptor");
+ return NULL;
+ }
+
+ // Restore the file descriptor's offset on exiting this function.
+ AutoFDSeek autoRestore(fd);
+
+ int dupFd = dup(fd);
+
+ FILE* file = fdopen(dupFd, "r");
+ if (file == NULL) {
+ ALOGI("Failed to open the file descriptor");
+ return NULL;
+ }
+
+ if (fgets(jpegSignature.get(), kJpegSignatureSize, file) == NULL) {
+ ALOGI("Corrupted image.");
+ return NULL;
+ }
+
+ if (memcmp(jpegSignature.get(), kJpegSignatureChars, kJpegSignatureSize) == 0) {
+ ALOGI("Should be a JPEG stream.");
+ return NULL;
+ }
+
+ // Rewind the file descriptor.
+ fseek(file, 0L, SEEK_SET);
+
+ std::unique_ptr<SkFILEStream> fileStream(new SkFILEStream(file,
+ SkFILEStream::kCallerPasses_Ownership));
+ return getRawAttributes(env, fileStream.get(), false);
+}
+
+static jobject ExifInterface_getRawAttributesFromInputStream(
+ JNIEnv* env, jclass /* clazz */, jobject jinputStream) {
+ jbyteArray byteArray = env->NewByteArray(8*1024);
+ ScopedLocalRef<jbyteArray> scoper(env, byteArray);
+ std::unique_ptr<SkStream> stream(CreateJavaInputStreamAdaptor(env, jinputStream, scoper.get()));
+ return getRawAttributes(env, stream.get(), true);
}
} // extern "C"
@@ -261,9 +407,14 @@
// ----------------------------------------------------------------------------
static JNINativeMethod gMethods[] = {
- { "initRawNative", "()V", (void *)ExifInterface_initRaw },
- { "getRawAttributesNative", "(Ljava/io/FileDescriptor;)Ljava/util/HashMap;",
- (void*)ExifInterface_getRawMetadata },
+ { "nativeInitRaw", "()V", (void *)ExifInterface_initRaw },
+ { "nativeGetThumbnailFromAsset", "(JII)[B", (void *)ExifInterface_getThumbnailFromAsset },
+ { "nativeGetRawAttributesFromAsset", "(J)Ljava/util/HashMap;",
+ (void*)ExifInterface_getRawAttributesFromAsset },
+ { "nativeGetRawAttributesFromFileDescriptor", "(Ljava/io/FileDescriptor;)Ljava/util/HashMap;",
+ (void*)ExifInterface_getRawAttributesFromFileDescriptor },
+ { "nativeGetRawAttributesFromInputStream", "(Ljava/io/InputStream;)Ljava/util/HashMap;",
+ (void*)ExifInterface_getRawAttributesFromInputStream },
};
int register_android_media_ExifInterface(JNIEnv *env) {
diff --git a/media/jni/android_media_Utils.cpp b/media/jni/android_media_Utils.cpp
index f429661..527e6c2 100644
--- a/media/jni/android_media_Utils.cpp
+++ b/media/jni/android_media_Utils.cpp
@@ -30,30 +30,78 @@
namespace android {
+AssetStream::AssetStream(SkStream* stream)
+ : mStream(stream) {
+}
+
+AssetStream::~AssetStream() {
+}
+
+piex::Error AssetStream::GetData(
+ const size_t offset, const size_t length, std::uint8_t* data) {
+ // Seek first.
+ if (mPosition != offset) {
+ if (!mStream->seek(offset)) {
+ return piex::Error::kFail;
+ }
+ }
+
+ // Read bytes.
+ size_t size = mStream->read((void*)data, length);
+ mPosition += size;
+
+ return size == length ? piex::Error::kOk : piex::Error::kFail;
+}
+
+BufferedStream::BufferedStream(SkStream* stream)
+ : mStream(stream) {
+}
+
+BufferedStream::~BufferedStream() {
+}
+
+piex::Error BufferedStream::GetData(
+ const size_t offset, const size_t length, std::uint8_t* data) {
+ // Seek first.
+ if (offset + length > mStreamBuffer.bytesWritten()) {
+ size_t sizeToRead = offset + length - mStreamBuffer.bytesWritten();
+ if (sizeToRead <= kMinSizeToRead) {
+ sizeToRead = kMinSizeToRead;
+ }
+ void* tempBuffer = malloc(sizeToRead);
+ if (tempBuffer != NULL) {
+ size_t bytesRead = mStream->read(tempBuffer, sizeToRead);
+ if (bytesRead != sizeToRead) {
+ free(tempBuffer);
+ return piex::Error::kFail;
+ }
+ mStreamBuffer.write(tempBuffer, bytesRead);
+ free(tempBuffer);
+ }
+ }
+
+ // Read bytes.
+ if (mStreamBuffer.read((void*)data, offset, length)) {
+ return piex::Error::kOk;
+ } else {
+ return piex::Error::kFail;
+ }
+}
+
FileStream::FileStream(const int fd)
- : mPosition(0),
- mSize(0) {
+ : mPosition(0) {
mFile = fdopen(fd, "r");
if (mFile == NULL) {
return;
}
- // Get the size.
- fseek(mFile, 0l, SEEK_END);
- mSize = ftell(mFile);
- fseek(mFile, 0l, SEEK_SET);
}
FileStream::FileStream(const String8 filename)
- : mPosition(0),
- mSize(0) {
+ : mPosition(0) {
mFile = fopen(filename.string(), "r");
if (mFile == NULL) {
return;
}
- // Get the size.
- fseek(mFile, 0l, SEEK_END);
- mSize = ftell(mFile);
- fseek(mFile, 0l, SEEK_SET);
}
FileStream::~FileStream() {
@@ -79,7 +127,7 @@
mPosition += size;
// Handle errors.
- if (ferror(mFile) || (size == 0 && feof(mFile))) {
+ if (ferror(mFile)) {
ALOGV("GetData read failed: (offset: %zu, length: %zu)", offset, length);
return piex::Error::kFail;
}
@@ -90,21 +138,12 @@
return mFile != NULL;
}
-size_t FileStream::size() const {
- return mSize;
-}
-
bool GetExifFromRawImage(
- FileStream* stream, const String8& filename, piex::PreviewImageData& image_data) {
+ piex::StreamInterface* stream, const String8& filename,
+ piex::PreviewImageData& image_data) {
// Reset the PreviewImageData to its default.
image_data = piex::PreviewImageData();
- if (!stream->exists()) {
- // File is not exists.
- ALOGV("File is not exists: %s", filename.string());
- return false;
- }
-
if (!piex::IsRaw(stream)) {
// Format not supported.
ALOGV("Format not supported: %s", filename.string());
@@ -119,12 +158,6 @@
return false;
}
- if (image_data.thumbnail_offset + image_data.thumbnail_length > stream->size()) {
- // Corrupted image.
- ALOGV("Corrupted file: %s", filename.string());
- return false;
- }
-
return true;
}
diff --git a/media/jni/android_media_Utils.h b/media/jni/android_media_Utils.h
index 3791ec9..8184f94 100644
--- a/media/jni/android_media_Utils.h
+++ b/media/jni/android_media_Utils.h
@@ -21,20 +21,62 @@
#include "src/piex.h"
#include <android_runtime/AndroidRuntime.h>
+#include <camera3.h>
+#include <gui/CpuConsumer.h>
#include <jni.h>
#include <JNIHelp.h>
#include <utils/KeyedVector.h>
#include <utils/String8.h>
-#include <gui/CpuConsumer.h>
-#include <camera3.h>
+#include <SkStream.h>
namespace android {
+class AssetStream : public piex::StreamInterface {
+private:
+ SkStream *mStream;
+ size_t mPosition;
+
+public:
+ AssetStream(SkStream* stream);
+ ~AssetStream();
+
+ // Reads 'length' amount of bytes from 'offset' to 'data'. The 'data' buffer
+ // provided by the caller, guaranteed to be at least "length" bytes long.
+ // On 'kOk' the 'data' pointer contains 'length' valid bytes beginning at
+ // 'offset' bytes from the start of the stream.
+ // Returns 'kFail' if 'offset' + 'length' exceeds the stream and does not
+ // change the contents of 'data'.
+ piex::Error GetData(
+ const size_t offset, const size_t length, std::uint8_t* data) override;
+};
+
+class BufferedStream : public piex::StreamInterface {
+private:
+ SkStream *mStream;
+ // Growable memory stream
+ SkDynamicMemoryWStream mStreamBuffer;
+
+ // Minimum size to read on filling the buffer.
+ const size_t kMinSizeToRead = 8192;
+
+public:
+ BufferedStream(SkStream* stream);
+ ~BufferedStream();
+
+ // Reads 'length' amount of bytes from 'offset' to 'data'. The 'data' buffer
+ // provided by the caller, guaranteed to be at least "length" bytes long.
+ // On 'kOk' the 'data' pointer contains 'length' valid bytes beginning at
+ // 'offset' bytes from the start of the stream.
+ // Returns 'kFail' if 'offset' + 'length' exceeds the stream and does not
+ // change the contents of 'data'.
+ piex::Error GetData(
+ const size_t offset, const size_t length, std::uint8_t* data) override;
+};
+
class FileStream : public piex::StreamInterface {
private:
FILE *mFile;
size_t mPosition;
- size_t mSize;
public:
FileStream(const int fd);
@@ -50,13 +92,12 @@
piex::Error GetData(
const size_t offset, const size_t length, std::uint8_t* data) override;
bool exists() const;
- size_t size() const;
};
// Reads EXIF metadata from a given raw image via piex.
// And returns true if the operation is successful; otherwise, false.
bool GetExifFromRawImage(
- FileStream* stream, const String8& filename, piex::PreviewImageData& image_data);
+ piex::StreamInterface* stream, const String8& filename, piex::PreviewImageData& image_data);
// Returns true if the conversion is successful; otherwise, false.
bool ConvertKeyValueArraysToKeyedVector(
diff --git a/media/tests/MediaFrameworkTest/assets/image_exif_byte_order_ii.jpg b/media/tests/MediaFrameworkTest/assets/image_exif_byte_order_ii.jpg
new file mode 100644
index 0000000..477cd3a
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/assets/image_exif_byte_order_ii.jpg
Binary files differ
diff --git a/media/tests/MediaFrameworkTest/assets/image_exif_byte_order_mm.jpg b/media/tests/MediaFrameworkTest/assets/image_exif_byte_order_mm.jpg
new file mode 100644
index 0000000..78ac703
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/assets/image_exif_byte_order_mm.jpg
Binary files differ
diff --git a/media/tests/MediaFrameworkTest/assets/lg_g4_iso_800.dng b/media/tests/MediaFrameworkTest/assets/lg_g4_iso_800.dng
new file mode 100644
index 0000000..5fcc720
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/assets/lg_g4_iso_800.dng
Binary files differ
diff --git a/media/tests/MediaFrameworkTest/res/values/exifinterface.xml b/media/tests/MediaFrameworkTest/res/values/exifinterface.xml
index 8fc6adc..eb13ff37 100644
--- a/media/tests/MediaFrameworkTest/res/values/exifinterface.xml
+++ b/media/tests/MediaFrameworkTest/res/values/exifinterface.xml
@@ -76,9 +76,9 @@
<item>0</item>
</array>
<array name="lg_g4_iso_800_dng">
- <item>false</item>
- <item>0</item>
- <item>0</item>
+ <item>true</item>
+ <item>256</item>
+ <item>144</item>
<item>true</item>
<item>53.834507</item>
<item>10.69585</item>
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/ExifInterfaceTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/ExifInterfaceTest.java
index 1c80746..cff18cf 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/ExifInterfaceTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/ExifInterfaceTest.java
@@ -23,20 +23,20 @@
import android.graphics.BitmapFactory;
import android.media.ExifInterface;
import android.os.Environment;
-import android.os.ParcelFileDescriptor;
import android.test.AndroidTestCase;
import android.util.Log;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.IOException;
-import java.lang.reflect.Type;
import libcore.io.IoUtils;
import libcore.io.Streams;
@@ -46,11 +46,10 @@
private static final boolean VERBOSE = false; // lots of logging
private static final double DIFFERENCE_TOLERANCE = .005;
- private static final int BUFFER_SIZE = 32768;
// List of files.
- private static final String EXIF_BYTE_ORDER_II_JPEG = "ExifByteOrderII.jpg";
- private static final String EXIF_BYTE_ORDER_MM_JPEG = "ExifByteOrderMM.jpg";
+ private static final String EXIF_BYTE_ORDER_II_JPEG = "image_exif_byte_order_ii.jpg";
+ private static final String EXIF_BYTE_ORDER_MM_JPEG = "image_exif_byte_order_mm.jpg";
private static final String LG_G4_ISO_800_DNG = "lg_g4_iso_800.dng";
private static final int[] IMAGE_RESOURCES = new int[] {
R.raw.image_exif_byte_order_ii, R.raw.image_exif_byte_order_mm, R.raw.lg_g4_iso_800 };
@@ -165,8 +164,6 @@
@Override
protected void setUp() throws Exception {
- byte[] buffer = new byte[BUFFER_SIZE];
-
for (int i = 0; i < IMAGE_RESOURCES.length; ++i) {
String outputPath = new File(Environment.getExternalStorageDirectory(),
IMAGE_FILENAMES[i]).getAbsolutePath();
@@ -314,26 +311,30 @@
expectedValue.whiteBalance);
}
- private void testExifInterfaceForJpeg(String fileName, int typedArrayResourceId)
+ private void testExifInterfaceCommon(File imageFile, ExpectedValue expectedValue)
throws IOException {
- ExpectedValue expectedValue = new ExpectedValue(
- getContext().getResources().obtainTypedArray(typedArrayResourceId));
- File imageFile = new File(Environment.getExternalStorageDirectory(), fileName);
-
// Created via path.
ExifInterface exifInterface = new ExifInterface(imageFile.getAbsolutePath());
if (VERBOSE) {
- printExifTagsAndValues(fileName, exifInterface);
+ printExifTagsAndValues(imageFile.getName(), exifInterface);
+ }
+ compareWithExpectedValue(exifInterface, expectedValue);
+
+ // Created from an asset file.
+ InputStream in = mContext.getAssets().open(imageFile.getName());
+ exifInterface = new ExifInterface(in);
+ if (VERBOSE) {
+ printExifTagsAndValues(imageFile.getName(), exifInterface);
}
compareWithExpectedValue(exifInterface, expectedValue);
// Created via InputStream.
- FileInputStream in = null;
+ in = null;
try {
- in = new FileInputStream(imageFile.getAbsolutePath());
+ in = new BufferedInputStream(new FileInputStream(imageFile.getAbsolutePath()));
exifInterface = new ExifInterface(in);
if (VERBOSE) {
- printExifTagsAndValues(fileName, exifInterface);
+ printExifTagsAndValues(imageFile.getName(), exifInterface);
}
compareWithExpectedValue(exifInterface, expectedValue);
} finally {
@@ -345,18 +346,30 @@
FileDescriptor fd = Os.open(imageFile.getAbsolutePath(), OsConstants.O_RDONLY, 0600);
exifInterface = new ExifInterface(fd);
if (VERBOSE) {
- printExifTagsAndValues(fileName, exifInterface);
+ printExifTagsAndValues(imageFile.getName(), exifInterface);
}
compareWithExpectedValue(exifInterface, expectedValue);
} catch (ErrnoException e) {
e.rethrowAsIOException();
}
+ }
+
+ private void testExifInterfaceForJpeg(String fileName, int typedArrayResourceId)
+ throws IOException {
+ ExpectedValue expectedValue = new ExpectedValue(
+ getContext().getResources().obtainTypedArray(typedArrayResourceId));
+ File imageFile = new File(Environment.getExternalStorageDirectory(), fileName);
+
+ // Test for reading from various inputs.
+ testExifInterfaceCommon(imageFile, expectedValue);
// Test for saving attributes.
+ ExifInterface exifInterface;
try {
FileDescriptor fd = Os.open(imageFile.getAbsolutePath(), OsConstants.O_RDWR, 0600);
exifInterface = new ExifInterface(fd);
exifInterface.saveAttributes();
+ fd = Os.open(imageFile.getAbsolutePath(), OsConstants.O_RDWR, 0600);
exifInterface = new ExifInterface(fd);
if (VERBOSE) {
printExifTagsAndValues(fileName, exifInterface);
@@ -383,25 +396,11 @@
getContext().getResources().obtainTypedArray(typedArrayResourceId));
File imageFile = new File(Environment.getExternalStorageDirectory(), fileName);
- // Created via path.
- ExifInterface exifInterface = new ExifInterface(imageFile.getAbsolutePath());
- if (VERBOSE) {
- printExifTagsAndValues(fileName, exifInterface);
- }
- compareWithExpectedValue(exifInterface, expectedValue);
+ // Test for reading from various inputs.
+ testExifInterfaceCommon(imageFile, expectedValue);
- // Created via FileDescriptor.
- FileInputStream in = null;
- try {
- in = new FileInputStream(imageFile);
- exifInterface = new ExifInterface(in.getFD());
- if (VERBOSE) {
- printExifTagsAndValues(fileName, exifInterface);
- }
- compareWithExpectedValue(exifInterface, expectedValue);
- } finally {
- IoUtils.closeQuietly(in);
- }
+ // Since ExifInterface does not support for saving attributes for RAW files, do not test
+ // about writing back in here.
}
public void testReadExifDataFromExifByteOrderIIJpeg() throws Throwable {
@@ -415,4 +414,14 @@
public void testReadExifDataFromLgG4Iso800Dng() throws Throwable {
testExifInterfaceForRaw(LG_G4_ISO_800_DNG, R.array.lg_g4_iso_800_dng);
}
+
+ public void testCorruptedImage() {
+ byte[] bytes = new byte[1024];
+ try {
+ new ExifInterface(new ByteArrayInputStream(bytes));
+ fail("Should not reach here!");
+ } catch (IOException e) {
+ // Success
+ }
+ }
}