Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 72500a1d authored by Chong Zhang's avatar Chong Zhang
Browse files

Allow muxer to write exif with APP1

Also contains misc fixes around exif reading/writing.

bug: 123094535
test: verify locally that ExifInterface retrieves the
      exif data at correct offset; HeifWriter accepts
      exif data starting with 'Exif'+APP1 marker.

Change-Id: I526641724caaea68d2b6f1075d9aa30d9a74b973
parent 6ead2c34
Loading
Loading
Loading
Loading
+25 −2
Original line number Diff line number Diff line
@@ -1687,8 +1687,31 @@ status_t ItemTable::getExifOffsetAndSize(off64_t *offset, size_t *size) {
    }

    // skip the first 4-byte of the offset to TIFF header
    *offset = mItemIdToExifMap[exifIndex].offset + 4;
    *size = mItemIdToExifMap[exifIndex].size - 4;
    uint32_t tiffOffset;
    if (!mDataSource->readAt(
            mItemIdToExifMap[exifIndex].offset, &tiffOffset, 4)) {
        return ERROR_IO;
    }

    // We need 'Exif\0\0' before the tiff header
    tiffOffset = ntohl(tiffOffset);
    if (tiffOffset < 6) {
        return ERROR_MALFORMED;
    }
    // The first 4-byte of the item is the offset of the tiff header within the
    // exif data. The size of the item should be > 4 for a non-empty exif (this
    // was already checked when the item was added). Also check that the tiff
    // header offset is valid.
    if (mItemIdToExifMap[exifIndex].size <= 4 ||
            tiffOffset > mItemIdToExifMap[exifIndex].size - 4) {
        return ERROR_MALFORMED;
    }

    // Offset of 'Exif\0\0' relative to the beginning of 'Exif' item
    // (first 4-byte is the tiff header offset)
    uint32_t exifOffset = 4 + tiffOffset - 6;
    *offset = mItemIdToExifMap[exifIndex].offset + exifOffset;
    *size = mItemIdToExifMap[exifIndex].size - exifOffset;
    return OK;
}

+58 −21
Original line number Diff line number Diff line
@@ -85,7 +85,7 @@ static const char kMetaKey_TemporalLayerCount[] = "com.android.video.temporal_la
static const int kTimestampDebugCount = 10;
static const int kItemIdBase = 10000;
static const char kExifHeader[] = {'E', 'x', 'i', 'f', '\0', '\0'};
static const int32_t kTiffHeaderOffset = htonl(sizeof(kExifHeader));
static const uint8_t kExifApp1Marker[] = {'E', 'x', 'i', 'f', 0xff, 0xe1};

