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

Commit c6c62e12 authored by Andreas Huber's avatar Andreas Huber
Browse files

Support for ogg(vorbis) metadata in stagefright including album art.

Change-Id: I0c8c0136cebe2d2d97caabb7bc0c65be86329dbb
related-to-bug: 2713414
parent cf3fa85c
Loading
Loading
Loading
Loading
+201 −14
Original line number Diff line number Diff line
@@ -78,6 +78,8 @@ struct MyVorbisExtractor {

    void init();

    sp<MetaData> getFileMetaData() { return mFileMeta; }

private:
    struct Page {
        uint64_t mGranulePosition;
@@ -100,6 +102,7 @@ private:
    vorbis_comment mVc;

    sp<MetaData> mMeta;
    sp<MetaData> mFileMeta;

    ssize_t readPage(off_t offset, Page *page);
    status_t findNextPage(off_t startOffset, off_t *pageOffset);
@@ -107,6 +110,9 @@ private:
    void verifyHeader(
            MediaBuffer *buffer, uint8_t type);

    void parseFileMetaData();
    void extractAlbumArt(const void *data, size_t size);

    MyVorbisExtractor(const MyVorbisExtractor &);
    MyVorbisExtractor &operator=(const MyVorbisExtractor &);
};
@@ -188,9 +194,14 @@ MyVorbisExtractor::MyVorbisExtractor(const sp<DataSource> &source)
      mNextLaceIndex(0),
      mFirstDataOffset(-1) {
    mCurrentPage.mNumSegments = 0;

    vorbis_info_init(&mVi);
    vorbis_comment_init(&mVc);
}

MyVorbisExtractor::~MyVorbisExtractor() {
    vorbis_comment_clear(&mVc);
    vorbis_info_clear(&mVi);
}

sp<MetaData> MyVorbisExtractor::getFormat() const {
@@ -305,7 +316,7 @@ ssize_t MyVorbisExtractor::readPage(off_t offset, Page *page) {
        tmp.append(x);
    }

    LOGV("%c %s", page->mFlags & 1 ? '+' : ' ', tmp.string());
    // LOGV("%c %s", page->mFlags & 1 ? '+' : ' ', tmp.string());

    return sizeof(header) + page->mNumSegments + totalSize;
}
@@ -422,10 +433,6 @@ status_t MyVorbisExtractor::readNextPacket(MediaBuffer **out) {
}

void MyVorbisExtractor::init() {
    vorbis_info_init(&mVi);

    vorbis_comment mVc;

    mMeta = new MetaData;
    mMeta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_VORBIS);

@@ -509,6 +516,8 @@ void MyVorbisExtractor::verifyHeader(
        case 3:
        {
            CHECK_EQ(0, _vorbis_unpack_comment(&mVc, &bits));

            parseFileMetaData();
            break;
        }

@@ -530,6 +539,192 @@ uint64_t MyVorbisExtractor::approxBitrate() {
    return (mVi.bitrate_lower + mVi.bitrate_upper) / 2;
}

void MyVorbisExtractor::parseFileMetaData() {
    mFileMeta = new MetaData;
    mFileMeta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_CONTAINER_OGG);

    struct {
        const char *const mTag;
        uint32_t mKey;
    } kMap[] = {
        { "TITLE", kKeyTitle },
        { "ARTIST", kKeyArtist },
        { "ALBUM", kKeyAlbum },
        { "COMPOSER", kKeyComposer },
        { "GENRE", kKeyGenre },
        { "AUTHOR", kKeyAuthor },
        { "TRACKNUMBER", kKeyCDTrackNumber },
        { "DISCNUMBER", kKeyDiscNumber },
        { "DATE", kKeyDate },
        { "LYRICIST", kKeyWriter },
        { "METADATA_BLOCK_PICTURE", kKeyAlbumArt },
    };

    for (int i = 0; i < mVc.comments; ++i) {
        const char *comment = mVc.user_comments[i];

        for (size_t j = 0; j < sizeof(kMap) / sizeof(kMap[0]); ++j) {
            size_t tagLen = strlen(kMap[j].mTag);
            if (!strncasecmp(kMap[j].mTag, comment, tagLen)
                    && comment[tagLen] == '=') {
                if (kMap[j].mKey == kKeyAlbumArt) {
                    extractAlbumArt(
                            &comment[tagLen + 1],
                            mVc.comment_lengths[i] - tagLen - 1);
                } else {
                    mFileMeta->setCString(kMap[j].mKey, &comment[tagLen + 1]);
                }
            }
        }

    }

#if 0
    for (int i = 0; i < mVc.comments; ++i) {
        LOGI("comment #%d: '%s'", i + 1, mVc.user_comments[i]);
    }
#endif
}

// The returned buffer should be free()d.
static uint8_t *DecodeBase64(const char *s, size_t size, size_t *outSize) {
    *outSize = 0;

    if ((size % 4) != 0) {
        return NULL;
    }

    size_t n = size;
    size_t padding = 0;
    if (n >= 1 && s[n - 1] == '=') {
        padding = 1;

        if (n >= 2 && s[n - 2] == '=') {
            padding = 2;
        }
    }

    size_t outLen = 3 * size / 4 - padding;

    *outSize = outLen;

    void *buffer = malloc(outLen);

    uint8_t *out = (uint8_t *)buffer;
    size_t j = 0;
    uint32_t accum = 0;
    for (size_t i = 0; i < n; ++i) {
        char c = s[i];
        unsigned value;
        if (c >= 'A' && c <= 'Z') {
            value = c - 'A';
        } else if (c >= 'a' && c <= 'z') {
            value = 26 + c - 'a';
        } else if (c >= '0' && c <= '9') {
            value = 52 + c - '0';
        } else if (c == '+') {
            value = 62;
        } else if (c == '/') {
            value = 63;
        } else if (c != '=') {
            return NULL;
        } else {
            if (i < n - padding) {
                return NULL;
            }

            value = 0;
        }

        accum = (accum << 6) | value;

        if (((i + 1) % 4) == 0) {
            out[j++] = (accum >> 16);

            if (j < outLen) { out[j++] = (accum >> 8) & 0xff; }
            if (j < outLen) { out[j++] = accum & 0xff; }

            accum = 0;
        }
    }

    return (uint8_t *)buffer;
}

void MyVorbisExtractor::extractAlbumArt(const void *data, size_t size) {
    LOGV("extractAlbumArt from '%s'", (const char *)data);

    size_t flacSize;
    uint8_t *flac = DecodeBase64((const char *)data, size, &flacSize);

    if (flac == NULL) {
        LOGE("malformed base64 encoded data.");
        return;
    }

    LOGV("got flac of size %d", flacSize);

    uint32_t picType;
    uint32_t typeLen;
    uint32_t descLen;
    uint32_t dataLen;
    char type[128];

    if (flacSize < 8) {
        goto exit;
    }

    picType = U32_AT(flac);

    if (picType != 3) {
        // This is not a front cover.
        goto exit;
    }

    typeLen = U32_AT(&flac[4]);
    if (typeLen + 1 > sizeof(type)) {
        goto exit;
    }

    if (flacSize < 8 + typeLen) {
        goto exit;
    }

    memcpy(type, &flac[8], typeLen);
    type[typeLen] = '\0';

    LOGV("picType = %d, type = '%s'", picType, type);

    if (!strcmp(type, "-->")) {
        // This is not inline cover art, but an external url instead.
        goto exit;
    }

    descLen = U32_AT(&flac[8 + typeLen]);

    if (flacSize < 32 + typeLen + descLen) {
        goto exit;
    }

    dataLen = U32_AT(&flac[8 + typeLen + 4 + descLen + 16]);

    if (flacSize < 32 + typeLen + descLen + dataLen) {
        goto exit;
    }

    LOGV("got image data, %d trailing bytes",
         flacSize - 32 - typeLen - descLen - dataLen);

    mFileMeta->setData(
            kKeyAlbumArt, 0, &flac[8 + typeLen + 4 + descLen + 20], dataLen);

    mFileMeta->setCString(kKeyAlbumArtMIME, type);

exit:
    free(flac);
    flac = NULL;
}

////////////////////////////////////////////////////////////////////////////////

OggExtractor::OggExtractor(const sp<DataSource> &source)
@@ -570,15 +765,7 @@ sp<MetaData> OggExtractor::getTrackMetaData(
}

sp<MetaData> OggExtractor::getMetaData() {
    sp<MetaData> meta = new MetaData;

    if (mInitCheck != OK) {
        return meta;
    }

    meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_CONTAINER_OGG);

    return meta;
    return mImpl->getFileMetaData();
}

