1 /*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 //#define LOG_NDEBUG 0
18 #define LOG_TAG "ItemTable"
19
20 #include <unordered_set>
21
22 #include <ItemTable.h>
23 #include <media/MediaExtractorPluginApi.h>
24 #include <media/MediaExtractorPluginHelper.h>
25 #include <media/stagefright/MetaData.h>
26 #include <media/stagefright/MediaErrors.h>
27 #include <media/stagefright/foundation/ABuffer.h>
28 #include <media/stagefright/foundation/ByteUtils.h>
29 #include <media/stagefright/foundation/hexdump.h>
30 #include <media/stagefright/foundation/MediaDefs.h>
31 #include <utils/Log.h>
32
33 namespace android {
34
35 namespace heif {
36
37 /////////////////////////////////////////////////////////////////////
38 //
39 // struct to keep track of one image item
40 //
41
42 struct ImageItem {
43 friend struct ItemReference;
44 friend struct ItemProperty;
45
ImageItemandroid::heif::ImageItem46 ImageItem() : ImageItem(0, 0, false) {}
ImageItemandroid::heif::ImageItem47 ImageItem(uint32_t _type, uint32_t _id, bool _hidden) :
48 type(_type), itemId(_id), hidden(_hidden),
49 rows(0), columns(0), width(0), height(0), rotation(0),
50 offset(0), size(0), nextTileIndex(0) {}
51
isGridandroid::heif::ImageItem52 bool isGrid() const {
53 return type == FOURCC("grid");
54 }
55
getNextTileItemIdandroid::heif::ImageItem56 status_t getNextTileItemId(uint32_t *nextTileItemId, bool reset) {
57 if (reset) {
58 nextTileIndex = 0;
59 }
60 if (nextTileIndex >= dimgRefs.size()) {
61 return ERROR_END_OF_STREAM;
62 }
63 *nextTileItemId = dimgRefs[nextTileIndex++];
64 return OK;
65 }
66
67 uint32_t type;
68 uint32_t itemId;
69 bool hidden;
70 int32_t rows;
71 int32_t columns;
72 int32_t width;
73 int32_t height;
74 int32_t rotation;
75 off64_t offset;
76 size_t size;
77 sp<ABuffer> hvcc;
78 sp<ABuffer> icc;
79
80 Vector<uint32_t> thumbnails;
81 Vector<uint32_t> dimgRefs;
82 Vector<uint32_t> cdscRefs;
83 size_t nextTileIndex;
84 };
85
86 struct ExifItem {
87 off64_t offset;
88 size_t size;
89 };
90
91 /////////////////////////////////////////////////////////////////////
92 //
93 // ISO boxes
94 //
95
96 struct Box {
97 protected:
Boxandroid::heif::Box98 Box(DataSourceHelper *source, uint32_t type) :
99 mDataSource(source), mType(type) {}
100
~Boxandroid::heif::Box101 virtual ~Box() {}
102
onChunkDataandroid::heif::Box103 virtual status_t onChunkData(
104 uint32_t /*type*/, off64_t /*offset*/, size_t /*size*/) {
105 return OK;
106 }
107
typeandroid::heif::Box108 inline uint32_t type() const { return mType; }
109
sourceandroid::heif::Box110 inline DataSourceHelper *source() const { return mDataSource; }
111
112 status_t parseChunk(off64_t *offset);
113
114 status_t parseChunks(off64_t offset, size_t size);
115
116 private:
117 DataSourceHelper *mDataSource;
118 uint32_t mType;
119 };
120
parseChunk(off64_t * offset)121 status_t Box::parseChunk(off64_t *offset) {
122 if (*offset < 0) {
123 ALOGE("b/23540914");
124 return ERROR_MALFORMED;
125 }
126 uint32_t hdr[2];
127 if (mDataSource->readAt(*offset, hdr, 8) < 8) {
128 return ERROR_IO;
129 }
130 uint64_t chunk_size = ntohl(hdr[0]);
131 int32_t chunk_type = ntohl(hdr[1]);
132 off64_t data_offset = *offset + 8;
133
134 if (chunk_size == 1) {
135 if (mDataSource->readAt(*offset + 8, &chunk_size, 8) < 8) {
136 return ERROR_IO;
137 }
138 chunk_size = ntoh64(chunk_size);
139 data_offset += 8;
140
141 if (chunk_size < 16) {
142 // The smallest valid chunk is 16 bytes long in this case.
143 return ERROR_MALFORMED;
144 }
145 } else if (chunk_size == 0) {
146 // This shouldn't happen since we should never be top level
147 ALOGE("invalid chunk size 0 for non-top level box");
148 return ERROR_MALFORMED;
149 } else if (chunk_size < 8) {
150 // The smallest valid chunk is 8 bytes long.
151 ALOGE("invalid chunk size: %lld", (long long)chunk_size);
152 return ERROR_MALFORMED;
153 }
154
155 char chunk[5];
156 MakeFourCCString(chunk_type, chunk);
157 ALOGV("chunk: %s @ %lld", chunk, (long long)*offset);
158
159 off64_t chunk_data_size = chunk_size - (data_offset - *offset);
160 if (chunk_data_size < 0) {
161 ALOGE("b/23540914");
162 return ERROR_MALFORMED;
163 }
164
165 status_t err = onChunkData(chunk_type, data_offset, chunk_data_size);
166
167 if (err != OK) {
168 return err;
169 }
170 *offset += chunk_size;
171 return OK;
172 }
173
parseChunks(off64_t offset,size_t size)174 status_t Box::parseChunks(off64_t offset, size_t size) {
175 off64_t stopOffset = offset + size;
176 while (offset < stopOffset) {
177 status_t err = parseChunk(&offset);
178 if (err != OK) {
179 return err;
180 }
181 }
182 if (offset != stopOffset) {
183 return ERROR_MALFORMED;
184 }
185 return OK;
186 }
187
188 ///////////////////////////////////////////////////////////////////////
189
190 struct FullBox : public Box {
191 protected:
FullBoxandroid::heif::FullBox192 FullBox(DataSourceHelper *source, uint32_t type) :
193 Box(source, type), mVersion(0), mFlags(0) {}
194
versionandroid::heif::FullBox195 inline uint8_t version() const { return mVersion; }
196
flagsandroid::heif::FullBox197 inline uint32_t flags() const { return mFlags; }
198
199 status_t parseFullBoxHeader(off64_t *offset, size_t *size);
200
201 private:
202 uint8_t mVersion;
203 uint32_t mFlags;
204 };
205
parseFullBoxHeader(off64_t * offset,size_t * size)206 status_t FullBox::parseFullBoxHeader(off64_t *offset, size_t *size) {
207 if (*size < 4) {
208 return ERROR_MALFORMED;
209 }
210 if (!source()->readAt(*offset, &mVersion, 1)) {
211 return ERROR_IO;
212 }
213 if (!source()->getUInt24(*offset + 1, &mFlags)) {
214 return ERROR_IO;
215 }
216 *offset += 4;
217 *size -= 4;
218 return OK;
219 }
220
221 /////////////////////////////////////////////////////////////////////
222 //
223 // PrimaryImage box
224 //
225
226 struct PitmBox : public FullBox {
PitmBoxandroid::heif::PitmBox227 PitmBox(DataSourceHelper *source) :
228 FullBox(source, FOURCC("pitm")) {}
229
230 status_t parse(off64_t offset, size_t size, uint32_t *primaryItemId);
231 };
232
parse(off64_t offset,size_t size,uint32_t * primaryItemId)233 status_t PitmBox::parse(off64_t offset, size_t size, uint32_t *primaryItemId) {
234 status_t err = parseFullBoxHeader(&offset, &size);
235 if (err != OK) {
236 return err;
237 }
238
239 size_t itemIdSize = (version() == 0) ? 2 : 4;
240 if (size < itemIdSize) {
241 return ERROR_MALFORMED;
242 }
243 uint32_t itemId;
244 if (!source()->getUInt32Var(offset, &itemId, itemIdSize)) {
245 return ERROR_IO;
246 }
247
248 ALOGV("primary id %d", itemId);
249 *primaryItemId = itemId;
250
251 return OK;
252 }
253
254 /////////////////////////////////////////////////////////////////////
255 //
256 // ItemLocation related boxes
257 //
258
259 struct ExtentEntry {
260 uint64_t extentIndex;
261 uint64_t extentOffset;
262 uint64_t extentLength;
263 };
264
265 struct ItemLoc {
ItemLocandroid::heif::ItemLoc266 ItemLoc() : ItemLoc(0, 0, 0, 0) {}
ItemLocandroid::heif::ItemLoc267 ItemLoc(uint32_t item_id, uint16_t construction_method,
268 uint16_t data_reference_index, uint64_t base_offset) :
269 itemId(item_id),
270 constructionMethod(construction_method),
271 dataReferenceIndex(data_reference_index),
272 baseOffset(base_offset) {}
273
addExtentandroid::heif::ItemLoc274 void addExtent(const ExtentEntry& extent) {
275 extents.push_back(extent);
276 }
277
getLocandroid::heif::ItemLoc278 status_t getLoc(off64_t *offset, size_t *size,
279 off64_t idatOffset, size_t idatSize) const {
280 // TODO: fix extent handling, fix constructionMethod = 2
281 CHECK(extents.size() == 1);
282 if (constructionMethod == 0) {
283 *offset = baseOffset + extents[0].extentOffset;
284 *size = extents[0].extentLength;
285 return OK;
286 } else if (constructionMethod == 1) {
287 if (baseOffset + extents[0].extentOffset + extents[0].extentLength
288 > idatSize) {
289 return ERROR_MALFORMED;
290 }
291 *offset = baseOffset + extents[0].extentOffset + idatOffset;
292 *size = extents[0].extentLength;
293 return OK;
294 }
295 return ERROR_UNSUPPORTED;
296 }
297
298 // parsed info
299 uint32_t itemId;
300 uint16_t constructionMethod;
301 uint16_t dataReferenceIndex;
302 off64_t baseOffset;
303 Vector<ExtentEntry> extents;
304 };
305
306 struct IlocBox : public FullBox {
IlocBoxandroid::heif::IlocBox307 IlocBox(DataSourceHelper *source, KeyedVector<uint32_t, ItemLoc> *itemLocs) :
308 FullBox(source, FOURCC("iloc")),
309 mItemLocs(itemLocs), mHasConstructMethod1(false) {}
310
311 status_t parse(off64_t offset, size_t size);
312
hasConstructMethod1android::heif::IlocBox313 bool hasConstructMethod1() { return mHasConstructMethod1; }
314
315 private:
isSizeFieldValidandroid::heif::IlocBox316 static bool isSizeFieldValid(uint32_t offset_size) {
317 return offset_size == 0 || offset_size == 4 || offset_size == 8;
318 }
319 KeyedVector<uint32_t, ItemLoc> *mItemLocs;
320 bool mHasConstructMethod1;
321 };
322
parse(off64_t offset,size_t size)323 status_t IlocBox::parse(off64_t offset, size_t size) {
324 status_t err = parseFullBoxHeader(&offset, &size);
325 if (err != OK) {
326 return err;
327 }
328 if (version() > 2) {
329 ALOGE("%s: invalid version %d", __FUNCTION__, version());
330 return ERROR_MALFORMED;
331 }
332
333 if (size < 2) {
334 return ERROR_MALFORMED;
335 }
336 uint8_t offset_size;
337 if (!source()->readAt(offset++, &offset_size, 1)) {
338 return ERROR_IO;
339 }
340 uint8_t length_size = (offset_size & 0xF);
341 offset_size >>= 4;
342
343 uint8_t base_offset_size;
344 if (!source()->readAt(offset++, &base_offset_size, 1)) {
345 return ERROR_IO;
346 }
347 uint8_t index_size = 0;
348 if (version() == 1 || version() == 2) {
349 index_size = (base_offset_size & 0xF);
350 }
351 base_offset_size >>= 4;
352 size -= 2;
353
354 if (!isSizeFieldValid(offset_size)
355 || !isSizeFieldValid(length_size)
356 || !isSizeFieldValid(base_offset_size)
357 || !isSizeFieldValid((index_size))) {
358 ALOGE("%s: offset size not valid: %d, %d, %d, %d", __FUNCTION__,
359 offset_size, length_size, base_offset_size, index_size);
360 return ERROR_MALFORMED;
361 }
362
363 uint32_t item_count;
364 size_t itemFieldSize = version() < 2 ? 2 : 4;
365 if (size < itemFieldSize) {
366 return ERROR_MALFORMED;
367 }
368 if (!source()->getUInt32Var(offset, &item_count, itemFieldSize)) {
369 return ERROR_IO;
370 }
371
372 ALOGV("item_count %lld", (long long) item_count);
373 offset += itemFieldSize;
374 size -= itemFieldSize;
375
376 for (size_t i = 0; i < item_count; i++) {
377 uint32_t item_id;
378 if (!source()->getUInt32Var(offset, &item_id, itemFieldSize)) {
379 return ERROR_IO;
380 }
381 ALOGV("item[%zu]: id %lld", i, (long long)item_id);
382 offset += itemFieldSize;
383
384 uint8_t construction_method = 0;
385 if (version() == 1 || version() == 2) {
386 uint8_t buf[2];
387 if (!source()->readAt(offset, buf, 2)) {
388 return ERROR_IO;
389 }
390 construction_method = (buf[1] & 0xF);
391 ALOGV("construction_method %d", construction_method);
392 if (construction_method == 1) {
393 mHasConstructMethod1 = true;
394 }
395
396 offset += 2;
397 }
398
399 uint16_t data_reference_index;
400 if (!source()->getUInt16(offset, &data_reference_index)) {
401 return ERROR_IO;
402 }
403 ALOGV("data_reference_index %d", data_reference_index);
404 if (data_reference_index != 0) {
405 // we don't support reference to other files
406 return ERROR_UNSUPPORTED;
407 }
408 offset += 2;
409
410 uint64_t base_offset = 0;
411 if (base_offset_size != 0) {
412 if (!source()->getUInt64Var(offset, &base_offset, base_offset_size)) {
413 return ERROR_IO;
414 }
415 offset += base_offset_size;
416 }
417 ALOGV("base_offset %lld", (long long) base_offset);
418
419 ssize_t index = mItemLocs->add(item_id, ItemLoc(
420 item_id, construction_method, data_reference_index, base_offset));
421 ItemLoc &item = mItemLocs->editValueAt(index);
422
423 uint16_t extent_count;
424 if (!source()->getUInt16(offset, &extent_count)) {
425 return ERROR_IO;
426 }
427 ALOGV("extent_count %d", extent_count);
428
429 if (extent_count > 1) {
430 return ERROR_UNSUPPORTED;
431 }
432 offset += 2;
433
434 for (size_t j = 0; j < extent_count; j++) {
435 uint64_t extent_index = 1; // default=1
436 if ((version() == 1 || version() == 2) && (index_size > 0)) {
437 if (!source()->getUInt64Var(offset, &extent_index, index_size)) {
438 return ERROR_IO;
439 }
440 // TODO: add support for this mode
441 offset += index_size;
442 ALOGV("extent_index %lld", (long long)extent_index);
443 }
444
445 uint64_t extent_offset = 0; // default=0
446 if (offset_size > 0) {
447 if (!source()->getUInt64Var(offset, &extent_offset, offset_size)) {
448 return ERROR_IO;
449 }
450 offset += offset_size;
451 }
452 ALOGV("extent_offset %lld", (long long)extent_offset);
453
454 uint64_t extent_length = 0; // this indicates full length of file
455 if (length_size > 0) {
456 if (!source()->getUInt64Var(offset, &extent_length, length_size)) {
457 return ERROR_IO;
458 }
459 offset += length_size;
460 }
461 ALOGV("extent_length %lld", (long long)extent_length);
462
463 item.addExtent({ extent_index, extent_offset, extent_length });
464 }
465 }
466 return OK;
467 }
468
469 /////////////////////////////////////////////////////////////////////
470 //
471 // ItemReference related boxes
472 //
473
474 struct ItemReference : public Box, public RefBase {
ItemReferenceandroid::heif::ItemReference475 ItemReference(DataSourceHelper *source, uint32_t type, uint32_t itemIdSize) :
476 Box(source, type), mItemId(0), mRefIdSize(itemIdSize) {}
477
478 status_t parse(off64_t offset, size_t size);
479
itemIdandroid::heif::ItemReference480 uint32_t itemId() { return mItemId; }
481
482 void apply(
483 KeyedVector<uint32_t, ImageItem> &itemIdToItemMap,
484 KeyedVector<uint32_t, ExifItem> &itemIdToExifMap) const;
485
486 private:
487 uint32_t mItemId;
488 uint32_t mRefIdSize;
489 Vector<uint32_t> mRefs;
490
491 DISALLOW_EVIL_CONSTRUCTORS(ItemReference);
492 };
493
apply(KeyedVector<uint32_t,ImageItem> & itemIdToItemMap,KeyedVector<uint32_t,ExifItem> & itemIdToExifMap) const494 void ItemReference::apply(
495 KeyedVector<uint32_t, ImageItem> &itemIdToItemMap,
496 KeyedVector<uint32_t, ExifItem> &itemIdToExifMap) const {
497 ALOGV("attach reference type 0x%x to item id %d)", type(), mItemId);
498
499 switch(type()) {
500 case FOURCC("dimg"): {
501 ssize_t itemIndex = itemIdToItemMap.indexOfKey(mItemId);
502
503 // ignore non-image items
504 if (itemIndex < 0) {
505 return;
506 }
507
508 ImageItem &derivedImage = itemIdToItemMap.editValueAt(itemIndex);
509 if (!derivedImage.dimgRefs.empty()) {
510 ALOGW("dimgRefs not clean!");
511 }
512 derivedImage.dimgRefs.appendVector(mRefs);
513
514 for (size_t i = 0; i < mRefs.size(); i++) {
515 itemIndex = itemIdToItemMap.indexOfKey(mRefs[i]);
516
517 // ignore non-image items
518 if (itemIndex < 0) {
519 continue;
520 }
521 ImageItem &sourceImage = itemIdToItemMap.editValueAt(itemIndex);
522
523 // mark the source image of the derivation as hidden
524 sourceImage.hidden = true;
525 }
526 break;
527 }
528 case FOURCC("thmb"): {
529 ssize_t itemIndex = itemIdToItemMap.indexOfKey(mItemId);
530
531 // ignore non-image items
532 if (itemIndex < 0) {
533 return;
534 }
535
536 // mark thumbnail image as hidden, these can be retrieved if the client
537 // request thumbnail explicitly, but won't be exposed as displayables.
538 ImageItem &thumbImage = itemIdToItemMap.editValueAt(itemIndex);
539 thumbImage.hidden = true;
540
541 for (size_t i = 0; i < mRefs.size(); i++) {
542 itemIndex = itemIdToItemMap.indexOfKey(mRefs[i]);
543
544 // ignore non-image items
545 if (itemIndex < 0) {
546 continue;
547 }
548 ALOGV("Image item id %d uses thumbnail item id %d", mRefs[i], mItemId);
549 ImageItem &imageItem = itemIdToItemMap.editValueAt(itemIndex);
550 if (!imageItem.thumbnails.empty()) {
551 ALOGW("already has thumbnails!");
552 }
553 imageItem.thumbnails.push_back(mItemId);
554 }
555 break;
556 }
557 case FOURCC("cdsc"): {
558 ssize_t itemIndex = itemIdToExifMap.indexOfKey(mItemId);
559
560 // ignore non-exif block items
561 if (itemIndex < 0) {
562 return;
563 }
564
565 for (size_t i = 0; i < mRefs.size(); i++) {
566 itemIndex = itemIdToItemMap.indexOfKey(mRefs[i]);
567
568 // ignore non-image items
569 if (itemIndex < 0) {
570 continue;
571 }
572 ALOGV("Image item id %d uses metadata item id %d", mRefs[i], mItemId);
573 ImageItem &image = itemIdToItemMap.editValueAt(itemIndex);
574 image.cdscRefs.push_back(mItemId);
575 }
576 break;
577 }
578 case FOURCC("auxl"): {
579 ssize_t itemIndex = itemIdToItemMap.indexOfKey(mItemId);
580
581 // ignore non-image items
582 if (itemIndex < 0) {
583 return;
584 }
585
586 // mark auxiliary image as hidden
587 ImageItem &auxImage = itemIdToItemMap.editValueAt(itemIndex);
588 auxImage.hidden = true;
589 break;
590 }
591 default:
592 ALOGW("ignoring unsupported ref type 0x%x", type());
593 }
594 }
595
parse(off64_t offset,size_t size)596 status_t ItemReference::parse(off64_t offset, size_t size) {
597 if (size < mRefIdSize + 2) {
598 return ERROR_MALFORMED;
599 }
600 if (!source()->getUInt32Var(offset, &mItemId, mRefIdSize)) {
601 return ERROR_IO;
602 }
603 offset += mRefIdSize;
604
605 uint16_t count;
606 if (!source()->getUInt16(offset, &count)) {
607 return ERROR_IO;
608 }
609 offset += 2;
610 size -= (mRefIdSize + 2);
611
612 if (size < count * mRefIdSize) {
613 return ERROR_MALFORMED;
614 }
615
616 for (size_t i = 0; i < count; i++) {
617 uint32_t refItemId;
618 if (!source()->getUInt32Var(offset, &refItemId, mRefIdSize)) {
619 return ERROR_IO;
620 }
621 offset += mRefIdSize;
622 mRefs.push_back(refItemId);
623 ALOGV("item id %d: referencing item id %d", mItemId, refItemId);
624 }
625
626 return OK;
627 }
628
629 struct IrefBox : public FullBox {
IrefBoxandroid::heif::IrefBox630 IrefBox(DataSourceHelper *source, Vector<sp<ItemReference> > *itemRefs) :
631 FullBox(source, FOURCC("iref")), mRefIdSize(0), mItemRefs(itemRefs) {}
632
633 status_t parse(off64_t offset, size_t size);
634
635 protected:
636 status_t onChunkData(uint32_t type, off64_t offset, size_t size) override;
637
638 private:
639 uint32_t mRefIdSize;
640 Vector<sp<ItemReference> > *mItemRefs;
641 };
642
parse(off64_t offset,size_t size)643 status_t IrefBox::parse(off64_t offset, size_t size) {
644 ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
645 status_t err = parseFullBoxHeader(&offset, &size);
646 if (err != OK) {
647 return err;
648 }
649
650 mRefIdSize = (version() == 0) ? 2 : 4;
651 return parseChunks(offset, size);
652 }
653
onChunkData(uint32_t type,off64_t offset,size_t size)654 status_t IrefBox::onChunkData(uint32_t type, off64_t offset, size_t size) {
655 sp<ItemReference> itemRef = new ItemReference(source(), type, mRefIdSize);
656
657 status_t err = itemRef->parse(offset, size);
658 if (err != OK) {
659 return err;
660 }
661 mItemRefs->push_back(itemRef);
662 return OK;
663 }
664
665 /////////////////////////////////////////////////////////////////////
666 //
667 // ItemProperty related boxes
668 //
669
670 struct AssociationEntry {
671 uint32_t itemId;
672 bool essential;
673 uint16_t index;
674 };
675
676 struct ItemProperty : public RefBase {
ItemPropertyandroid::heif::ItemProperty677 ItemProperty() {}
678
attachToandroid::heif::ItemProperty679 virtual void attachTo(ImageItem &/*image*/) const {
680 ALOGW("Unrecognized property");
681 }
parseandroid::heif::ItemProperty682 virtual status_t parse(off64_t /*offset*/, size_t /*size*/) {
683 ALOGW("Unrecognized property");
684 return OK;
685 }
686
687 private:
688 DISALLOW_EVIL_CONSTRUCTORS(ItemProperty);
689 };
690
691 struct IspeBox : public FullBox, public ItemProperty {
IspeBoxandroid::heif::IspeBox692 IspeBox(DataSourceHelper *source) :
693 FullBox(source, FOURCC("ispe")), mWidth(0), mHeight(0) {}
694
695 status_t parse(off64_t offset, size_t size) override;
696
attachToandroid::heif::IspeBox697 void attachTo(ImageItem &image) const override {
698 image.width = mWidth;
699 image.height = mHeight;
700 }
701
702 private:
703 int32_t mWidth;
704 int32_t mHeight;
705 };
706
parse(off64_t offset,size_t size)707 status_t IspeBox::parse(off64_t offset, size_t size) {
708 ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
709
710 status_t err = parseFullBoxHeader(&offset, &size);
711 if (err != OK) {
712 return err;
713 }
714
715 if (size < 8) {
716 return ERROR_MALFORMED;
717 }
718 if (!source()->getUInt32(offset, (uint32_t *)&mWidth)
719 || !source()->getUInt32(offset + 4, (uint32_t *)&mHeight)) {
720 return ERROR_IO;
721 }
722
723 // Validate that the dimension doesn't cause overflow on calculated max input size.
724 // Max input size is width*height*1.5, restrict width*height to 1<<29 so that
725 // we don't need to cast to int64_t when doing mults.
726 if (mWidth <= 0 || mHeight <= 0 || mWidth > (1 << 29) / mHeight) {
727 return ERROR_MALFORMED;
728 }
729
730 ALOGV("property ispe: %dx%d", mWidth, mHeight);
731 return OK;
732 }
733
734 struct HvccBox : public Box, public ItemProperty {
HvccBoxandroid::heif::HvccBox735 HvccBox(DataSourceHelper *source) :
736 Box(source, FOURCC("hvcC")) {}
737
738 status_t parse(off64_t offset, size_t size) override;
739
attachToandroid::heif::HvccBox740 void attachTo(ImageItem &image) const override {
741 image.hvcc = mHVCC;
742 }
743
744 private:
745 sp<ABuffer> mHVCC;
746 };
747
parse(off64_t offset,size_t size)748 status_t HvccBox::parse(off64_t offset, size_t size) {
749 ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
750
751 mHVCC = new ABuffer(size);
752
753 if (mHVCC->data() == NULL) {
754 ALOGE("b/28471206");
755 return NO_MEMORY;
756 }
757
758 if (source()->readAt(offset, mHVCC->data(), size) < (ssize_t)size) {
759 return ERROR_IO;
760 }
761
762 ALOGV("property hvcC");
763
764 return OK;
765 }
766
767 struct IrotBox : public Box, public ItemProperty {
IrotBoxandroid::heif::IrotBox768 IrotBox(DataSourceHelper *source) :
769 Box(source, FOURCC("irot")), mAngle(0) {}
770
771 status_t parse(off64_t offset, size_t size) override;
772
attachToandroid::heif::IrotBox773 void attachTo(ImageItem &image) const override {
774 image.rotation = mAngle * 90;
775 }
776
777 private:
778 uint8_t mAngle;
779 };
780
parse(off64_t offset,size_t size)781 status_t IrotBox::parse(off64_t offset, size_t size) {
782 ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
783
784 if (size < 1) {
785 return ERROR_MALFORMED;
786 }
787 if (source()->readAt(offset, &mAngle, 1) != 1) {
788 return ERROR_IO;
789 }
790 mAngle &= 0x3;
791 ALOGV("property irot: %d", mAngle);
792
793 return OK;
794 }
795
796 struct ColrBox : public Box, public ItemProperty {
ColrBoxandroid::heif::ColrBox797 ColrBox(DataSourceHelper *source) :
798 Box(source, FOURCC("colr")) {}
799
800 status_t parse(off64_t offset, size_t size) override;
801
attachToandroid::heif::ColrBox802 void attachTo(ImageItem &image) const override {
803 image.icc = mICCData;
804 }
805
806 private:
807 sp<ABuffer> mICCData;
808 };
809
parse(off64_t offset,size_t size)810 status_t ColrBox::parse(off64_t offset, size_t size) {
811 ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
812
813 if (size < 4) {
814 return ERROR_MALFORMED;
815 }
816 uint32_t colour_type;
817 if (!source()->getUInt32(offset, &colour_type)) {
818 return ERROR_IO;
819 }
820 offset += 4;
821 size -= 4;
822 if (colour_type == FOURCC("nclx")) {
823 return OK;
824 }
825 if ((colour_type != FOURCC("rICC")) &&
826 (colour_type != FOURCC("prof"))) {
827 return ERROR_MALFORMED;
828 }
829
830 mICCData = new ABuffer(size);
831 if (mICCData->data() == NULL) {
832 ALOGE("b/28471206");
833 return NO_MEMORY;
834 }
835
836 if (source()->readAt(offset, mICCData->data(), size) != (ssize_t)size) {
837 return ERROR_IO;
838 }
839
840 ALOGV("property Colr: size %zd", size);
841 return OK;
842 }
843
844 struct IpmaBox : public FullBox {
IpmaBoxandroid::heif::IpmaBox845 IpmaBox(DataSourceHelper *source, Vector<AssociationEntry> *associations) :
846 FullBox(source, FOURCC("ipma")), mAssociations(associations) {}
847
848 status_t parse(off64_t offset, size_t size);
849 private:
850 Vector<AssociationEntry> *mAssociations;
851 };
852
parse(off64_t offset,size_t size)853 status_t IpmaBox::parse(off64_t offset, size_t size) {
854 status_t err = parseFullBoxHeader(&offset, &size);
855 if (err != OK) {
856 return err;
857 }
858
859 if (size < 4) {
860 return ERROR_MALFORMED;
861 }
862 uint32_t entryCount;
863 if (!source()->getUInt32(offset, &entryCount)) {
864 return ERROR_IO;
865 }
866 offset += 4;
867 size -= 4;
868
869 for (size_t k = 0; k < entryCount; ++k) {
870 uint32_t itemId = 0;
871 size_t itemIdSize = (version() < 1) ? 2 : 4;
872
873 if (size < itemIdSize + 1) {
874 return ERROR_MALFORMED;
875 }
876
877 if (!source()->getUInt32Var(offset, &itemId, itemIdSize)) {
878 return ERROR_IO;
879 }
880 offset += itemIdSize;
881 size -= itemIdSize;
882
883 uint8_t associationCount;
884 if (!source()->readAt(offset, &associationCount, 1)) {
885 return ERROR_IO;
886 }
887 offset++;
888 size--;
889
890 for (size_t i = 0; i < associationCount; ++i) {
891 size_t propIndexSize = (flags() & 1) ? 2 : 1;
892 if (size < propIndexSize) {
893 return ERROR_MALFORMED;
894 }
895 uint16_t propIndex;
896 if (!source()->getUInt16Var(offset, &propIndex, propIndexSize)) {
897 return ERROR_IO;
898 }
899 offset += propIndexSize;
900 size -= propIndexSize;
901 uint16_t bitmask = (1 << (8 * propIndexSize - 1));
902 AssociationEntry entry = {
903 .itemId = itemId,
904 .essential = !!(propIndex & bitmask),
905 .index = (uint16_t) (propIndex & ~bitmask)
906 };
907
908 ALOGV("item id %d associated to property %d (essential %d)",
909 itemId, entry.index, entry.essential);
910
911 mAssociations->push_back(entry);
912 }
913 }
914
915 return OK;
916 }
917
918 struct IpcoBox : public Box {
IpcoBoxandroid::heif::IpcoBox919 IpcoBox(DataSourceHelper *source, Vector<sp<ItemProperty> > *properties) :
920 Box(source, FOURCC("ipco")), mItemProperties(properties) {}
921
922 status_t parse(off64_t offset, size_t size);
923 protected:
924 status_t onChunkData(uint32_t type, off64_t offset, size_t size) override;
925
926 private:
927 Vector<sp<ItemProperty> > *mItemProperties;
928 };
929
parse(off64_t offset,size_t size)930 status_t IpcoBox::parse(off64_t offset, size_t size) {
931 ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
932 // push a placeholder as the index is 1-based
933 mItemProperties->push_back(new ItemProperty());
934 return parseChunks(offset, size);
935 }
936
onChunkData(uint32_t type,off64_t offset,size_t size)937 status_t IpcoBox::onChunkData(uint32_t type, off64_t offset, size_t size) {
938 sp<ItemProperty> itemProperty;
939 switch(type) {
940 case FOURCC("hvcC"):
941 {
942 itemProperty = new HvccBox(source());
943 break;
944 }
945 case FOURCC("ispe"):
946 {
947 itemProperty = new IspeBox(source());
948 break;
949 }
950 case FOURCC("irot"):
951 {
952 itemProperty = new IrotBox(source());
953 break;
954 }
955 case FOURCC("colr"):
956 {
957 itemProperty = new ColrBox(source());
958 break;
959 }
960 default:
961 {
962 // push dummy to maintain correct item property index
963 itemProperty = new ItemProperty();
964 break;
965 }
966 }
967 status_t err = itemProperty->parse(offset, size);
968 if (err != OK) {
969 return err;
970 }
971 mItemProperties->push_back(itemProperty);
972 return OK;
973 }
974
975 struct IprpBox : public Box {
IprpBoxandroid::heif::IprpBox976 IprpBox(DataSourceHelper *source,
977 Vector<sp<ItemProperty> > *properties,
978 Vector<AssociationEntry> *associations) :
979 Box(source, FOURCC("iprp")),
980 mProperties(properties), mAssociations(associations) {}
981
982 status_t parse(off64_t offset, size_t size);
983 protected:
984 status_t onChunkData(uint32_t type, off64_t offset, size_t size) override;
985
986 private:
987 Vector<sp<ItemProperty> > *mProperties;
988 Vector<AssociationEntry> *mAssociations;
989 };
990
parse(off64_t offset,size_t size)991 status_t IprpBox::parse(off64_t offset, size_t size) {
992 ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
993
994 status_t err = parseChunks(offset, size);
995 if (err != OK) {
996 return err;
997 }
998 return OK;
999 }
1000
onChunkData(uint32_t type,off64_t offset,size_t size)1001 status_t IprpBox::onChunkData(uint32_t type, off64_t offset, size_t size) {
1002 switch(type) {
1003 case FOURCC("ipco"):
1004 {
1005 IpcoBox ipcoBox(source(), mProperties);
1006 return ipcoBox.parse(offset, size);
1007 }
1008 case FOURCC("ipma"):
1009 {
1010 IpmaBox ipmaBox(source(), mAssociations);
1011 return ipmaBox.parse(offset, size);
1012 }
1013 default:
1014 {
1015 ALOGW("Unrecognized box.");
1016 break;
1017 }
1018 }
1019 return OK;
1020 }
1021
1022 /////////////////////////////////////////////////////////////////////
1023 //
1024 // ItemInfo related boxes
1025 //
1026 struct ItemInfo {
1027 uint32_t itemId;
1028 uint32_t itemType;
1029 bool hidden;
1030 };
1031
1032 struct InfeBox : public FullBox {
InfeBoxandroid::heif::InfeBox1033 InfeBox(DataSourceHelper *source) :
1034 FullBox(source, FOURCC("infe")) {}
1035
1036 status_t parse(off64_t offset, size_t size, ItemInfo *itemInfo);
1037
1038 private:
1039 bool parseNullTerminatedString(off64_t *offset, size_t *size, String8 *out);
1040 };
1041
parseNullTerminatedString(off64_t * offset,size_t * size,String8 * out)1042 bool InfeBox::parseNullTerminatedString(
1043 off64_t *offset, size_t *size, String8 *out) {
1044 char tmp;
1045 Vector<char> buf;
1046 buf.setCapacity(256);
1047 off64_t newOffset = *offset;
1048 off64_t stopOffset = *offset + *size;
1049 while (newOffset < stopOffset) {
1050 if (!source()->readAt(newOffset++, &tmp, 1)) {
1051 return false;
1052 }
1053 buf.push_back(tmp);
1054 if (tmp == 0) {
1055 out->setTo(buf.array());
1056
1057 *offset = newOffset;
1058 *size = stopOffset - newOffset;
1059
1060 return true;
1061 }
1062 }
1063 return false;
1064 }
1065
parse(off64_t offset,size_t size,ItemInfo * itemInfo)1066 status_t InfeBox::parse(off64_t offset, size_t size, ItemInfo *itemInfo) {
1067 status_t err = parseFullBoxHeader(&offset, &size);
1068 if (err != OK) {
1069 return err;
1070 }
1071
1072 if (version() == 0 || version() == 1) {
1073 return ERROR_UNSUPPORTED;
1074 } else { // version >= 2
1075 uint32_t item_id;
1076 size_t itemIdSize = (version() == 2) ? 2 : 4;
1077 if (size < itemIdSize + 6) {
1078 return ERROR_MALFORMED;
1079 }
1080 if (!source()->getUInt32Var(offset, &item_id, itemIdSize)) {
1081 return ERROR_IO;
1082 }
1083 ALOGV("item_id %d", item_id);
1084 offset += itemIdSize;
1085 uint16_t item_protection_index;
1086 if (!source()->getUInt16(offset, &item_protection_index)) {
1087 return ERROR_IO;
1088 }
1089 ALOGV("item_protection_index %d", item_protection_index);
1090 offset += 2;
1091 uint32_t item_type;
1092 if (!source()->getUInt32(offset, &item_type)) {
1093 return ERROR_IO;
1094 }
1095
1096 itemInfo->itemId = item_id;
1097 itemInfo->itemType = item_type;
1098 // According to HEIF spec, (flags & 1) indicates the image is hidden
1099 // and not supposed to be displayed.
1100 itemInfo->hidden = (flags() & 1);
1101
1102 char itemTypeString[5];
1103 MakeFourCCString(item_type, itemTypeString);
1104 ALOGV("item_type %s", itemTypeString);
1105 offset += 4;
1106 size -= itemIdSize + 6;
1107
1108 String8 item_name;
1109 if (!parseNullTerminatedString(&offset, &size, &item_name)) {
1110 return ERROR_MALFORMED;
1111 }
1112 ALOGV("item_name %s", item_name.c_str());
1113
1114 if (item_type == FOURCC("mime")) {
1115 String8 content_type;
1116 if (!parseNullTerminatedString(&offset, &size, &content_type)) {
1117 return ERROR_MALFORMED;
1118 }
1119
1120 // content_encoding is optional; can be omitted if would be empty
1121 if (size > 0) {
1122 String8 content_encoding;
1123 if (!parseNullTerminatedString(&offset, &size, &content_encoding)) {
1124 return ERROR_MALFORMED;
1125 }
1126 }
1127 } else if (item_type == FOURCC("uri ")) {
1128 String8 item_uri_type;
1129 if (!parseNullTerminatedString(&offset, &size, &item_uri_type)) {
1130 return ERROR_MALFORMED;
1131 }
1132 }
1133 }
1134 return OK;
1135 }
1136
1137 struct IinfBox : public FullBox {
IinfBoxandroid::heif::IinfBox1138 IinfBox(DataSourceHelper *source, Vector<ItemInfo> *itemInfos) :
1139 FullBox(source, FOURCC("iinf")), mItemInfos(itemInfos) {}
1140
1141 status_t parse(off64_t offset, size_t size);
1142
hasFourCCandroid::heif::IinfBox1143 bool hasFourCC(uint32_t type) { return mFourCCSeen.count(type) > 0; }
1144
1145 protected:
1146 status_t onChunkData(uint32_t type, off64_t offset, size_t size) override;
1147
1148 private:
1149 Vector<ItemInfo> *mItemInfos;
1150 std::unordered_set<uint32_t> mFourCCSeen;
1151 };
1152
parse(off64_t offset,size_t size)1153 status_t IinfBox::parse(off64_t offset, size_t size) {
1154 ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
1155
1156 status_t err = parseFullBoxHeader(&offset, &size);
1157 if (err != OK) {
1158 return err;
1159 }
1160
1161 size_t entryCountSize = version() == 0 ? 2 : 4;
1162 if (size < entryCountSize) {
1163 return ERROR_MALFORMED;
1164 }
1165 uint32_t entry_count;
1166 if (!source()->getUInt32Var(offset, &entry_count, entryCountSize)) {
1167 return ERROR_IO;
1168 }
1169 ALOGV("entry_count %d", entry_count);
1170
1171 off64_t stopOffset = offset + size;
1172 offset += entryCountSize;
1173 for (size_t i = 0; i < entry_count && offset < stopOffset; i++) {
1174 ALOGV("entry %zu", i);
1175 status_t err = parseChunk(&offset);
1176 if (err != OK) {
1177 return err;
1178 }
1179 }
1180 if (offset != stopOffset) {
1181 return ERROR_MALFORMED;
1182 }
1183
1184 return OK;
1185 }
1186
onChunkData(uint32_t type,off64_t offset,size_t size)1187 status_t IinfBox::onChunkData(uint32_t type, off64_t offset, size_t size) {
1188 if (type != FOURCC("infe")) {
1189 return OK;
1190 }
1191
1192 InfeBox infeBox(source());
1193 ItemInfo itemInfo;
1194 status_t err = infeBox.parse(offset, size, &itemInfo);
1195 if (err == OK) {
1196 mItemInfos->push_back(itemInfo);
1197 mFourCCSeen.insert(itemInfo.itemType);
1198 }
1199 // InfeBox parse returns ERROR_UNSUPPORTED if the box if an unsupported
1200 // version. Ignore this error as it's not fatal.
1201 return (err == ERROR_UNSUPPORTED) ? OK : err;
1202 }
1203
1204 //////////////////////////////////////////////////////////////////
1205
ItemTable(DataSourceHelper * source)1206 ItemTable::ItemTable(DataSourceHelper *source)
1207 : mDataSource(source),
1208 mPrimaryItemId(0),
1209 mIdatOffset(0),
1210 mIdatSize(0),
1211 mImageItemsValid(false),
1212 mCurrentItemIndex(0) {
1213 mRequiredBoxes.insert('iprp');
1214 mRequiredBoxes.insert('iloc');
1215 mRequiredBoxes.insert('pitm');
1216 mRequiredBoxes.insert('iinf');
1217 }
1218
~ItemTable()1219 ItemTable::~ItemTable() {}
1220
parse(uint32_t type,off64_t data_offset,size_t chunk_data_size)1221 status_t ItemTable::parse(uint32_t type, off64_t data_offset, size_t chunk_data_size) {
1222 switch(type) {
1223 case FOURCC("iloc"):
1224 {
1225 return parseIlocBox(data_offset, chunk_data_size);
1226 }
1227 case FOURCC("iinf"):
1228 {
1229 return parseIinfBox(data_offset, chunk_data_size);
1230 }
1231 case FOURCC("iprp"):
1232 {
1233 return parseIprpBox(data_offset, chunk_data_size);
1234 }
1235 case FOURCC("pitm"):
1236 {
1237 return parsePitmBox(data_offset, chunk_data_size);
1238 }
1239 case FOURCC("idat"):
1240 {
1241 return parseIdatBox(data_offset, chunk_data_size);
1242 }
1243 case FOURCC("iref"):
1244 {
1245 return parseIrefBox(data_offset, chunk_data_size);
1246 }
1247 case FOURCC("ipro"):
1248 {
1249 ALOGW("ipro box not supported!");
1250 break;
1251 }
1252 default:
1253 {
1254 ALOGW("unrecognized box type: 0x%x", type);
1255 break;
1256 }
1257 }
1258 return ERROR_UNSUPPORTED;
1259 }
1260
parseIlocBox(off64_t offset,size_t size)1261 status_t ItemTable::parseIlocBox(off64_t offset, size_t size) {
1262 ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
1263
1264 IlocBox ilocBox(mDataSource, &mItemLocs);
1265 status_t err = ilocBox.parse(offset, size);
1266 if (err != OK) {
1267 return err;
1268 }
1269
1270 if (ilocBox.hasConstructMethod1()) {
1271 mRequiredBoxes.insert('idat');
1272 }
1273
1274 return buildImageItemsIfPossible('iloc');
1275 }
1276
parseIinfBox(off64_t offset,size_t size)1277 status_t ItemTable::parseIinfBox(off64_t offset, size_t size) {
1278 ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
1279
1280 IinfBox iinfBox(mDataSource, &mItemInfos);
1281 status_t err = iinfBox.parse(offset, size);
1282 if (err != OK) {
1283 return err;
1284 }
1285
1286 if (iinfBox.hasFourCC(FOURCC("grid")) || iinfBox.hasFourCC(FOURCC("Exif"))) {
1287 mRequiredBoxes.insert('iref');
1288 }
1289
1290 return buildImageItemsIfPossible('iinf');
1291 }
1292
parsePitmBox(off64_t offset,size_t size)1293 status_t ItemTable::parsePitmBox(off64_t offset, size_t size) {
1294 ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
1295
1296 PitmBox pitmBox(mDataSource);
1297 status_t err = pitmBox.parse(offset, size, &mPrimaryItemId);
1298 if (err != OK) {
1299 return err;
1300 }
1301
1302 return buildImageItemsIfPossible('pitm');
1303 }
1304
parseIprpBox(off64_t offset,size_t size)1305 status_t ItemTable::parseIprpBox(off64_t offset, size_t size) {
1306 ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
1307
1308 IprpBox iprpBox(mDataSource, &mItemProperties, &mAssociations);
1309 status_t err = iprpBox.parse(offset, size);
1310 if (err != OK) {
1311 return err;
1312 }
1313
1314 return buildImageItemsIfPossible('iprp');
1315 }
1316
parseIdatBox(off64_t offset,size_t size)1317 status_t ItemTable::parseIdatBox(off64_t offset, size_t size) {
1318 ALOGV("%s: idat offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
1319
1320 // only remember the offset and size of idat box for later use
1321 mIdatOffset = offset;
1322 mIdatSize = size;
1323
1324 return buildImageItemsIfPossible('idat');
1325 }
1326
parseIrefBox(off64_t offset,size_t size)1327 status_t ItemTable::parseIrefBox(off64_t offset, size_t size) {
1328 ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
1329
1330 IrefBox irefBox(mDataSource, &mItemReferences);
1331 status_t err = irefBox.parse(offset, size);
1332 if (err != OK) {
1333 return err;
1334 }
1335
1336 return buildImageItemsIfPossible('iref');
1337 }
1338
buildImageItemsIfPossible(uint32_t type)1339 status_t ItemTable::buildImageItemsIfPossible(uint32_t type) {
1340 if (mImageItemsValid) {
1341 return OK;
1342 }
1343
1344 mBoxesSeen.insert(type);
1345
1346 // need at least 'iprp', 'iloc', 'pitm', 'iinf';
1347 // need 'idat' if any items used construction_method of 2;
1348 // need 'iref' if there are grids.
1349 if (!std::includes(
1350 mBoxesSeen.begin(), mBoxesSeen.end(),
1351 mRequiredBoxes.begin(), mRequiredBoxes.end())) {
1352 return OK;
1353 }
1354
1355 ALOGV("building image table...");
1356
1357 for (size_t i = 0; i < mItemInfos.size(); i++) {
1358 const ItemInfo &info = mItemInfos[i];
1359
1360 // Only handle 3 types of items, all others are ignored:
1361 // 'grid': derived image from tiles
1362 // 'hvc1': coded image (or tile)
1363 // 'Exif': EXIF metadata
1364 if (info.itemType != FOURCC("grid") &&
1365 info.itemType != FOURCC("hvc1") &&
1366 info.itemType != FOURCC("Exif")) {
1367 continue;
1368 }
1369
1370 ssize_t itemIndex = mItemIdToItemMap.indexOfKey(info.itemId);
1371 if (itemIndex >= 0) {
1372 ALOGW("ignoring duplicate image item id %d", info.itemId);
1373 continue;
1374 }
1375
1376 ssize_t ilocIndex = mItemLocs.indexOfKey(info.itemId);
1377 if (ilocIndex < 0) {
1378 ALOGE("iloc missing for image item id %d", info.itemId);
1379 continue;
1380 }
1381 const ItemLoc &iloc = mItemLocs[ilocIndex];
1382
1383 off64_t offset;
1384 size_t size;
1385 if (iloc.getLoc(&offset, &size, mIdatOffset, mIdatSize) != OK) {
1386 return ERROR_MALFORMED;
1387 }
1388
1389 if (info.itemType == FOURCC("Exif")) {
1390 // Only add if the Exif data is non-empty. The first 4 bytes contain
1391 // the offset to TIFF header, which the Exif parser doesn't use.
1392 if (size > 4) {
1393 ExifItem exifItem = {
1394 .offset = offset,
1395 .size = size,
1396 };
1397 mItemIdToExifMap.add(info.itemId, exifItem);
1398 }
1399 continue;
1400 }
1401
1402 ImageItem image(info.itemType, info.itemId, info.hidden);
1403
1404 ALOGV("adding %s: itemId %d", image.isGrid() ? "grid" : "image", info.itemId);
1405
1406 if (image.isGrid()) {
1407 // ImageGrid struct is at least 8-byte, at most 12-byte (if flags&1)
1408 if (size < 8 || size > 12) {
1409 return ERROR_MALFORMED;
1410 }
1411 uint8_t buf[12];
1412 if (!mDataSource->readAt(offset, buf, size)) {
1413 return ERROR_IO;
1414 }
1415
1416 image.rows = buf[2] + 1;
1417 image.columns = buf[3] + 1;
1418
1419 ALOGV("rows %d, columans %d", image.rows, image.columns);
1420 } else {
1421 image.offset = offset;
1422 image.size = size;
1423 }
1424 mItemIdToItemMap.add(info.itemId, image);
1425 }
1426
1427 for (size_t i = 0; i < mAssociations.size(); i++) {
1428 attachProperty(mAssociations[i]);
1429 }
1430
1431 for (size_t i = 0; i < mItemReferences.size(); i++) {
1432 mItemReferences[i]->apply(mItemIdToItemMap, mItemIdToExifMap);
1433 }
1434
1435 bool foundPrimary = false;
1436 for (size_t i = 0; i < mItemIdToItemMap.size(); i++) {
1437 // add all non-hidden images, also add the primary even if it's marked
1438 // hidden, in case the primary is set to a thumbnail
1439 bool isPrimary = (mItemIdToItemMap[i].itemId == mPrimaryItemId);
1440 if (!mItemIdToItemMap[i].hidden || isPrimary) {
1441 mDisplayables.push_back(i);
1442 }
1443 foundPrimary |= isPrimary;
1444 }
1445
1446 ALOGV("found %zu displayables", mDisplayables.size());
1447
1448 // fail if no displayables are found
1449 if (mDisplayables.empty()) {
1450 return ERROR_MALFORMED;
1451 }
1452
1453 // if the primary item id is invalid, set primary to the first displayable
1454 if (!foundPrimary) {
1455 mPrimaryItemId = mItemIdToItemMap[mDisplayables[0]].itemId;
1456 }
1457
1458 mImageItemsValid = true;
1459 return OK;
1460 }
1461
attachProperty(const AssociationEntry & association)1462 void ItemTable::attachProperty(const AssociationEntry &association) {
1463 ssize_t itemIndex = mItemIdToItemMap.indexOfKey(association.itemId);
1464
1465 // ignore non-image items
1466 if (itemIndex < 0) {
1467 return;
1468 }
1469
1470 uint16_t propertyIndex = association.index;
1471 if (propertyIndex >= mItemProperties.size()) {
1472 ALOGW("Ignoring invalid property index %d", propertyIndex);
1473 return;
1474 }
1475
1476 ALOGV("attach property %d to item id %d)",
1477 propertyIndex, association.itemId);
1478
1479 mItemProperties[propertyIndex]->attachTo(mItemIdToItemMap.editValueAt(itemIndex));
1480 }
1481
countImages() const1482 uint32_t ItemTable::countImages() const {
1483 return mImageItemsValid ? mDisplayables.size() : 0;
1484 }
1485
getImageMeta(const uint32_t imageIndex)1486 AMediaFormat *ItemTable::getImageMeta(const uint32_t imageIndex) {
1487 if (!mImageItemsValid) {
1488 return NULL;
1489 }
1490
1491 if (imageIndex >= mDisplayables.size()) {
1492 ALOGE("%s: invalid image index %u", __FUNCTION__, imageIndex);
1493 return NULL;
1494 }
1495 const uint32_t itemIndex = mDisplayables[imageIndex];
1496 ALOGV("image[%u]: item index %u", imageIndex, itemIndex);
1497
1498 const ImageItem *image = &mItemIdToItemMap[itemIndex];
1499
1500 ssize_t tileItemIndex = -1;
1501 if (image->isGrid()) {
1502 if (image->dimgRefs.empty()) {
1503 return NULL;
1504 }
1505 tileItemIndex = mItemIdToItemMap.indexOfKey(image->dimgRefs[0]);
1506 if (tileItemIndex < 0) {
1507 return NULL;
1508 }
1509 }
1510
1511 AMediaFormat *meta = AMediaFormat_new();
1512 AMediaFormat_setString(meta, AMEDIAFORMAT_KEY_MIME, MEDIA_MIMETYPE_IMAGE_ANDROID_HEIC);
1513
1514 if (image->itemId == mPrimaryItemId) {
1515 AMediaFormat_setInt32(meta, AMEDIAFORMAT_KEY_IS_DEFAULT, 1);
1516 }
1517
1518 ALOGV("image[%u]: size %dx%d", imageIndex, image->width, image->height);
1519
1520 AMediaFormat_setInt32(meta, AMEDIAFORMAT_KEY_WIDTH, image->width);
1521 AMediaFormat_setInt32(meta, AMEDIAFORMAT_KEY_HEIGHT, image->height);
1522 if (image->rotation != 0) {
1523 // Rotation angle in HEIF is CCW, convert to CW here to be
1524 // consistent with the other media formats.
1525 switch(image->rotation) {
1526 case 90:
1527 case 180:
1528 case 270:
1529 AMediaFormat_setInt32(meta, AMEDIAFORMAT_KEY_ROTATION, 360 - image->rotation);
1530 break;
1531 default: break; // don't set if invalid
1532 }
1533 }
1534 // we validated no overflow in IspeBox::parse()
1535 AMediaFormat_setInt32(meta,
1536 AMEDIAFORMAT_KEY_MAX_INPUT_SIZE, image->width * image->height * 3 / 2);
1537
1538 if (!image->thumbnails.empty()) {
1539 ssize_t thumbItemIndex = mItemIdToItemMap.indexOfKey(image->thumbnails[0]);
1540 if (thumbItemIndex >= 0) {
1541 const ImageItem &thumbnail = mItemIdToItemMap[thumbItemIndex];
1542
1543 if (thumbnail.hvcc != NULL) {
1544 AMediaFormat_setInt32(meta,
1545 AMEDIAFORMAT_KEY_THUMBNAIL_WIDTH, thumbnail.width);
1546 AMediaFormat_setInt32(meta,
1547 AMEDIAFORMAT_KEY_THUMBNAIL_HEIGHT, thumbnail.height);
1548 AMediaFormat_setBuffer(meta,
1549 AMEDIAFORMAT_KEY_THUMBNAIL_CSD_HEVC,
1550 thumbnail.hvcc->data(), thumbnail.hvcc->size());
1551 ALOGV("image[%u]: thumbnail: size %dx%d, item index %zd",
1552 imageIndex, thumbnail.width, thumbnail.height, thumbItemIndex);
1553 } else {
1554 ALOGW("%s: thumbnail data is missing for image[%u]!", __FUNCTION__, imageIndex);
1555 }
1556 } else {
1557 ALOGW("%s: Referenced thumbnail does not exist!", __FUNCTION__);
1558 }
1559 }
1560
1561 if (image->isGrid()) {
1562 AMediaFormat_setInt32(meta,
1563 AMEDIAFORMAT_KEY_GRID_ROWS, image->rows);
1564 AMediaFormat_setInt32(meta,
1565 AMEDIAFORMAT_KEY_GRID_COLUMNS, image->columns);
1566 // point image to the first tile for grid size and HVCC
1567 image = &mItemIdToItemMap.editValueAt(tileItemIndex);
1568 AMediaFormat_setInt32(meta,
1569 AMEDIAFORMAT_KEY_TILE_WIDTH, image->width);
1570 AMediaFormat_setInt32(meta,
1571 AMEDIAFORMAT_KEY_TILE_HEIGHT, image->height);
1572 // we validated no overflow in IspeBox::parse()
1573 AMediaFormat_setInt32(meta,
1574 AMEDIAFORMAT_KEY_MAX_INPUT_SIZE, image->width * image->height * 3 / 2);
1575 }
1576
1577 if (image->hvcc == NULL) {
1578 ALOGE("%s: hvcc is missing for image[%u]!", __FUNCTION__, imageIndex);
1579 return NULL;
1580 }
1581 AMediaFormat_setBuffer(meta,
1582 AMEDIAFORMAT_KEY_CSD_HEVC, image->hvcc->data(), image->hvcc->size());
1583
1584 if (image->icc != NULL) {
1585 AMediaFormat_setBuffer(meta,
1586 AMEDIAFORMAT_KEY_ICC_PROFILE, image->icc->data(), image->icc->size());
1587 }
1588 return meta;
1589 }
1590
findImageItem(const uint32_t imageIndex,uint32_t * itemIndex)1591 status_t ItemTable::findImageItem(const uint32_t imageIndex, uint32_t *itemIndex) {
1592 if (!mImageItemsValid) {
1593 return INVALID_OPERATION;
1594 }
1595
1596 if (imageIndex >= mDisplayables.size()) {
1597 ALOGE("%s: invalid image index %d", __FUNCTION__, imageIndex);
1598 return BAD_VALUE;
1599 }
1600
1601 *itemIndex = mDisplayables[imageIndex];
1602
1603 ALOGV("image[%u]: item index %u", imageIndex, *itemIndex);
1604 return OK;
1605 }
1606
findThumbnailItem(const uint32_t imageIndex,uint32_t * itemIndex)1607 status_t ItemTable::findThumbnailItem(const uint32_t imageIndex, uint32_t *itemIndex) {
1608 if (!mImageItemsValid) {
1609 return INVALID_OPERATION;
1610 }
1611
1612 if (imageIndex >= mDisplayables.size()) {
1613 ALOGE("%s: invalid image index %d", __FUNCTION__, imageIndex);
1614 return BAD_VALUE;
1615 }
1616
1617 uint32_t imageItemIndex = mDisplayables[imageIndex];
1618
1619 const ImageItem &imageItem = mItemIdToItemMap[imageItemIndex];
1620 if (imageItem.thumbnails.empty()) {
1621 *itemIndex = imageItemIndex;
1622 return OK;
1623 }
1624
1625 ssize_t thumbItemIndex = mItemIdToItemMap.indexOfKey(imageItem.thumbnails[0]);
1626 if (thumbItemIndex < 0) {
1627 // Do not return the image item in this case, fail it so that the
1628 // thumbnail extraction code knows we really don't have it.
1629 return INVALID_OPERATION;
1630 }
1631
1632 *itemIndex = thumbItemIndex;
1633 return OK;
1634 }
1635
getImageOffsetAndSize(uint32_t * itemIndex,off64_t * offset,size_t * size)1636 status_t ItemTable::getImageOffsetAndSize(
1637 uint32_t *itemIndex, off64_t *offset, size_t *size) {
1638 if (!mImageItemsValid) {
1639 return INVALID_OPERATION;
1640 }
1641
1642 if (itemIndex != NULL) {
1643 if (*itemIndex >= mItemIdToItemMap.size()) {
1644 ALOGE("%s: Bad item index!", __FUNCTION__);
1645 return BAD_VALUE;
1646 }
1647 mCurrentItemIndex = *itemIndex;
1648 }
1649
1650 ImageItem &image = mItemIdToItemMap.editValueAt(mCurrentItemIndex);
1651 if (image.isGrid()) {
1652 uint32_t tileItemId;
1653 status_t err = image.getNextTileItemId(&tileItemId, itemIndex != NULL);
1654 if (err != OK) {
1655 return err;
1656 }
1657 ssize_t tileItemIndex = mItemIdToItemMap.indexOfKey(tileItemId);
1658 if (tileItemIndex < 0) {
1659 return ERROR_END_OF_STREAM;
1660 }
1661 *offset = mItemIdToItemMap[tileItemIndex].offset;
1662 *size = mItemIdToItemMap[tileItemIndex].size;
1663 } else {
1664 if (itemIndex == NULL) {
1665 // For single images, we only allow it to be read once, after that
1666 // it's EOS. New item index must be requested each time.
1667 return ERROR_END_OF_STREAM;
1668 }
1669 *offset = mItemIdToItemMap[mCurrentItemIndex].offset;
1670 *size = mItemIdToItemMap[mCurrentItemIndex].size;
1671 }
1672
1673 return OK;
1674 }
1675
getExifOffsetAndSize(off64_t * offset,size_t * size)1676 status_t ItemTable::getExifOffsetAndSize(off64_t *offset, size_t *size) {
1677 if (!mImageItemsValid) {
1678 return INVALID_OPERATION;
1679 }
1680
1681 ssize_t itemIndex = mItemIdToItemMap.indexOfKey(mPrimaryItemId);
1682
1683 // this should not happen, something's seriously wrong.
1684 if (itemIndex < 0) {
1685 return INVALID_OPERATION;
1686 }
1687
1688 const ImageItem &image = mItemIdToItemMap[itemIndex];
1689 if (image.cdscRefs.size() == 0) {
1690 return NAME_NOT_FOUND;
1691 }
1692
1693 ssize_t exifIndex = mItemIdToExifMap.indexOfKey(image.cdscRefs[0]);
1694 if (exifIndex < 0) {
1695 return NAME_NOT_FOUND;
1696 }
1697
1698 // skip the first 4-byte of the offset to TIFF header
1699 uint32_t tiffOffset;
1700 if (!mDataSource->readAt(
1701 mItemIdToExifMap[exifIndex].offset, &tiffOffset, 4)) {
1702 return ERROR_IO;
1703 }
1704
1705 // We need 'Exif\0\0' before the tiff header
1706 tiffOffset = ntohl(tiffOffset);
1707 if (tiffOffset < 6) {
1708 return ERROR_MALFORMED;
1709 }
1710 // The first 4-byte of the item is the offset of the tiff header within the
1711 // exif data. The size of the item should be > 4 for a non-empty exif (this
1712 // was already checked when the item was added). Also check that the tiff
1713 // header offset is valid.
1714 if (mItemIdToExifMap[exifIndex].size <= 4 ||
1715 tiffOffset > mItemIdToExifMap[exifIndex].size - 4) {
1716 return ERROR_MALFORMED;
1717 }
1718
1719 // Offset of 'Exif\0\0' relative to the beginning of 'Exif' item
1720 // (first 4-byte is the tiff header offset)
1721 uint32_t exifOffset = 4 + tiffOffset - 6;
1722 *offset = mItemIdToExifMap[exifIndex].offset + exifOffset;
1723 *size = mItemIdToExifMap[exifIndex].size - exifOffset;
1724 return OK;
1725 }
1726
1727 } // namespace heif
1728
1729 } // namespace android
1730