static const uint8_t kMandatoryHevcNalUnitTypes[3] = {
    kHevcNalUnitTypeVps,
@@ -125,7 +125,7 @@ public:
    bool isAudio() const { return mIsAudio; }
    bool isMPEG4() const { return mIsMPEG4; }
    bool usePrefix() const { return mIsAvc || mIsHevc || mIsHeic; }
    bool isExifData(const MediaBufferBase *buffer) const;
    bool isExifData(MediaBufferBase *buffer, uint32_t *tiffHdrOffset) const;
    void addChunkOffset(off64_t offset);
    void addItemOffsetAndSize(off64_t offset, size_t size, bool isExif);
    void flushItemRefs();
@@ -364,7 +364,7 @@ private:

    Vector<uint16_t> mProperties;
    ItemRefs mDimgRefs;
    ItemRefs mCdscRefs;
    Vector<uint16_t> mExifList;
    uint16_t mImageItemId;
    int32_t mIsPrimary;
    int32_t mWidth, mHeight;
@@ -1368,14 +1368,16 @@ void MPEG4Writer::unlock() {
}

off64_t MPEG4Writer::addSample_l(
        MediaBuffer *buffer, bool usePrefix, bool isExif, size_t *bytesWritten) {
        MediaBuffer *buffer, bool usePrefix,
        uint32_t tiffHdrOffset, size_t *bytesWritten) {
    off64_t old_offset = mOffset;

    if (usePrefix) {
        addMultipleLengthPrefixedSamples_l(buffer);
    } else {
        if (isExif) {
            ::write(mFd, &kTiffHeaderOffset, 4); // exif_tiff_header_offset field
        if (tiffHdrOffset > 0) {
            tiffHdrOffset = htonl(tiffHdrOffset);
            ::write(mFd, &tiffHdrOffset, 4); // exif_tiff_header_offset field
            mOffset += 4;
        }

@@ -1803,7 +1805,6 @@ MPEG4Writer::Track::Track(
      mStartTimestampUs(-1),
      mRotation(0),
      mDimgRefs("dimg"),
      mCdscRefs("cdsc"),
      mImageItemId(0),
      mIsPrimary(0),
      mWidth(0),
@@ -1984,11 +1985,34 @@ status_t MPEG4Writer::setNextFd(int fd) {
    return OK;
}

bool MPEG4Writer::Track::isExifData(const MediaBufferBase *buffer) const {
    return mIsHeic
            && (buffer->range_length() > sizeof(kExifHeader))
            && !memcmp((uint8_t *)buffer->data() + buffer->range_offset(),
                    kExifHeader, sizeof(kExifHeader));
bool MPEG4Writer::Track::isExifData(
        MediaBufferBase *buffer, uint32_t *tiffHdrOffset) const {
    if (!mIsHeic) {
        return false;
    }

    // Exif block starting with 'Exif\0\0'
    size_t length = buffer->range_length();
    uint8_t *data = (uint8_t *)buffer->data() + buffer->range_offset();
    if ((length > sizeof(kExifHeader))
        && !memcmp(data, kExifHeader, sizeof(kExifHeader))) {
        *tiffHdrOffset = sizeof(kExifHeader);
        return true;
    }

    // Exif block starting with fourcc 'Exif' followed by APP1 marker
    if ((length > sizeof(kExifApp1Marker) + 2 + sizeof(kExifHeader))
            && !memcmp(data, kExifApp1Marker, sizeof(kExifApp1Marker))
            && !memcmp(data + sizeof(kExifApp1Marker) + 2, kExifHeader, sizeof(kExifHeader))) {
        // skip 'Exif' fourcc
        buffer->set_range(4, buffer->range_length() - 4);

        // 2-byte APP1 + 2-byte size followed by kExifHeader
        *tiffHdrOffset = 2 + 2 + sizeof(kExifHeader);
        return true;
    }

    return false;
}

void MPEG4Writer::Track::addChunkOffset(off64_t offset) {
@@ -2014,7 +2038,7 @@ void MPEG4Writer::Track::addItemOffsetAndSize(off64_t offset, size_t size, bool
    }

    if (isExif) {
         mCdscRefs.value.push_back(mOwner->addItem_l({
         mExifList.push_back(mOwner->addItem_l({
            .itemType = "Exif",
            .isPrimary = false,
            .isHidden = false,
@@ -2117,7 +2141,16 @@ void MPEG4Writer::Track::flushItemRefs() {

    if (mImageItemId > 0) {
        mOwner->addRefs_l(mImageItemId, mDimgRefs);
        mOwner->addRefs_l(mImageItemId, mCdscRefs);

        if (!mExifList.empty()) {
            // The "cdsc" ref is from the metadata/exif item to the image item.
            // So the refs all contain the image item.
            ItemRefs cdscRefs("cdsc");
            cdscRefs.value.push_back(mImageItemId);
            for (uint16_t exifItem : mExifList) {
                mOwner->addRefs_l(exifItem, cdscRefs);
            }
        }
    }
}

@@ -2269,14 +2302,16 @@ void MPEG4Writer::writeChunkToFile(Chunk* chunk) {
    while (!chunk->mSamples.empty()) {
        List<MediaBuffer *>::iterator it = chunk->mSamples.begin();

        int32_t isExif;
        if (!(*it)->meta_data().findInt32(kKeyIsExif, &isExif)) {
            isExif = 0;
        uint32_t tiffHdrOffset;
        if (!(*it)->meta_data().findInt32(
                kKeyExifTiffOffset, (int32_t*)&tiffHdrOffset)) {
            tiffHdrOffset = 0;
        }
        bool isExif = (tiffHdrOffset > 0);
        bool usePrefix = chunk->mTrack->usePrefix() && !isExif;

        size_t bytesWritten;
        off64_t offset = addSample_l(*it, usePrefix, isExif, &bytesWritten);
        off64_t offset = addSample_l(*it, usePrefix, tiffHdrOffset, &bytesWritten);

        if (chunk->mTrack->isHeic()) {
            chunk->mTrack->addItemOffsetAndSize(offset, bytesWritten, isExif);
@@ -3002,10 +3037,11 @@ status_t MPEG4Writer::Track::threadEntry() {
        }

        bool isExif = false;
        uint32_t tiffHdrOffset = 0;
        int32_t isMuxerData;
        if (buffer->meta_data().findInt32(kKeyIsMuxerData, &isMuxerData) && isMuxerData) {
            // We only support one type of muxer data, which is Exif data block.
            isExif = isExifData(buffer);
            isExif = isExifData(buffer, &tiffHdrOffset);
            if (!isExif) {
                ALOGW("Ignoring bad Exif data block");
                buffer->release();
@@ -3027,7 +3063,7 @@ status_t MPEG4Writer::Track::threadEntry() {
        buffer = NULL;

        if (isExif) {
            copy->meta_data().setInt32(kKeyIsExif, 1);
            copy->meta_data().setInt32(kKeyExifTiffOffset, tiffHdrOffset);
        }
        bool usePrefix = this->usePrefix() && !isExif;

@@ -3300,7 +3336,8 @@ status_t MPEG4Writer::Track::threadEntry() {
        }
        if (!hasMultipleTracks) {
            size_t bytesWritten;
            off64_t offset = mOwner->addSample_l(copy, usePrefix, isExif, &bytesWritten);
            off64_t offset = mOwner->addSample_l(
                    copy, usePrefix, tiffHdrOffset, &bytesWritten);

            if (mIsHeic) {
                addItemOffsetAndSize(offset, bytesWritten, isExif);
+3 −1
Original line number Diff line number Diff line
@@ -257,7 +257,9 @@ private:
    void initInternal(int fd, bool isFirstSession);

    // Acquire lock before calling these methods
    off64_t addSample_l(MediaBuffer *buffer, bool usePrefix, bool isExif, size_t *bytesWritten);
    off64_t addSample_l(
            MediaBuffer *buffer, bool usePrefix,
            uint32_t tiffHdrOffset, size_t *bytesWritten);
    void addLengthPrefixedSample_l(MediaBuffer *buffer);
    void addMultipleLengthPrefixedSamples_l(MediaBuffer *buffer);
    uint16_t addProperty_l(const ItemProperty &);
+2 −1
Original line number Diff line number Diff line
@@ -221,7 +221,8 @@ enum {
    kKeyFrameCount       = 'nfrm', // int32_t, total number of frame in video track
    kKeyExifOffset       = 'exof', // int64_t, Exif data offset
    kKeyExifSize         = 'exsz', // int64_t, Exif data size
    kKeyIsExif           = 'exif', // bool (int32_t) buffer contains exif data block
    kKeyExifTiffOffset   = 'thdr', // int32_t, if > 0, buffer contains exif data block with
                                   // tiff hdr at specified offset
    kKeyPcmBigEndian     = 'pcmb', // bool (int32_t)

    // Key for ALAC Magic Cookie