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