1a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson/* 2a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson * Copyright (C) 2016 The Android Open Source Project 3a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson * 4a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson * Licensed under the Apache License, Version 2.0 (the "License"); 5a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson * you may not use this file except in compliance with the License. 6a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson * You may obtain a copy of the License at 7a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson * 8a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson * http://www.apache.org/licenses/LICENSE-2.0 9a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson * 10a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson * Unless required by applicable law or agreed to in writing, software 11a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson * distributed under the License is distributed on an "AS IS" BASIS, 12a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson * See the License for the specific language governing permissions and 14a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson * limitations under the License. 15a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson */ 16a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson 17a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson#include "Exif.h" 18a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson 19a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson#define LOG_NDEBUG 0 20a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson#define LOG_TAG "EmulatedCamera_Exif" 21a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson#include <cutils/log.h> 22a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson 23a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson#include <inttypes.h> 24a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson#include <math.h> 25a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson#include <stdint.h> 26a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson 27a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson#include <camera/CameraParameters.h> 28a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson#include <libexif/exif-data.h> 29a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson#include <libexif/exif-entry.h> 30a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson#include <libexif/exif-ifd.h> 31a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson#include <libexif/exif-tag.h> 32a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson 33a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson#include <string> 34a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson#include <vector> 35a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson 36248ebd6652f632d81c43d79a535be0680ae9ee33Bjoern Johansson// For GPS timestamping we want to ensure we use a 64-bit time_t, 32-bit 37248ebd6652f632d81c43d79a535be0680ae9ee33Bjoern Johansson// platforms have time64_t but 64-bit platforms do not. 38248ebd6652f632d81c43d79a535be0680ae9ee33Bjoern Johansson#if defined(__LP64__) 39248ebd6652f632d81c43d79a535be0680ae9ee33Bjoern Johansson#include <time.h> 40248ebd6652f632d81c43d79a535be0680ae9ee33Bjoern Johanssonusing Timestamp = time_t; 41248ebd6652f632d81c43d79a535be0680ae9ee33Bjoern Johansson#define TIMESTAMP_TO_TM(timestamp, tm) gmtime_r(timestamp, tm) 42248ebd6652f632d81c43d79a535be0680ae9ee33Bjoern Johansson#else 43248ebd6652f632d81c43d79a535be0680ae9ee33Bjoern Johansson#include <time64.h> 44248ebd6652f632d81c43d79a535be0680ae9ee33Bjoern Johanssonusing Timestamp = time64_t; 45248ebd6652f632d81c43d79a535be0680ae9ee33Bjoern Johansson#define TIMESTAMP_TO_TM(timestamp, tm) gmtime64_r(timestamp, tm) 46248ebd6652f632d81c43d79a535be0680ae9ee33Bjoern Johansson#endif 47248ebd6652f632d81c43d79a535be0680ae9ee33Bjoern Johansson 48a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johanssonnamespace android { 49a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson 50a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson// A prefix that is used for tags with the "undefined" format to indicate that 51a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson// the contents are ASCII encoded. See the user comment section of the EXIF spec 52a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson// for more details http://www.exif.org/Exif2-2.PDF 53a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johanssonstatic const unsigned char kAsciiPrefix[] = { 54a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson 0x41, 0x53, 0x43, 0x49, 0x49, 0x00, 0x00, 0x00 // "ASCII\0\0\0" 55a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson}; 56a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson 57b013ab25d76ab91e08236824a06bde64241b1539Bjoern Johansson// Remove an existing EXIF entry from |exifData| if it exists. This is useful 58b013ab25d76ab91e08236824a06bde64241b1539Bjoern Johansson// when replacing existing data, it's easier to just remove the data and 59b013ab25d76ab91e08236824a06bde64241b1539Bjoern Johansson// re-allocate it than to adjust the amount of allocated data. 60b013ab25d76ab91e08236824a06bde64241b1539Bjoern Johanssonstatic void removeExistingEntry(ExifData* exifData, ExifIfd ifd, int tag) { 61b013ab25d76ab91e08236824a06bde64241b1539Bjoern Johansson ExifEntry* entry = exif_content_get_entry(exifData->ifd[ifd], 62b013ab25d76ab91e08236824a06bde64241b1539Bjoern Johansson static_cast<ExifTag>(tag)); 63b013ab25d76ab91e08236824a06bde64241b1539Bjoern Johansson if (entry) { 64b013ab25d76ab91e08236824a06bde64241b1539Bjoern Johansson exif_content_remove_entry(exifData->ifd[ifd], entry); 65b013ab25d76ab91e08236824a06bde64241b1539Bjoern Johansson } 66b013ab25d76ab91e08236824a06bde64241b1539Bjoern Johansson} 67b013ab25d76ab91e08236824a06bde64241b1539Bjoern Johansson 68a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johanssonstatic ExifEntry* allocateEntry(int tag, 69a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson ExifFormat format, 70a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson unsigned int numComponents) { 71a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson ExifMem* mem = exif_mem_new_default(); 72a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson ExifEntry* entry = exif_entry_new_mem(mem); 73a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson 74a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson unsigned int size = numComponents * exif_format_get_size(format); 75a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson entry->data = reinterpret_cast<unsigned char*>(exif_mem_alloc(mem, size)); 76a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson entry->size = size; 77a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson entry->tag = static_cast<ExifTag>(tag); 78a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson entry->components = numComponents; 79a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson entry->format = format; 80a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson 81a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson exif_mem_unref(mem); 82a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson return entry; 83a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson} 84a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson 85a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson// Create an entry and place it in |exifData|, the entry is initialized with an 86a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson// array of floats from |values| 87a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johanssontemplate<size_t N> 88a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johanssonstatic bool createEntry(ExifData* exifData, 89a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson ExifIfd ifd, 90a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson int tag, 91a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson const float (&values)[N], 92a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson float denominator = 1000.0) { 93b013ab25d76ab91e08236824a06bde64241b1539Bjoern Johansson removeExistingEntry(exifData, ifd, tag); 94a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson ExifByteOrder byteOrder = exif_data_get_byte_order(exifData); 95a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson ExifEntry* entry = allocateEntry(tag, EXIF_FORMAT_RATIONAL, N); 96a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson exif_content_add_entry(exifData->ifd[ifd], entry); 97a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson unsigned int rationalSize = exif_format_get_size(EXIF_FORMAT_RATIONAL); 98a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson for (size_t i = 0; i < N; ++i) { 99a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson ExifRational rational = { 100a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson static_cast<uint32_t>(values[i] * denominator), 101a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson static_cast<uint32_t>(denominator) 102a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson }; 103a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson 104a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson exif_set_rational(&entry->data[i * rationalSize], byteOrder, rational); 105a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson } 106a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson 107a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson // Unref entry after changing owner to the ExifData struct 108a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson exif_entry_unref(entry); 109a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson return true; 110a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson} 111a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson 112a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson// Create an entry with a single float |value| in it and place it in |exifData| 113a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johanssonstatic bool createEntry(ExifData* exifData, 114a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson ExifIfd ifd, 115a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson int tag, 116a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson const float value, 117a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson float denominator = 1000.0) { 118a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson float values[1] = { value }; 119a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson // Recycling functions is good for the environment 120a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson return createEntry(exifData, ifd, tag, values, denominator); 121a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson} 122a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson 123a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson// Create an entry and place it in |exifData|, the entry contains the raw data 124a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson// pointed to by |data| of length |size|. 125a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johanssonstatic bool createEntry(ExifData* exifData, 126a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson ExifIfd ifd, 127a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson int tag, 128a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson const unsigned char* data, 129a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson size_t size, 130a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson ExifFormat format = EXIF_FORMAT_UNDEFINED) { 131b013ab25d76ab91e08236824a06bde64241b1539Bjoern Johansson removeExistingEntry(exifData, ifd, tag); 132a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson ExifEntry* entry = allocateEntry(tag, format, size); 133a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson memcpy(entry->data, data, size); 134a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson exif_content_add_entry(exifData->ifd[ifd], entry); 135a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson // Unref entry after changing owner to the ExifData struct 136a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson exif_entry_unref(entry); 137a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson return true; 138a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson} 139a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson 140a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson// Create an entry and place it in |exifData|, the entry is initialized with 141a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson// the string provided in |value| 142a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johanssonstatic bool createEntry(ExifData* exifData, 143a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson ExifIfd ifd, 144a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson int tag, 145a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson const char* value) { 146a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson unsigned int length = strlen(value) + 1; 147a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson const unsigned char* data = reinterpret_cast<const unsigned char*>(value); 148a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson return createEntry(exifData, ifd, tag, data, length, EXIF_FORMAT_ASCII); 149a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson} 150a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson 151a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson// Create an entry and place it in |exifData|, the entry is initialized with a 152a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson// single byte in |value| 153a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johanssonstatic bool createEntry(ExifData* exifData, 154a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson ExifIfd ifd, 155a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson int tag, 156a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson uint8_t value) { 157a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson return createEntry(exifData, ifd, tag, &value, 1, EXIF_FORMAT_BYTE); 158a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson} 159a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson 160a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson// Create an entry and place it in |exifData|, the entry is default initialized 161a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson// by the exif library based on |tag| 162a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johanssonstatic bool createEntry(ExifData* exifData, 163a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson ExifIfd ifd, 164a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson int tag) { 165b013ab25d76ab91e08236824a06bde64241b1539Bjoern Johansson removeExistingEntry(exifData, ifd, tag); 166a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson ExifEntry* entry = exif_entry_new(); 167a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson exif_content_add_entry(exifData->ifd[ifd], entry); 168a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson exif_entry_initialize(entry, static_cast<ExifTag>(tag)); 169a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson // Unref entry after changing owner to the ExifData struct 170a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson exif_entry_unref(entry); 171a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson return true; 172a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson} 173a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson 174b013ab25d76ab91e08236824a06bde64241b1539Bjoern Johansson// Create an entry with a single EXIF LONG (32-bit value) and place it in 175b013ab25d76ab91e08236824a06bde64241b1539Bjoern Johansson// |exifData|. 176b013ab25d76ab91e08236824a06bde64241b1539Bjoern Johanssonstatic bool createEntry(ExifData* exifData, 177b013ab25d76ab91e08236824a06bde64241b1539Bjoern Johansson ExifIfd ifd, 178b013ab25d76ab91e08236824a06bde64241b1539Bjoern Johansson int tag, 179b013ab25d76ab91e08236824a06bde64241b1539Bjoern Johansson int value) { 180b013ab25d76ab91e08236824a06bde64241b1539Bjoern Johansson removeExistingEntry(exifData, ifd, tag); 181b013ab25d76ab91e08236824a06bde64241b1539Bjoern Johansson ExifByteOrder byteOrder = exif_data_get_byte_order(exifData); 182b013ab25d76ab91e08236824a06bde64241b1539Bjoern Johansson ExifEntry* entry = allocateEntry(tag, EXIF_FORMAT_LONG, 1); 183b013ab25d76ab91e08236824a06bde64241b1539Bjoern Johansson exif_content_add_entry(exifData->ifd[ifd], entry); 184b013ab25d76ab91e08236824a06bde64241b1539Bjoern Johansson exif_set_long(entry->data, byteOrder, value); 185b013ab25d76ab91e08236824a06bde64241b1539Bjoern Johansson 186b013ab25d76ab91e08236824a06bde64241b1539Bjoern Johansson // Unref entry after changing owner to the ExifData struct 187b013ab25d76ab91e08236824a06bde64241b1539Bjoern Johansson exif_entry_unref(entry); 188b013ab25d76ab91e08236824a06bde64241b1539Bjoern Johansson return true; 189b013ab25d76ab91e08236824a06bde64241b1539Bjoern Johansson} 190b013ab25d76ab91e08236824a06bde64241b1539Bjoern Johansson 191a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johanssonstatic bool getCameraParam(const CameraParameters& parameters, 192a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson const char* parameterKey, 193a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson const char** outValue) { 194a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson const char* value = parameters.get(parameterKey); 195a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson if (value) { 196a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson *outValue = value; 197a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson return true; 198a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson } 199a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson return false; 200a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson} 201a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson 202a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johanssonstatic bool getCameraParam(const CameraParameters& parameters, 203a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson const char* parameterKey, 204a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson float* outValue) { 205a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson const char* value = parameters.get(parameterKey); 206a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson if (value) { 207a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson *outValue = parameters.getFloat(parameterKey); 208a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson return true; 209a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson } 210a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson return false; 211a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson} 212a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson 213a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johanssonstatic bool getCameraParam(const CameraParameters& parameters, 214a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson const char* parameterKey, 215a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson int64_t* outValue) { 216a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson const char* value = parameters.get(parameterKey); 217a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson if (value) { 218a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson char dummy = 0; 219a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson // Attempt to scan an extra character and then make sure it was not 220a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson // scanned by checking that the return value indicates only one item. 221a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson // This way we fail on any trailing characters 222a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson if (sscanf(value, "%" SCNd64 "%c", outValue, &dummy) == 1) { 223a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson return true; 224a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson } 225a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson } 226a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson return false; 227a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson} 228a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson 229a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson// Convert a GPS coordinate represented as a decimal degree value to sexagesimal 230a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson// GPS coordinates comprised of <degrees> <minutes>' <seconds>" 231a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johanssonstatic void convertGpsCoordinate(float degrees, float (*result)[3]) { 232a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson float absDegrees = fabs(degrees); 233a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson // First value is degrees without any decimal digits 234a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson (*result)[0] = floor(absDegrees); 235a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson 236a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson // Subtract degrees so we only have the fraction left, then multiply by 237a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson // 60 to get the minutes 238a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson float minutes = (absDegrees - (*result)[0]) * 60.0f; 239a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson (*result)[1] = floor(minutes); 240a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson 241a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson // Same thing for seconds but here we store seconds with the fraction 242a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson float seconds = (minutes - (*result)[1]) * 60.0f; 243a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson (*result)[2] = seconds; 244a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson} 245a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson 246a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson// Convert a UNIX epoch timestamp to a timestamp comprised of three floats for 247a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson// hour, minute and second, and a date part that is represented as a string. 248a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johanssonstatic bool convertTimestampToTimeAndDate(int64_t timestamp, 249a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson float (*timeValues)[3], 250a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson std::string* date) { 251248ebd6652f632d81c43d79a535be0680ae9ee33Bjoern Johansson Timestamp time = timestamp; 252a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson struct tm utcTime; 253248ebd6652f632d81c43d79a535be0680ae9ee33Bjoern Johansson if (TIMESTAMP_TO_TM(&time, &utcTime) == nullptr) { 254a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson ALOGE("Could not decompose timestamp into components"); 255a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson return false; 256a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson } 257a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson (*timeValues)[0] = utcTime.tm_hour; 258a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson (*timeValues)[1] = utcTime.tm_min; 259a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson (*timeValues)[2] = utcTime.tm_sec; 260a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson 261a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson char buffer[64] = {}; 262a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson if (strftime(buffer, sizeof(buffer), "%Y:%m:%d", &utcTime) == 0) { 263a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson ALOGE("Could not construct date string from timestamp"); 264a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson return false; 265a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson } 266a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson *date = buffer; 267a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson return true; 268a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson} 269a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson 270a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern JohanssonExifData* createExifData(const CameraParameters& params) { 271a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson ExifData* exifData = exif_data_new(); 272a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson 273a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson exif_data_set_option(exifData, EXIF_DATA_OPTION_FOLLOW_SPECIFICATION); 274a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson exif_data_set_data_type(exifData, EXIF_DATA_TYPE_COMPRESSED); 275a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson exif_data_set_byte_order(exifData, EXIF_BYTE_ORDER_INTEL); 276a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson 277a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson // Create mandatory exif fields and set their default values 278a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson exif_data_fix(exifData); 279a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson 280a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson float triplet[3]; 281a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson float floatValue = 0.0f; 282a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson const char* stringValue; 283a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson 284a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson // Datetime, creating and initializing a datetime tag will automatically 285a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson // set the current date and time in the tag so just do that. 286a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson createEntry(exifData, EXIF_IFD_0, EXIF_TAG_DATE_TIME); 287a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson 288b013ab25d76ab91e08236824a06bde64241b1539Bjoern Johansson // Picture size 289b013ab25d76ab91e08236824a06bde64241b1539Bjoern Johansson int width = -1, height = -1; 290b013ab25d76ab91e08236824a06bde64241b1539Bjoern Johansson params.getPictureSize(&width, &height); 291b013ab25d76ab91e08236824a06bde64241b1539Bjoern Johansson if (width >= 0 && height >= 0) { 292b013ab25d76ab91e08236824a06bde64241b1539Bjoern Johansson createEntry(exifData, EXIF_IFD_EXIF, 293b013ab25d76ab91e08236824a06bde64241b1539Bjoern Johansson EXIF_TAG_PIXEL_X_DIMENSION, width); 294b013ab25d76ab91e08236824a06bde64241b1539Bjoern Johansson createEntry(exifData, EXIF_IFD_EXIF, 295b013ab25d76ab91e08236824a06bde64241b1539Bjoern Johansson EXIF_TAG_PIXEL_Y_DIMENSION, height); 296b013ab25d76ab91e08236824a06bde64241b1539Bjoern Johansson } 297a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson // Focal length 298a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson if (getCameraParam(params, 299a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson CameraParameters::KEY_FOCAL_LENGTH, 300a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson &floatValue)) { 301a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson createEntry(exifData, EXIF_IFD_EXIF, EXIF_TAG_FOCAL_LENGTH, floatValue); 302a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson } 303a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson // GPS latitude and reference, reference indicates sign, store unsigned 304a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson if (getCameraParam(params, 305a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson CameraParameters::KEY_GPS_LATITUDE, 306a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson &floatValue)) { 307a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson convertGpsCoordinate(floatValue, &triplet); 308a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson createEntry(exifData, EXIF_IFD_GPS, EXIF_TAG_GPS_LATITUDE, triplet); 309a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson 310a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson const char* ref = floatValue < 0.0f ? "S" : "N"; 311a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson createEntry(exifData, EXIF_IFD_GPS, EXIF_TAG_GPS_LATITUDE_REF, ref); 312a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson } 313a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson // GPS longitude and reference, reference indicates sign, store unsigned 314a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson if (getCameraParam(params, 315a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson CameraParameters::KEY_GPS_LONGITUDE, 316a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson &floatValue)) { 317a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson convertGpsCoordinate(floatValue, &triplet); 318a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson createEntry(exifData, EXIF_IFD_GPS, EXIF_TAG_GPS_LONGITUDE, triplet); 319a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson 320a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson const char* ref = floatValue < 0.0f ? "W" : "E"; 321a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson createEntry(exifData, EXIF_IFD_GPS, EXIF_TAG_GPS_LONGITUDE_REF, ref); 322a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson } 323a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson // GPS altitude and reference, reference indicates sign, store unsigned 324a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson if (getCameraParam(params, 325a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson CameraParameters::KEY_GPS_ALTITUDE, 326a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson &floatValue)) { 327a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson createEntry(exifData, EXIF_IFD_GPS, EXIF_TAG_GPS_ALTITUDE, 328a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson static_cast<float>(fabs(floatValue))); 329a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson 330a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson // 1 indicated below sea level, 0 indicates above sea level 331a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson uint8_t ref = floatValue < 0.0f ? 1 : 0; 332a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson createEntry(exifData, EXIF_IFD_GPS, EXIF_TAG_GPS_ALTITUDE_REF, ref); 333a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson } 334a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson // GPS timestamp and datestamp 335a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson int64_t timestamp = 0; 336a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson if (getCameraParam(params, 337a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson CameraParameters::KEY_GPS_TIMESTAMP, 338a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson ×tamp)) { 339a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson std::string date; 340a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson if (convertTimestampToTimeAndDate(timestamp, &triplet, &date)) { 341a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson createEntry(exifData, EXIF_IFD_GPS, EXIF_TAG_GPS_TIME_STAMP, 342a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson triplet, 1.0f); 343a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson createEntry(exifData, EXIF_IFD_GPS, EXIF_TAG_GPS_DATE_STAMP, 344a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson date.c_str()); 345a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson } 346a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson } 347a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson 348a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson // GPS processing method 349a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson if (getCameraParam(params, 350a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson CameraParameters::KEY_GPS_PROCESSING_METHOD, 351a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson &stringValue)) { 352a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson std::vector<unsigned char> data; 353a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson // Because this is a tag with an undefined format it has to be prefixed 354a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson // with the encoding type. Insert an ASCII prefix first, then the 355a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson // actual string. Undefined tags do not have to be null terminated. 356a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson data.insert(data.end(), 357a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson std::begin(kAsciiPrefix), 358a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson std::end(kAsciiPrefix)); 359a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson data.insert(data.end(), stringValue, stringValue + strlen(stringValue)); 360a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson createEntry(exifData, EXIF_IFD_GPS, EXIF_TAG_GPS_PROCESSING_METHOD, 361a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson &data[0], data.size()); 362a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson } 363a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson 364a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson return exifData; 365a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson} 366a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson 367a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johanssonvoid freeExifData(ExifData* exifData) { 368a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson exif_data_free(exifData); 369a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson} 370a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson 371a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson} // namespace android 372a11bd58ddc5ce90e7bc66e1cf9c6ed0bad61c376Bjoern Johansson 373