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