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

Commit 4cf9f6bf authored by Arun Johnson's avatar Arun Johnson
Browse files

Update to Dolby Vision media components

Extractors should parse dvcc/dvvc and dvwc according to
https://professionalsupport.dolby.com/s/article
/How-to-signal-Dolby-Vision-in-ISOBMFF-format-AKA-mp4-container?
language=en_US


Bug: 216824291

Change-Id: I156b46c3b0670e22390346ded8028c65c68db2ad
parent 2c64503d
Loading
Loading
Loading
Loading
+16 −32
Original line number Diff line number Diff line
@@ -1127,15 +1127,15 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
                    void *data;
                    size_t size;

                    if (AMediaFormat_getBuffer(mLastTrack->meta, AMEDIAFORMAT_KEY_CSD_0,
                    if (AMediaFormat_getBuffer(mLastTrack->meta, AMEDIAFORMAT_KEY_CSD_2,
                                               &data, &size)
                        && size >= 24) {
                        const uint8_t *ptr = (const uint8_t *)data + (size - 24);
                        && size >= 5) {
                        const uint8_t *ptr = (const uint8_t *)data;
                        const uint8_t profile = ptr[2] >> 1;
                        const uint8_t bl_compatibility_id = (ptr[4]) >> 4;
                        const uint8_t blCompatibilityId = (ptr[4]) >> 4;
                        bool create_two_tracks = false;

                        if (bl_compatibility_id && bl_compatibility_id != 15) {
                        if (blCompatibilityId && blCompatibilityId != 15) {
                            create_two_tracks = true;
                        }

@@ -1168,11 +1168,11 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
                            mLastTrack->next = track_b;
                            track_b->next = NULL;

                            // we want to remove the csd-0 key from the metadata, but
                            // we want to remove the csd-2 key from the metadata, but
                            // don't have an AMediaFormat_* function to do so. Settle
                            // for replacing this csd-0 with an empty csd-0.
                            // for replacing this csd-2 with an empty csd-2.
                            uint8_t emptybuffer[8] = {};
                            AMediaFormat_setBuffer(track_b->meta, AMEDIAFORMAT_KEY_CSD_0,
                            AMediaFormat_setBuffer(track_b->meta, AMEDIAFORMAT_KEY_CSD_2,
                                                   emptybuffer, 0);

                            if (4 == profile || 7 == profile || 8 == profile ) {
@@ -1184,8 +1184,6 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
                            } else if (10 == profile) {
                                AMediaFormat_setString(track_b->meta,
                                        AMEDIAFORMAT_KEY_MIME, MEDIA_MIMETYPE_VIDEO_AV1);
                                AMediaFormat_setBuffer(track_b->meta, AMEDIAFORMAT_KEY_CSD_0,
                                    data, size - 24);
                            } // Should never get to else part

                            mLastTrack = track_b;
@@ -2618,22 +2616,8 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
            if (mLastTrack == NULL)
                return ERROR_MALFORMED;

            void *data = nullptr;
            size_t size = 0;
            if (AMediaFormat_getBuffer(mLastTrack->meta, AMEDIAFORMAT_KEY_CSD_0, &data, &size)) {
                //if csd-0 is already present, then append dvcc
                auto csd0_dvcc = heapbuffer<uint8_t>(size + chunk_data_size);

                memcpy(csd0_dvcc.get(), data, size);
                memcpy(csd0_dvcc.get() + size, buffer.get(), chunk_data_size);

                AMediaFormat_setBuffer(mLastTrack->meta, AMEDIAFORMAT_KEY_CSD_0,
                                    csd0_dvcc.get(), size + chunk_data_size);
            } else {
                //if not set csd-0 directly
                AMediaFormat_setBuffer(mLastTrack->meta, AMEDIAFORMAT_KEY_CSD_0,
            AMediaFormat_setBuffer(mLastTrack->meta, AMEDIAFORMAT_KEY_CSD_2,
                                    buffer.get(), chunk_data_size);
            }
            AMediaFormat_setString(mLastTrack->meta, AMEDIAFORMAT_KEY_MIME,
                                   MEDIA_MIMETYPE_VIDEO_DOLBY_VISION);

@@ -4511,12 +4495,12 @@ MediaTrackHelper *MPEG4Extractor::getTrack(size_t index) {
    } else if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_DOLBY_VISION)) {
        void *data;
        size_t size;
        if (!AMediaFormat_getBuffer(track->meta, AMEDIAFORMAT_KEY_CSD_0, &data, &size)
                || size < 24) {
        if (!AMediaFormat_getBuffer(track->meta, AMEDIAFORMAT_KEY_CSD_2, &data, &size)
                || size != 24) {
            return NULL;
        }

        const uint8_t *ptr = (const uint8_t *)data + (size - 24);
        const uint8_t *ptr = (const uint8_t *)data;
        // dv_major.dv_minor Should be 1.0 or 2.1
        if ((ptr[0] != 1 || ptr[1] != 0) && (ptr[0] != 2 || ptr[1] != 1)) {
            return NULL;
@@ -4596,7 +4580,7 @@ status_t MPEG4Extractor::verifyTrack(Track *track) {
            return ERROR_MALFORMED;
        }
    } else if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_DOLBY_VISION)) {
        if (!AMediaFormat_getBuffer(track->meta, AMEDIAFORMAT_KEY_CSD_0, &data, &size)) {
        if (!AMediaFormat_getBuffer(track->meta, AMEDIAFORMAT_KEY_CSD_2, &data, &size)) {
            return ERROR_MALFORMED;
        }
    } else if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AV1)) {
@@ -5172,11 +5156,11 @@ MPEG4Source::MPEG4Source(
        ALOGV("%s DolbyVision stream detected", __FUNCTION__);
        void *data;
        size_t size;
        CHECK(AMediaFormat_getBuffer(format, AMEDIAFORMAT_KEY_CSD_0, &data, &size));
        CHECK(AMediaFormat_getBuffer(format, AMEDIAFORMAT_KEY_CSD_2, &data, &size));

        const uint8_t *ptr = (const uint8_t *)data + (size - 24);
        const uint8_t *ptr = (const uint8_t *)data;

        CHECK(size >= 24);
        CHECK(size == 24);

        // dv_major.dv_minor Should be 1.0 or 2.1
        CHECK(!((ptr[0] != 1 || ptr[1] != 0) && (ptr[0] != 2 || ptr[1] != 1)));
+170 −149
Original line number Diff line number Diff line
@@ -243,26 +243,8 @@ static void parseAvcProfileLevelFromAvcc(const uint8_t *ptr, size_t size, sp<AMe
    }
}

static void parseDolbyVisionProfileLevelFromDvcc(const uint8_t *ptr, size_t size, sp<AMessage> &format) {
    // dv_major.dv_minor Should be 1.0 or 2.1
    if (size != 24 || ((ptr[0] != 1 || ptr[1] != 0) && (ptr[0] != 2 || ptr[1] != 1))) {
        ALOGV("Size %zu, dv_major %d, dv_minor %d", size, ptr[0], ptr[1]);
        return;
    }

    const uint8_t profile = ptr[2] >> 1;
    const uint8_t level = ((ptr[2] & 0x1) << 5) | ((ptr[3] >> 3) & 0x1f);
    const uint8_t rpu_present_flag = (ptr[3] >> 2) & 0x01;
    const uint8_t el_present_flag = (ptr[3] >> 1) & 0x01;
    const uint8_t bl_present_flag = (ptr[3] & 0x01);
    const int32_t bl_compatibility_id = (int32_t)(ptr[4] >> 4);

    ALOGV("profile-level-compatibility value in dv(c|v)c box %d-%d-%d",
          profile, level, bl_compatibility_id);

    // All Dolby Profiles will have profile and level info in MediaFormat
    // Profile 8 and 9 will have bl_compatibility_id too.
    const static ALookup<uint8_t, int32_t> profiles{
static const ALookup<uint8_t, int32_t>&  getDolbyVisionProfileTable() {
    static const ALookup<uint8_t, int32_t> profileTable = {
        {1, DolbyVisionProfileDvavPen},
        {3, DolbyVisionProfileDvheDen},
        {4, DolbyVisionProfileDvheDtr},
@@ -273,8 +255,11 @@ static void parseDolbyVisionProfileLevelFromDvcc(const uint8_t *ptr, size_t size
        {9, DolbyVisionProfileDvavSe},
        {10, DolbyVisionProfileDvav110},
    };
    return profileTable;
}

    const static ALookup<uint8_t, int32_t> levels{
static const ALookup<uint8_t, int32_t>&  getDolbyVisionLevelsTable() {
    static const ALookup<uint8_t, int32_t> levelsTable = {
        {0, DolbyVisionLevelUnknown},
        {1, DolbyVisionLevelHd24},
        {2, DolbyVisionLevelHd30},
@@ -289,6 +274,30 @@ static void parseDolbyVisionProfileLevelFromDvcc(const uint8_t *ptr, size_t size
        {11, DolbyVisionLevel8k30},
        {12, DolbyVisionLevel8k60},
    };
    return levelsTable;
}
static void parseDolbyVisionProfileLevelFromDvcc(const uint8_t *ptr, size_t size, sp<AMessage> &format) {
    // dv_major.dv_minor Should be 1.0 or 2.1
    if (size != 24 || ((ptr[0] != 1 || ptr[1] != 0) && (ptr[0] != 2 || ptr[1] != 1))) {
        ALOGV("Size %zu, dv_major %d, dv_minor %d", size, ptr[0], ptr[1]);
        return;
    }

    const uint8_t profile = ptr[2] >> 1;
    const uint8_t level = ((ptr[2] & 0x1) << 5) | ((ptr[3] >> 3) & 0x1f);
    const uint8_t rpu_present_flag = (ptr[3] >> 2) & 0x01;
    const uint8_t el_present_flag = (ptr[3] >> 1) & 0x01;
    const uint8_t bl_present_flag = (ptr[3] & 0x01);
    const int32_t bl_compatibility_id = (int32_t)(ptr[4] >> 4);

    ALOGV("profile-level-compatibility value in dv(c|v)c box %d-%d-%d",
          profile, level, bl_compatibility_id);

    // All Dolby Profiles will have profile and level info in MediaFormat
    // Profile 8 and 9 will have bl_compatibility_id too.
    const ALookup<uint8_t, int32_t> &profiles = getDolbyVisionProfileTable();
    const ALookup<uint8_t, int32_t> &levels = getDolbyVisionLevelsTable();

    // set rpuAssoc
    if (rpu_present_flag && el_present_flag && !bl_present_flag) {
        format->setInt32("rpuAssoc", 1);
@@ -1516,30 +1525,18 @@ status_t convertMetaDataToMessage(
    if (meta->findData(kKeyDVCC, &type, &data, &size)
            || meta->findData(kKeyDVVC, &type, &data, &size)
            || meta->findData(kKeyDVWC, &type, &data, &size)) {
        sp<ABuffer> buffer, csdOrg;
        if (msg->findBuffer("csd-0", &csdOrg)) {
            buffer = new (std::nothrow) ABuffer(size + csdOrg->size());
            if (buffer.get() == NULL || buffer->base() == NULL) {
                return NO_MEMORY;
            }

            memcpy(buffer->data(), csdOrg->data(), csdOrg->size());
            memcpy(buffer->data() + csdOrg->size(), data, size);
        } else {
            buffer = new (std::nothrow) ABuffer(size);
            if (buffer.get() == NULL || buffer->base() == NULL) {
        const uint8_t *ptr = (const uint8_t *)data;
        ALOGV("DV: calling parseDolbyVisionProfileLevelFromDvcc with data size %zu", size);
        parseDolbyVisionProfileLevelFromDvcc(ptr, size, msg);
        sp<ABuffer> buffer = new (std::nothrow) ABuffer(size);
        if (buffer.get() == nullptr || buffer->base() == nullptr) {
            return NO_MEMORY;
        }
        memcpy(buffer->data(), data, size);
        }

        buffer->meta()->setInt32("csd", true);
        buffer->meta()->setInt64("timeUs", 0);
        msg->setBuffer("csd-0", buffer);

        const uint8_t *ptr = (const uint8_t *)data;
        ALOGV("DV: calling parseDolbyVisionProfileLevelFromDvcc with data size %zu", size);
        parseDolbyVisionProfileLevelFromDvcc(ptr, size, msg);
        msg->setBuffer("csd-2", buffer);
    }

    *format = msg;
@@ -2041,49 +2038,59 @@ status_t convertMessageToMetaData(const sp<AMessage> &msg, sp<MetaData> &meta) {
                   mime == MEDIA_MIMETYPE_IMAGE_AVIF) {
            meta->setData(kKeyAV1C, 0, csd0->data(), csd0->size());
        } else if (mime == MEDIA_MIMETYPE_VIDEO_DOLBY_VISION) {
            int32_t needCreateDoviCSD = 0;
            int32_t profile = 0;
            uint8_t bl_compatibility = 0;
            if (msg->findInt32("profile", &profile)) {
            int32_t profile = -1;
            uint8_t blCompatibilityId = -1;
            int32_t level = 0;
            uint8_t profileVal = -1;
            uint8_t profileVal1 = -1;
            uint8_t profileVal2 = -1;
            constexpr size_t dvccSize = 24;

            const ALookup<uint8_t, int32_t> &profiles =
                getDolbyVisionProfileTable();
            const ALookup<uint8_t, int32_t> &levels =
                getDolbyVisionLevelsTable();

            if (!msg->findBuffer("csd-2", &csd2)) {
                // MP4 extractors are expected to generate csd buffer
                // some encoders might not be generating it, in which
                // case we populate the track metadata dv (cc|vc|wc)
                // from the 'profile' and 'level' info.
                // This is done according to Dolby Vision ISOBMFF spec

                if (!msg->findInt32("profile", &profile)) {
                    ALOGE("Dolby Vision profile not found");
                    return BAD_VALUE;
                }
                msg->findInt32("level", &level);

                if (profile == DolbyVisionProfileDvheSt) {
                    profile = 8;
                    bl_compatibility = 4;
                } else if (profile == DolbyVisionProfileDvavSe) {
                    profile = 9;
                    bl_compatibility = 2;
                    if (!profiles.rlookup(DolbyVisionProfileDvheSt, &profileVal)) { // dvhe.08
                        ALOGE("Dolby Vision profile lookup error");
                        return BAD_VALUE;
                    }
                if (profile == 8 || profile == 9) {
                    needCreateDoviCSD = 1;
                    blCompatibilityId = 4;
                } else if (profile == DolbyVisionProfileDvavSe) {
                    if (!profiles.rlookup(DolbyVisionProfileDvavSe, &profileVal)) { // dvav.09
                        ALOGE("Dolby Vision profile lookup error");
                        return BAD_VALUE;
                    }
                    blCompatibilityId = 2;
                } else {
                ALOGW("did not find dolby vision profile");
                    ALOGE("Dolby Vision profile look up error");
                    return BAD_VALUE;
                }
            // No dovi csd data, need to create it
            if (needCreateDoviCSD) {
                uint8_t dvcc[24];
                int32_t level = 0;
                uint8_t level_val = 0;

                if (msg->findInt32("level", &level)) {
                    const static ALookup<int32_t, uint8_t> levels {
                        {DolbyVisionLevelUnknown, 0},
                        {DolbyVisionLevelHd24, 1},
                        {DolbyVisionLevelHd30, 2},
                        {DolbyVisionLevelFhd24, 3},
                        {DolbyVisionLevelFhd30, 4},
                        {DolbyVisionLevelFhd60, 5},
                        {DolbyVisionLevelUhd24, 6},
                        {DolbyVisionLevelUhd30, 7},
                        {DolbyVisionLevelUhd48, 8},
                        {DolbyVisionLevelUhd60, 9},
                        {DolbyVisionLevelUhd120, 10},
                        {DolbyVisionLevel8k30, 11},
                        {DolbyVisionLevel8k60, 12},
                    };
                    levels.map(level, &level_val);
                    ALOGV("found dolby vision level: %d, value: %d", level, level_val);
                profile = (int32_t) profileVal;

                uint8_t level_val = 0;
                if (!levels.map(level, &level_val)) {
                    ALOGE("Dolby Vision level lookup error");
                    return BAD_VALUE;
                }

                std::vector<uint8_t> dvcc(dvccSize);

                dvcc[0] = 1; // major version
                dvcc[1] = 0; // minor version
                dvcc[2] = (uint8_t)((profile & 0x7f) << 1); // dolby vision profile
@@ -2091,51 +2098,57 @@ status_t convertMessageToMetaData(const sp<AMessage> &msg, sp<MetaData> &meta) {
                dvcc[3] = (uint8_t)((level_val & 0x1f) << 3); // dolby vision level
                dvcc[3] = (uint8_t)(dvcc[3] | (1 << 2)); // rpu_present_flag
                dvcc[3] = (uint8_t)(dvcc[3] | (1)); // bl_present_flag
                dvcc[4] = (uint8_t)(bl_compatibility << 4);// bl_compatibility id

                std::vector<uint8_t> dvcc_data(24);
                memcpy(dvcc_data.data(), dvcc, 24);
                if (profile > 10) {
                    meta->setData(kKeyDVWC, kTypeDVWC, dvcc_data.data(), 24);
                } else if (profile > 7) {
                    meta->setData(kKeyDVVC, kTypeDVVC, dvcc_data.data(), 24);
                dvcc[4] = (uint8_t)(blCompatibilityId << 4); // bl_compatibility id

                profiles.rlookup(DolbyVisionProfileDvav110, &profileVal);
                profiles.rlookup(DolbyVisionProfileDvheDtb, &profileVal1);
                if (profile > (int32_t) profileVal) {
                    meta->setData(kKeyDVWC, kTypeDVWC, dvcc.data(), dvccSize);
                } else if (profile > (int32_t) profileVal1) {
                    meta->setData(kKeyDVVC, kTypeDVVC, dvcc.data(), dvccSize);
                } else {
                    meta->setData(kKeyDVCC, kTypeDVCC, dvcc_data.data(), 24);
                }
            } else if (csd0size >= 24) { // have dovi csd, just send it out...
                uint8_t *dvconfig = csd0->data() + (csd0size -24);
                profile = dvconfig[2] >> 1;
                if (profile > 10) {
                    meta->setData(kKeyDVWC, kTypeDVWC, dvconfig, 24);
                } else if (profile > 7) {
                    meta->setData(kKeyDVVC, kTypeDVVC, dvconfig, 24);
                    meta->setData(kKeyDVCC, kTypeDVCC, dvcc.data(), dvccSize);
                }

            } else {
                // we have csd-2, just use that to populate dvcc
                if (csd2->size() == dvccSize) {
                    uint8_t *dvcc = csd2->data();
                    profile = dvcc[2] >> 1;

                    profiles.rlookup(DolbyVisionProfileDvav110, &profileVal);
                    profiles.rlookup(DolbyVisionProfileDvheDtb, &profileVal1);
                    if (profile > (int32_t) profileVal) {
                        meta->setData(kKeyDVWC, kTypeDVWC, csd2->data(), csd2->size());
                    } else if (profile > (int32_t) profileVal1) {
                        meta->setData(kKeyDVVC, kTypeDVVC, csd2->data(), csd2->size());
                    } else {
                    meta->setData(kKeyDVCC, kTypeDVCC, dvconfig, 24);
                         meta->setData(kKeyDVCC, kTypeDVCC, csd2->data(), csd2->size());
                    }

                } else {
                    ALOGE("Convert MessageToMetadata csd-2 is present but not valid");
                    return BAD_VALUE;
                }

            // Send the avc/hevc/av1 csd data...
            if (csd0size >= 24) {
                sp<ABuffer> csd;
                if ( profile > 1 && profile < 9) {
                    if (msg->findBuffer("csd-hevc", &csd)) {
                        meta->setData(kKeyHVCC, kTypeHVCC, csd->data(), csd->size());
                    } else if (csd0size > 24) {
            }
            profiles.rlookup(DolbyVisionProfileDvavPen, &profileVal);
            profiles.rlookup(DolbyVisionProfileDvavSe, &profileVal1);
            profiles.rlookup(DolbyVisionProfileDvav110, &profileVal2);
            if ((profile > (int32_t) profileVal) && (profile < (int32_t) profileVal1)) {
                std::vector<uint8_t> hvcc(csd0size + 1024);
                size_t outsize = reassembleHVCC(csd0, hvcc.data(), hvcc.size(), 4);
                meta->setData(kKeyHVCC, kTypeHVCC, hvcc.data(), outsize);
                    }
                } else if (profile == 9) {
            } else if (profile == (int32_t) profileVal2) {
                meta->setData(kKeyAV1C, 0, csd0->data(), csd0->size());
            } else {
                sp<ABuffer> csd1;
                    if (msg->findBuffer("csd-avc", &csd)) {
                        meta->setData(kKeyAVCC, kTypeAVCC, csd->data(), csd->size());
                    } else if (msg->findBuffer("csd-1", &csd1)) {
                if (msg->findBuffer("csd-1", &csd1)) {
                    std::vector<char> avcc(csd0size + csd1->size() + 1024);
                    size_t outsize = reassembleAVCC(csd0, csd1, avcc.data());
                    meta->setData(kKeyAVCC, kTypeAVCC, avcc.data(), outsize);
                    } else { // for dolby vision avc, csd0 also holds csd1
                }
                else {
                    // for dolby vision avc, csd0 also holds csd1
                    size_t i = 0;
                    int csd0realsize = 0;
                    do {
@@ -2166,9 +2179,6 @@ status_t convertMessageToMetaData(const sp<AMessage> &msg, sp<MetaData> &meta) {
                    size_t outsize = reassembleAVCC(buffer0, buffer1, avcc.data());
                    meta->setData(kKeyAVCC, kTypeAVCC, avcc.data(), outsize);
                }
                } else if (profile == 10) {
                    meta->setData(kKeyAV1C, 0, csd0->data(), csd0->size() - 24);
                }
            }
        } else if (mime == MEDIA_MIMETYPE_VIDEO_VP9) {
            meta->setData(kKeyVp9CodecPrivate, 0, csd0->data(), csd0->size());
@@ -2216,6 +2226,17 @@ status_t convertMessageToMetaData(const sp<AMessage> &msg, sp<MetaData> &meta) {
        meta->setData(kKeyStreamHeader, 'mdat', csd0->data(), csd0->size());
    } else if (msg->findBuffer("d263", &csd0)) {
        meta->setData(kKeyD263, kTypeD263, csd0->data(), csd0->size());
    } else if (mime == MEDIA_MIMETYPE_VIDEO_DOLBY_VISION && msg->findBuffer("csd-2", &csd2)) {
        meta->setData(kKeyDVCC, kTypeDVCC, csd2->data(), csd2->size());

        // Remove CSD-2 from the data here to avoid duplicate data in meta
        meta->remove(kKeyOpaqueCSD2);

        if (msg->findBuffer("csd-avc", &csd0)) {
            meta->setData(kKeyAVCC, kTypeAVCC, csd0->data(), csd0->size());
        } else if (msg->findBuffer("csd-hevc", &csd0)) {
            meta->setData(kKeyHVCC, kTypeHVCC, csd0->data(), csd0->size());
        }
    }
    // XXX TODO add whatever other keys there are