bool SniffOgg(
+0 −50
Original line number Diff line number Diff line
@@ -26,10 +26,6 @@
// Sonivox includes
#include <libsonivox/eas.h>

// Ogg Vorbis includes
#include <Tremolo/ivorbiscodec.h>
#include <Tremolo/ivorbisfile.h>

namespace android {

StagefrightMediaScanner::StagefrightMediaScanner()
@@ -103,48 +99,6 @@ static status_t HandleMIDI(
    return OK;
}

static status_t HandleOGG(
        const char *filename, MediaScannerClient *client) {
    int duration;

    FILE *file = fopen(filename,"r");
    if (!file)
        return UNKNOWN_ERROR;

    OggVorbis_File vf;
    if (ov_open(file, &vf, NULL, 0) < 0) {
        return UNKNOWN_ERROR;
    }

    char **ptr=ov_comment(&vf,-1)->user_comments;
    while(*ptr){
        char *val = strstr(*ptr, "=");
        if (val) {
            int keylen = val++ - *ptr;
            char key[keylen + 1];
            strncpy(key, *ptr, keylen);
            key[keylen] = 0;
            if (!client->addStringTag(key, val)) goto failure;
        }
        ++ptr;
    }

    // Duration
    duration = ov_time_total(&vf, -1);
    if (duration > 0) {
        char buffer[20];
        sprintf(buffer, "%d", duration);
        if (!client->addStringTag("duration", buffer)) goto failure;
    }

    ov_clear(&vf); // this also closes the FILE
    return OK;

failure:
    ov_clear(&vf); // this also closes the FILE
    return UNKNOWN_ERROR;
}

status_t StagefrightMediaScanner::processFile(
        const char *path, const char *mimeType,
        MediaScannerClient &client) {
@@ -176,10 +130,6 @@ status_t StagefrightMediaScanner::processFile(
        return HandleMIDI(path, &client);
    }

    if (!strcasecmp(extension, ".ogg")) {
        return HandleOGG(path, &client);
    }

    if (mRetriever->setDataSource(path) == OK
            && mRetriever->setMode(
                METADATA_MODE_METADATA_RETRIEVAL_ONLY) == OK) {