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 &masterImage = itemIdToItemMap.editValueAt(itemIndex);
550 if (!masterImage.thumbnails.empty()) {
551 ALOGW("already has thumbnails!");
552 }
553 masterImage.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 uint32_t mWidth;
704 uint32_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, &mWidth)
719 || !source()->getUInt32(offset + 4, &mHeight)) {
720 return ERROR_IO;
721 }
722 ALOGV("property ispe: %dx%d", mWidth, mHeight);
723
724 return OK;
725 }
726
727 struct HvccBox : public Box, public ItemProperty {
HvccBoxandroid::heif::HvccBox728 HvccBox(DataSourceHelper *source) :
729 Box(source, FOURCC("hvcC")) {}
730
731 status_t parse(off64_t offset, size_t size) override;
732
attachToandroid::heif::HvccBox733 void attachTo(ImageItem &image) const override {
734 image.hvcc = mHVCC;
735 }
736
737 private:
738 sp<ABuffer> mHVCC;
739 };
740
parse(off64_t offset,size_t size)741 status_t HvccBox::parse(off64_t offset, size_t size) {
742 ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
743
744 mHVCC = new ABuffer(size);
745
746 if (mHVCC->data() == NULL) {
747 ALOGE("b/28471206");
748 return NO_MEMORY;
749 }
750
751 if (source()->readAt(offset, mHVCC->data(), size) < (ssize_t)size) {
752 return ERROR_IO;
753 }
754
755 ALOGV("property hvcC");
756
757 return OK;
758 }
759
760 struct IrotBox : public Box, public ItemProperty {
IrotBoxandroid::heif::IrotBox761 IrotBox(DataSourceHelper *source) :
762 Box(source, FOURCC("irot")), mAngle(0) {}
763
764 status_t parse(off64_t offset, size_t size) override;
765
attachToandroid::heif::IrotBox766 void attachTo(ImageItem &image) const override {
767 image.rotation = mAngle * 90;
768 }
769
770 private:
771 uint8_t mAngle;
772 };
773
parse(off64_t offset,size_t size)774 status_t IrotBox::parse(off64_t offset, size_t size) {
775 ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
776
777 if (size < 1) {
778 return ERROR_MALFORMED;
779 }
780 if (source()->readAt(offset, &mAngle, 1) != 1) {
781 return ERROR_IO;
782 }
783 mAngle &= 0x3;
784 ALOGV("property irot: %d", mAngle);
785
786 return OK;
787 }
788
789 struct ColrBox : public Box, public ItemProperty {
ColrBoxandroid::heif::ColrBox790 ColrBox(DataSourceHelper *source) :
791 Box(source, FOURCC("colr")) {}
792
793 status_t parse(off64_t offset, size_t size) override;
794
attachToandroid::heif::ColrBox795 void attachTo(ImageItem &image) const override {
796 image.icc = mICCData;
797 }
798
799 private:
800 sp<ABuffer> mICCData;
801 };
802
parse(off64_t offset,size_t size)803 status_t ColrBox::parse(off64_t offset, size_t size) {
804 ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
805
806 if (size < 4) {
807 return ERROR_MALFORMED;
808 }
809 uint32_t colour_type;
810 if (!source()->getUInt32(offset, &colour_type)) {
811 return ERROR_IO;
812 }
813 offset += 4;
814 size -= 4;
815 if (colour_type == FOURCC("nclx")) {
816 return OK;
817 }
818 if ((colour_type != FOURCC("rICC")) &&
819 (colour_type != FOURCC("prof"))) {
820 return ERROR_MALFORMED;
821 }
822
823 mICCData = new ABuffer(size);
824 if (mICCData->data() == NULL) {
825 ALOGE("b/28471206");
826 return NO_MEMORY;
827 }
828
829 if (source()->readAt(offset, mICCData->data(), size) != (ssize_t)size) {
830 return ERROR_IO;
831 }
832
833 ALOGV("property Colr: size %zd", size);
834 return OK;
835 }
836
837 struct IpmaBox : public FullBox {
IpmaBoxandroid::heif::IpmaBox838 IpmaBox(DataSourceHelper *source, Vector<AssociationEntry> *associations) :
839 FullBox(source, FOURCC("ipma")), mAssociations(associations) {}
840
841 status_t parse(off64_t offset, size_t size);
842 private:
843 Vector<AssociationEntry> *mAssociations;
844 };
845
parse(off64_t offset,size_t size)846 status_t IpmaBox::parse(off64_t offset, size_t size) {
847 status_t err = parseFullBoxHeader(&offset, &size);
848 if (err != OK) {
849 return err;
850 }
851
852 if (size < 4) {
853 return ERROR_MALFORMED;
854 }
855 uint32_t entryCount;
856 if (!source()->getUInt32(offset, &entryCount)) {
857 return ERROR_IO;
858 }
859 offset += 4;
860 size -= 4;
861
862 for (size_t k = 0; k < entryCount; ++k) {
863 uint32_t itemId = 0;
864 size_t itemIdSize = (version() < 1) ? 2 : 4;
865
866 if (size < itemIdSize + 1) {
867 return ERROR_MALFORMED;
868 }
869
870 if (!source()->getUInt32Var(offset, &itemId, itemIdSize)) {
871 return ERROR_IO;
872 }
873 offset += itemIdSize;
874 size -= itemIdSize;
875
876 uint8_t associationCount;
877 if (!source()->readAt(offset, &associationCount, 1)) {
878 return ERROR_IO;
879 }
880 offset++;
881 size--;
882
883 for (size_t i = 0; i < associationCount; ++i) {
884 size_t propIndexSize = (flags() & 1) ? 2 : 1;
885 if (size < propIndexSize) {
886 return ERROR_MALFORMED;
887 }
888 uint16_t propIndex;
889 if (!source()->getUInt16Var(offset, &propIndex, propIndexSize)) {
890 return ERROR_IO;
891 }
892 offset += propIndexSize;
893 size -= propIndexSize;
894 uint16_t bitmask = (1 << (8 * propIndexSize - 1));
895 AssociationEntry entry = {
896 .itemId = itemId,
897 .essential = !!(propIndex & bitmask),
898 .index = (uint16_t) (propIndex & ~bitmask)
899 };
900
901 ALOGV("item id %d associated to property %d (essential %d)",
902 itemId, entry.index, entry.essential);
903
904 mAssociations->push_back(entry);
905 }
906 }
907
908 return OK;
909 }
910
911 struct IpcoBox : public Box {
IpcoBoxandroid::heif::IpcoBox912 IpcoBox(DataSourceHelper *source, Vector<sp<ItemProperty> > *properties) :
913 Box(source, FOURCC("ipco")), mItemProperties(properties) {}
914
915 status_t parse(off64_t offset, size_t size);
916 protected:
917 status_t onChunkData(uint32_t type, off64_t offset, size_t size) override;
918
919 private:
920 Vector<sp<ItemProperty> > *mItemProperties;
921 };
922
parse(off64_t offset,size_t size)923 status_t IpcoBox::parse(off64_t offset, size_t size) {
924 ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
925 // push dummy as the index is 1-based
926 mItemProperties->push_back(new ItemProperty());
927 return parseChunks(offset, size);
928 }
929
onChunkData(uint32_t type,off64_t offset,size_t size)930 status_t IpcoBox::onChunkData(uint32_t type, off64_t offset, size_t size) {
931 sp<ItemProperty> itemProperty;
932 switch(type) {
933 case FOURCC("hvcC"):
934 {
935 itemProperty = new HvccBox(source());
936 break;
937 }
938 case FOURCC("ispe"):
939 {
940 itemProperty = new IspeBox(source());
941 break;
942 }
943 case FOURCC("irot"):
944 {
945 itemProperty = new IrotBox(source());
946 break;
947 }
948 case FOURCC("colr"):
949 {
950 itemProperty = new ColrBox(source());
951 break;
952 }
953 default:
954 {
955 // push dummy to maintain correct item property index
956 itemProperty = new ItemProperty();
957 break;
958 }
959 }
960 status_t err = itemProperty->parse(offset, size);
961 if (err != OK) {
962 return err;
963 }
964 mItemProperties->push_back(itemProperty);
965 return OK;
966 }
967
968 struct IprpBox : public Box {
IprpBoxandroid::heif::IprpBox969 IprpBox(DataSourceHelper *source,
970 Vector<sp<ItemProperty> > *properties,
971 Vector<AssociationEntry> *associations) :
972 Box(source, FOURCC("iprp")),
973 mProperties(properties), mAssociations(associations) {}
974
975 status_t parse(off64_t offset, size_t size);
976 protected:
977 status_t onChunkData(uint32_t type, off64_t offset, size_t size) override;
978
979 private:
980 Vector<sp<ItemProperty> > *mProperties;
981 Vector<AssociationEntry> *mAssociations;
982 };
983
parse(off64_t offset,size_t size)984 status_t IprpBox::parse(off64_t offset, size_t size) {
985 ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
986
987 status_t err = parseChunks(offset, size);
988 if (err != OK) {
989 return err;
990 }
991 return OK;
992 }
993
onChunkData(uint32_t type,off64_t offset,size_t size)994 status_t IprpBox::onChunkData(uint32_t type, off64_t offset, size_t size) {
995 switch(type) {
996 case FOURCC("ipco"):
997 {
998 IpcoBox ipcoBox(source(), mProperties);
999 return ipcoBox.parse(offset, size);
1000 }
1001 case FOURCC("ipma"):
1002 {
1003 IpmaBox ipmaBox(source(), mAssociations);
1004 return ipmaBox.parse(offset, size);
1005 }
1006 default:
1007 {
1008 ALOGW("Unrecognized box.");
1009 break;
1010 }
1011 }
1012 return OK;
1013 }
1014
1015 /////////////////////////////////////////////////////////////////////
1016 //
1017 // ItemInfo related boxes
1018 //
1019 struct ItemInfo {
1020 uint32_t itemId;
1021 uint32_t itemType;
1022 bool hidden;
1023 };
1024
1025 struct InfeBox : public FullBox {
InfeBoxandroid::heif::InfeBox1026 InfeBox(DataSourceHelper *source) :
1027 FullBox(source, FOURCC("infe")) {}
1028
1029 status_t parse(off64_t offset, size_t size, ItemInfo *itemInfo);
1030
1031 private:
1032 bool parseNullTerminatedString(off64_t *offset, size_t *size, String8 *out);
1033 };
1034
parseNullTerminatedString(off64_t * offset,size_t * size,String8 * out)1035 bool InfeBox::parseNullTerminatedString(
1036 off64_t *offset, size_t *size, String8 *out) {
1037 char tmp;
1038 Vector<char> buf;
1039 buf.setCapacity(256);
1040 off64_t newOffset = *offset;
1041 off64_t stopOffset = *offset + *size;
1042 while (newOffset < stopOffset) {
1043 if (!source()->readAt(newOffset++, &tmp, 1)) {
1044 return false;
1045 }
1046 buf.push_back(tmp);
1047 if (tmp == 0) {
1048 out->setTo(buf.array());
1049
1050 *offset = newOffset;
1051 *size = stopOffset - newOffset;
1052
1053 return true;
1054 }
1055 }
1056 return false;
1057 }
1058
parse(off64_t offset,size_t size,ItemInfo * itemInfo)1059 status_t InfeBox::parse(off64_t offset, size_t size, ItemInfo *itemInfo) {
1060 status_t err = parseFullBoxHeader(&offset, &size);
1061 if (err != OK) {
1062 return err;
1063 }
1064
1065 if (version() == 0 || version() == 1) {
1066 return ERROR_UNSUPPORTED;
1067 } else { // version >= 2
1068 uint32_t item_id;
1069 size_t itemIdSize = (version() == 2) ? 2 : 4;
1070 if (size < itemIdSize + 6) {
1071 return ERROR_MALFORMED;
1072 }
1073 if (!source()->getUInt32Var(offset, &item_id, itemIdSize)) {
1074 return ERROR_IO;
1075 }
1076 ALOGV("item_id %d", item_id);
1077 offset += itemIdSize;
1078 uint16_t item_protection_index;
1079 if (!source()->getUInt16(offset, &item_protection_index)) {
1080 return ERROR_IO;
1081 }
1082 ALOGV("item_protection_index %d", item_protection_index);
1083 offset += 2;
1084 uint32_t item_type;
1085 if (!source()->getUInt32(offset, &item_type)) {
1086 return ERROR_IO;
1087 }
1088
1089 itemInfo->itemId = item_id;
1090 itemInfo->itemType = item_type;
1091 // According to HEIF spec, (flags & 1) indicates the image is hidden
1092 // and not supposed to be displayed.
1093 itemInfo->hidden = (flags() & 1);
1094
1095 char itemTypeString[5];
1096 MakeFourCCString(item_type, itemTypeString);
1097 ALOGV("item_type %s", itemTypeString);
1098 offset += 4;
1099 size -= itemIdSize + 6;
1100
1101 String8 item_name;
1102 if (!parseNullTerminatedString(&offset, &size, &item_name)) {
1103 return ERROR_MALFORMED;
1104 }
1105 ALOGV("item_name %s", item_name.c_str());
1106
1107 if (item_type == FOURCC("mime")) {
1108 String8 content_type;
1109 if (!parseNullTerminatedString(&offset, &size, &content_type)) {
1110 return ERROR_MALFORMED;
1111 }
1112
1113 // content_encoding is optional; can be omitted if would be empty
1114 if (size > 0) {
1115 String8 content_encoding;
1116 if (!parseNullTerminatedString(&offset, &size, &content_encoding)) {
1117 return ERROR_MALFORMED;
1118 }
1119 }
1120 } else if (item_type == FOURCC("uri ")) {
1121 String8 item_uri_type;
1122 if (!parseNullTerminatedString(&offset, &size, &item_uri_type)) {
1123 return ERROR_MALFORMED;
1124 }
1125 }
1126 }
1127 return OK;
1128 }
1129
1130 struct IinfBox : public FullBox {
IinfBoxandroid::heif::IinfBox1131 IinfBox(DataSourceHelper *source, Vector<ItemInfo> *itemInfos) :
1132 FullBox(source, FOURCC("iinf")), mItemInfos(itemInfos) {}
1133
1134 status_t parse(off64_t offset, size_t size);
1135
hasFourCCandroid::heif::IinfBox1136 bool hasFourCC(uint32_t type) { return mFourCCSeen.count(type) > 0; }
1137
1138 protected:
1139 status_t onChunkData(uint32_t type, off64_t offset, size_t size) override;
1140
1141 private:
1142 Vector<ItemInfo> *mItemInfos;
1143 std::unordered_set<uint32_t> mFourCCSeen;
1144 };
1145
parse(off64_t offset,size_t size)1146 status_t IinfBox::parse(off64_t offset, size_t size) {
1147 ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
1148
1149 status_t err = parseFullBoxHeader(&offset, &size);
1150 if (err != OK) {
1151 return err;
1152 }
1153
1154 size_t entryCountSize = version() == 0 ? 2 : 4;
1155 if (size < entryCountSize) {
1156 return ERROR_MALFORMED;
1157 }
1158 uint32_t entry_count;
1159 if (!source()->getUInt32Var(offset, &entry_count, entryCountSize)) {
1160 return ERROR_IO;
1161 }
1162 ALOGV("entry_count %d", entry_count);
1163
1164 off64_t stopOffset = offset + size;
1165 offset += entryCountSize;
1166 for (size_t i = 0; i < entry_count && offset < stopOffset; i++) {
1167 ALOGV("entry %zu", i);
1168 status_t err = parseChunk(&offset);
1169 if (err != OK) {
1170 return err;
1171 }
1172 }
1173 if (offset != stopOffset) {
1174 return ERROR_MALFORMED;
1175 }
1176
1177 return OK;
1178 }
1179
onChunkData(uint32_t type,off64_t offset,size_t size)1180 status_t IinfBox::onChunkData(uint32_t type, off64_t offset, size_t size) {
1181 if (type != FOURCC("infe")) {
1182 return OK;
1183 }
1184
1185 InfeBox infeBox(source());
1186 ItemInfo itemInfo;
1187 status_t err = infeBox.parse(offset, size, &itemInfo);
1188 if (err == OK) {
1189 mItemInfos->push_back(itemInfo);
1190 mFourCCSeen.insert(itemInfo.itemType);
1191 }
1192 // InfeBox parse returns ERROR_UNSUPPORTED if the box if an unsupported
1193 // version. Ignore this error as it's not fatal.
1194 return (err == ERROR_UNSUPPORTED) ? OK : err;
1195 }
1196
1197 //////////////////////////////////////////////////////////////////
1198
ItemTable(DataSourceHelper * source)1199 ItemTable::ItemTable(DataSourceHelper *source)
1200 : mDataSource(source),
1201 mPrimaryItemId(0),
1202 mIdatOffset(0),
1203 mIdatSize(0),
1204 mImageItemsValid(false),
1205 mCurrentItemIndex(0) {
1206 mRequiredBoxes.insert('iprp');
1207 mRequiredBoxes.insert('iloc');
1208 mRequiredBoxes.insert('pitm');
1209 mRequiredBoxes.insert('iinf');
1210 }
1211
~ItemTable()1212 ItemTable::~ItemTable() {}
1213
parse(uint32_t type,off64_t data_offset,size_t chunk_data_size)1214 status_t ItemTable::parse(uint32_t type, off64_t data_offset, size_t chunk_data_size) {
1215 switch(type) {
1216 case FOURCC("iloc"):
1217 {
1218 return parseIlocBox(data_offset, chunk_data_size);
1219 }
1220 case FOURCC("iinf"):
1221 {
1222 return parseIinfBox(data_offset, chunk_data_size);
1223 }
1224 case FOURCC("iprp"):
1225 {
1226 return parseIprpBox(data_offset, chunk_data_size);
1227 }
1228 case FOURCC("pitm"):
1229 {
1230 return parsePitmBox(data_offset, chunk_data_size);
1231 }
1232 case FOURCC("idat"):
1233 {
1234 return parseIdatBox(data_offset, chunk_data_size);
1235 }
1236 case FOURCC("iref"):
1237 {
1238 return parseIrefBox(data_offset, chunk_data_size);
1239 }
1240 case FOURCC("ipro"):
1241 {
1242 ALOGW("ipro box not supported!");
1243 break;
1244 }
1245 default:
1246 {
1247 ALOGW("unrecognized box type: 0x%x", type);
1248 break;
1249 }
1250 }
1251 return ERROR_UNSUPPORTED;
1252 }
1253
parseIlocBox(off64_t offset,size_t size)1254 status_t ItemTable::parseIlocBox(off64_t offset, size_t size) {
1255 ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
1256
1257 IlocBox ilocBox(mDataSource, &mItemLocs);
1258 status_t err = ilocBox.parse(offset, size);
1259 if (err != OK) {
1260 return err;
1261 }
1262
1263 if (ilocBox.hasConstructMethod1()) {
1264 mRequiredBoxes.insert('idat');
1265 }
1266
1267 return buildImageItemsIfPossible('iloc');
1268 }
1269
parseIinfBox(off64_t offset,size_t size)1270 status_t ItemTable::parseIinfBox(off64_t offset, size_t size) {
1271 ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
1272
1273 IinfBox iinfBox(mDataSource, &mItemInfos);
1274 status_t err = iinfBox.parse(offset, size);
1275 if (err != OK) {
1276 return err;
1277 }
1278
1279 if (iinfBox.hasFourCC(FOURCC("grid")) || iinfBox.hasFourCC(FOURCC("Exif"))) {
1280 mRequiredBoxes.insert('iref');
1281 }
1282
1283 return buildImageItemsIfPossible('iinf');
1284 }
1285
parsePitmBox(off64_t offset,size_t size)1286 status_t ItemTable::parsePitmBox(off64_t offset, size_t size) {
1287 ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
1288
1289 PitmBox pitmBox(mDataSource);
1290 status_t err = pitmBox.parse(offset, size, &mPrimaryItemId);
1291 if (err != OK) {
1292 return err;
1293 }
1294
1295 return buildImageItemsIfPossible('pitm');
1296 }
1297
parseIprpBox(off64_t offset,size_t size)1298 status_t ItemTable::parseIprpBox(off64_t offset, size_t size) {
1299 ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
1300
1301 IprpBox iprpBox(mDataSource, &mItemProperties, &mAssociations);
1302 status_t err = iprpBox.parse(offset, size);
1303 if (err != OK) {
1304 return err;
1305 }
1306
1307 return buildImageItemsIfPossible('iprp');
1308 }
1309
parseIdatBox(off64_t offset,size_t size)1310 status_t ItemTable::parseIdatBox(off64_t offset, size_t size) {
1311 ALOGV("%s: idat offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
1312
1313 // only remember the offset and size of idat box for later use
1314 mIdatOffset = offset;
1315 mIdatSize = size;
1316
1317 return buildImageItemsIfPossible('idat');
1318 }
1319
parseIrefBox(off64_t offset,size_t size)1320 status_t ItemTable::parseIrefBox(off64_t offset, size_t size) {
1321 ALOGV("%s: offset %lld, size %zu", __FUNCTION__, (long long)offset, size);
1322
1323 IrefBox irefBox(mDataSource, &mItemReferences);
1324 status_t err = irefBox.parse(offset, size);
1325 if (err != OK) {
1326 return err;
1327 }
1328
1329 return buildImageItemsIfPossible('iref');
1330 }
1331
buildImageItemsIfPossible(uint32_t type)1332 status_t ItemTable::buildImageItemsIfPossible(uint32_t type) {
1333 if (mImageItemsValid) {
1334 return OK;
1335 }
1336
1337 mBoxesSeen.insert(type);
1338
1339 // need at least 'iprp', 'iloc', 'pitm', 'iinf';
1340 // need 'idat' if any items used construction_method of 2;
1341 // need 'iref' if there are grids.
1342 if (!std::includes(
1343 mBoxesSeen.begin(), mBoxesSeen.end(),
1344 mRequiredBoxes.begin(), mRequiredBoxes.end())) {
1345 return OK;
1346 }
1347
1348 ALOGV("building image table...");
1349
1350 for (size_t i = 0; i < mItemInfos.size(); i++) {
1351 const ItemInfo &info = mItemInfos[i];
1352
1353 // Only handle 3 types of items, all others are ignored:
1354 // 'grid': derived image from tiles
1355 // 'hvc1': coded image (or tile)
1356 // 'Exif': EXIF metadata
1357 if (info.itemType != FOURCC("grid") &&
1358 info.itemType != FOURCC("hvc1") &&
1359 info.itemType != FOURCC("Exif")) {
1360 continue;
1361 }
1362
1363 ssize_t itemIndex = mItemIdToItemMap.indexOfKey(info.itemId);
1364 if (itemIndex >= 0) {
1365 ALOGW("ignoring duplicate image item id %d", info.itemId);
1366 continue;
1367 }
1368
1369 ssize_t ilocIndex = mItemLocs.indexOfKey(info.itemId);
1370 if (ilocIndex < 0) {
1371 ALOGE("iloc missing for image item id %d", info.itemId);
1372 continue;
1373 }
1374 const ItemLoc &iloc = mItemLocs[ilocIndex];
1375
1376 off64_t offset;
1377 size_t size;
1378 if (iloc.getLoc(&offset, &size, mIdatOffset, mIdatSize) != OK) {
1379 return ERROR_MALFORMED;
1380 }
1381
1382 if (info.itemType == FOURCC("Exif")) {
1383 // Only add if the Exif data is non-empty. The first 4 bytes contain
1384 // the offset to TIFF header, which the Exif parser doesn't use.
1385 if (size > 4) {
1386 ExifItem exifItem = {
1387 .offset = offset,
1388 .size = size,
1389 };
1390 mItemIdToExifMap.add(info.itemId, exifItem);
1391 }
1392 continue;
1393 }
1394
1395 ImageItem image(info.itemType, info.itemId, info.hidden);
1396
1397 ALOGV("adding %s: itemId %d", image.isGrid() ? "grid" : "image", info.itemId);
1398
1399 if (image.isGrid()) {
1400 // ImageGrid struct is at least 8-byte, at most 12-byte (if flags&1)
1401 if (size < 8 || size > 12) {
1402 return ERROR_MALFORMED;
1403 }
1404 uint8_t buf[12];
1405 if (!mDataSource->readAt(offset, buf, size)) {
1406 return ERROR_IO;
1407 }
1408
1409 image.rows = buf[2] + 1;
1410 image.columns = buf[3] + 1;
1411
1412 ALOGV("rows %d, columans %d", image.rows, image.columns);
1413 } else {
1414 image.offset = offset;
1415 image.size = size;
1416 }
1417 mItemIdToItemMap.add(info.itemId, image);
1418 }
1419
1420 for (size_t i = 0; i < mAssociations.size(); i++) {
1421 attachProperty(mAssociations[i]);
1422 }
1423
1424 for (size_t i = 0; i < mItemReferences.size(); i++) {
1425 mItemReferences[i]->apply(mItemIdToItemMap, mItemIdToExifMap);
1426 }
1427
1428 bool foundPrimary = false;
1429 for (size_t i = 0; i < mItemIdToItemMap.size(); i++) {
1430 // add all non-hidden images, also add the primary even if it's marked
1431 // hidden, in case the primary is set to a thumbnail
1432 bool isPrimary = (mItemIdToItemMap[i].itemId == mPrimaryItemId);
1433 if (!mItemIdToItemMap[i].hidden || isPrimary) {
1434 mDisplayables.push_back(i);
1435 }
1436 foundPrimary |= isPrimary;
1437 }
1438
1439 ALOGV("found %zu displayables", mDisplayables.size());
1440
1441 // fail if no displayables are found
1442 if (mDisplayables.empty()) {
1443 return ERROR_MALFORMED;
1444 }
1445
1446 // if the primary item id is invalid, set primary to the first displayable
1447 if (!foundPrimary) {
1448 mPrimaryItemId = mItemIdToItemMap[mDisplayables[0]].itemId;
1449 }
1450
1451 mImageItemsValid = true;
1452 return OK;
1453 }
1454
attachProperty(const AssociationEntry & association)1455 void ItemTable::attachProperty(const AssociationEntry &association) {
1456 ssize_t itemIndex = mItemIdToItemMap.indexOfKey(association.itemId);
1457
1458 // ignore non-image items
1459 if (itemIndex < 0) {
1460 return;
1461 }
1462
1463 uint16_t propertyIndex = association.index;
1464 if (propertyIndex >= mItemProperties.size()) {
1465 ALOGW("Ignoring invalid property index %d", propertyIndex);
1466 return;
1467 }
1468
1469 ALOGV("attach property %d to item id %d)",
1470 propertyIndex, association.itemId);
1471
1472 mItemProperties[propertyIndex]->attachTo(mItemIdToItemMap.editValueAt(itemIndex));
1473 }
1474
countImages() const1475 uint32_t ItemTable::countImages() const {
1476 return mImageItemsValid ? mDisplayables.size() : 0;
1477 }
1478
getImageMeta(const uint32_t imageIndex)1479 AMediaFormat *ItemTable::getImageMeta(const uint32_t imageIndex) {
1480 if (!mImageItemsValid) {
1481 return NULL;
1482 }
1483
1484 if (imageIndex >= mDisplayables.size()) {
1485 ALOGE("%s: invalid image index %u", __FUNCTION__, imageIndex);
1486 return NULL;
1487 }
1488 const uint32_t itemIndex = mDisplayables[imageIndex];
1489 ALOGV("image[%u]: item index %u", imageIndex, itemIndex);
1490
1491 const ImageItem *image = &mItemIdToItemMap[itemIndex];
1492
1493 ssize_t tileItemIndex = -1;
1494 if (image->isGrid()) {
1495 if (image->dimgRefs.empty()) {
1496 return NULL;
1497 }
1498 tileItemIndex = mItemIdToItemMap.indexOfKey(image->dimgRefs[0]);
1499 if (tileItemIndex < 0) {
1500 return NULL;
1501 }
1502 }
1503
1504 AMediaFormat *meta = AMediaFormat_new();
1505 AMediaFormat_setString(meta, AMEDIAFORMAT_KEY_MIME, MEDIA_MIMETYPE_IMAGE_ANDROID_HEIC);
1506
1507 if (image->itemId == mPrimaryItemId) {
1508 AMediaFormat_setInt32(meta, AMEDIAFORMAT_KEY_IS_DEFAULT, 1);
1509 }
1510
1511 ALOGV("image[%u]: size %dx%d", imageIndex, image->width, image->height);
1512
1513 AMediaFormat_setInt32(meta, AMEDIAFORMAT_KEY_WIDTH, image->width);
1514 AMediaFormat_setInt32(meta, AMEDIAFORMAT_KEY_HEIGHT, image->height);
1515 if (image->rotation != 0) {
1516 // Rotation angle in HEIF is CCW, convert to CW here to be
1517 // consistent with the other media formats.
1518 switch(image->rotation) {
1519 case 90:
1520 case 180:
1521 case 270:
1522 AMediaFormat_setInt32(meta, AMEDIAFORMAT_KEY_ROTATION, 360 - image->rotation);
1523 break;
1524 default: break; // don't set if invalid
1525 }
1526 }
1527 AMediaFormat_setInt32(meta,
1528 AMEDIAFORMAT_KEY_MAX_INPUT_SIZE, image->width * image->height * 1.5);
1529
1530 if (!image->thumbnails.empty()) {
1531 ssize_t thumbItemIndex = mItemIdToItemMap.indexOfKey(image->thumbnails[0]);
1532 if (thumbItemIndex >= 0) {
1533 const ImageItem &thumbnail = mItemIdToItemMap[thumbItemIndex];
1534
1535 if (thumbnail.hvcc != NULL) {
1536 AMediaFormat_setInt32(meta,
1537 AMEDIAFORMAT_KEY_THUMBNAIL_WIDTH, thumbnail.width);
1538 AMediaFormat_setInt32(meta,
1539 AMEDIAFORMAT_KEY_THUMBNAIL_HEIGHT, thumbnail.height);
1540 AMediaFormat_setBuffer(meta,
1541 AMEDIAFORMAT_KEY_THUMBNAIL_CSD_HEVC,
1542 thumbnail.hvcc->data(), thumbnail.hvcc->size());
1543 ALOGV("image[%u]: thumbnail: size %dx%d, item index %zd",
1544 imageIndex, thumbnail.width, thumbnail.height, thumbItemIndex);
1545 } else {
1546 ALOGW("%s: thumbnail data is missing for image[%u]!", __FUNCTION__, imageIndex);
1547 }
1548 } else {
1549 ALOGW("%s: Referenced thumbnail does not exist!", __FUNCTION__);
1550 }
1551 }
1552
1553 if (image->isGrid()) {
1554 AMediaFormat_setInt32(meta,
1555 AMEDIAFORMAT_KEY_GRID_ROWS, image->rows);
1556 AMediaFormat_setInt32(meta,
1557 AMEDIAFORMAT_KEY_GRID_COLUMNS, image->columns);
1558 // point image to the first tile for grid size and HVCC
1559 image = &mItemIdToItemMap.editValueAt(tileItemIndex);
1560 AMediaFormat_setInt32(meta,
1561 AMEDIAFORMAT_KEY_TILE_WIDTH, image->width);
1562 AMediaFormat_setInt32(meta,
1563 AMEDIAFORMAT_KEY_TILE_HEIGHT, image->height);
1564 AMediaFormat_setInt32(meta,
1565 AMEDIAFORMAT_KEY_MAX_INPUT_SIZE, image->width * image->height * 1.5);
1566 }
1567
1568 if (image->hvcc == NULL) {
1569 ALOGE("%s: hvcc is missing for image[%u]!", __FUNCTION__, imageIndex);
1570 return NULL;
1571 }
1572 AMediaFormat_setBuffer(meta,
1573 AMEDIAFORMAT_KEY_CSD_HEVC, image->hvcc->data(), image->hvcc->size());
1574
1575 if (image->icc != NULL) {
1576 AMediaFormat_setBuffer(meta,
1577 AMEDIAFORMAT_KEY_ICC_PROFILE, image->icc->data(), image->icc->size());
1578 }
1579 return meta;
1580 }
1581
findImageItem(const uint32_t imageIndex,uint32_t * itemIndex)1582 status_t ItemTable::findImageItem(const uint32_t imageIndex, uint32_t *itemIndex) {
1583 if (!mImageItemsValid) {
1584 return INVALID_OPERATION;
1585 }
1586
1587 if (imageIndex >= mDisplayables.size()) {
1588 ALOGE("%s: invalid image index %d", __FUNCTION__, imageIndex);
1589 return BAD_VALUE;
1590 }
1591
1592 *itemIndex = mDisplayables[imageIndex];
1593
1594 ALOGV("image[%u]: item index %u", imageIndex, *itemIndex);
1595 return OK;
1596 }
1597
findThumbnailItem(const uint32_t imageIndex,uint32_t * itemIndex)1598 status_t ItemTable::findThumbnailItem(const uint32_t imageIndex, uint32_t *itemIndex) {
1599 if (!mImageItemsValid) {
1600 return INVALID_OPERATION;
1601 }
1602
1603 if (imageIndex >= mDisplayables.size()) {
1604 ALOGE("%s: invalid image index %d", __FUNCTION__, imageIndex);
1605 return BAD_VALUE;
1606 }
1607
1608 uint32_t masterItemIndex = mDisplayables[imageIndex];
1609
1610 const ImageItem &masterImage = mItemIdToItemMap[masterItemIndex];
1611 if (masterImage.thumbnails.empty()) {
1612 *itemIndex = masterItemIndex;
1613 return OK;
1614 }
1615
1616 ssize_t thumbItemIndex = mItemIdToItemMap.indexOfKey(masterImage.thumbnails[0]);
1617 if (thumbItemIndex < 0) {
1618 // Do not return the master image in this case, fail it so that the
1619 // thumbnail extraction code knows we really don't have it.
1620 return INVALID_OPERATION;
1621 }
1622
1623 *itemIndex = thumbItemIndex;
1624 return OK;
1625 }
1626
getImageOffsetAndSize(uint32_t * itemIndex,off64_t * offset,size_t * size)1627 status_t ItemTable::getImageOffsetAndSize(
1628 uint32_t *itemIndex, off64_t *offset, size_t *size) {
1629 if (!mImageItemsValid) {
1630 return INVALID_OPERATION;
1631 }
1632
1633 if (itemIndex != NULL) {
1634 if (*itemIndex >= mItemIdToItemMap.size()) {
1635 ALOGE("%s: Bad item index!", __FUNCTION__);
1636 return BAD_VALUE;
1637 }
1638 mCurrentItemIndex = *itemIndex;
1639 }
1640
1641 ImageItem &image = mItemIdToItemMap.editValueAt(mCurrentItemIndex);
1642 if (image.isGrid()) {
1643 uint32_t tileItemId;
1644 status_t err = image.getNextTileItemId(&tileItemId, itemIndex != NULL);
1645 if (err != OK) {
1646 return err;
1647 }
1648 ssize_t tileItemIndex = mItemIdToItemMap.indexOfKey(tileItemId);
1649 if (tileItemIndex < 0) {
1650 return ERROR_END_OF_STREAM;
1651 }
1652 *offset = mItemIdToItemMap[tileItemIndex].offset;
1653 *size = mItemIdToItemMap[tileItemIndex].size;
1654 } else {
1655 if (itemIndex == NULL) {
1656 // For single images, we only allow it to be read once, after that
1657 // it's EOS. New item index must be requested each time.
1658 return ERROR_END_OF_STREAM;
1659 }
1660 *offset = mItemIdToItemMap[mCurrentItemIndex].offset;
1661 *size = mItemIdToItemMap[mCurrentItemIndex].size;
1662 }
1663
1664 return OK;
1665 }
1666
getExifOffsetAndSize(off64_t * offset,size_t * size)1667 status_t ItemTable::getExifOffsetAndSize(off64_t *offset, size_t *size) {
1668 if (!mImageItemsValid) {
1669 return INVALID_OPERATION;
1670 }
1671
1672 ssize_t itemIndex = mItemIdToItemMap.indexOfKey(mPrimaryItemId);
1673
1674 // this should not happen, something's seriously wrong.
1675 if (itemIndex < 0) {
1676 return INVALID_OPERATION;
1677 }
1678
1679 const ImageItem &image = mItemIdToItemMap[itemIndex];
1680 if (image.cdscRefs.size() == 0) {
1681 return NAME_NOT_FOUND;
1682 }
1683
1684 ssize_t exifIndex = mItemIdToExifMap.indexOfKey(image.cdscRefs[0]);
1685 if (exifIndex < 0) {
1686 return NAME_NOT_FOUND;
1687 }
1688
1689 // skip the first 4-byte of the offset to TIFF header
1690 uint32_t tiffOffset;
1691 if (!mDataSource->readAt(
1692 mItemIdToExifMap[exifIndex].offset, &tiffOffset, 4)) {
1693 return ERROR_IO;
1694 }
1695
1696 // We need 'Exif\0\0' before the tiff header
1697 tiffOffset = ntohl(tiffOffset);
1698 if (tiffOffset < 6) {
1699 return ERROR_MALFORMED;
1700 }
1701 // The first 4-byte of the item is the offset of the tiff header within the
1702 // exif data. The size of the item should be > 4 for a non-empty exif (this
1703 // was already checked when the item was added). Also check that the tiff
1704 // header offset is valid.
1705 if (mItemIdToExifMap[exifIndex].size <= 4 ||
1706 tiffOffset > mItemIdToExifMap[exifIndex].size - 4) {
1707 return ERROR_MALFORMED;
1708 }
1709
1710 // Offset of 'Exif\0\0' relative to the beginning of 'Exif' item
1711 // (first 4-byte is the tiff header offset)
1712 uint32_t exifOffset = 4 + tiffOffset - 6;
1713 *offset = mItemIdToExifMap[exifIndex].offset + exifOffset;
1714 *size = mItemIdToExifMap[exifIndex].size - exifOffset;
1715 return OK;
1716 }
1717
1718 } // namespace heif
1719
1720 } // namespace android
1721