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

Commit 61fa01d4 authored by Andreas Huber's avatar Andreas Huber Committed by Android (Google) Code Review
Browse files

Merge "Support for 'iTunes-style' metadata in .mp4 and .3gp files."

parents 243bf50e c2c9dd32
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -60,6 +60,7 @@ enum {
    kKeyAuthor            = 'auth',  // cstring
    kKeyCDTrackNumber     = 'cdtr',  // cstring
    kKeyDate              = 'date',  // cstring
    kKeyWriter            = 'writ',  // cstring
};

enum {
+232 −12
Original line number Diff line number Diff line
@@ -154,7 +154,8 @@ MPEG4Extractor::MPEG4Extractor(const sp<DataSource> &source)
      mHaveMetadata(false),
      mHasVideo(false),
      mFirstTrack(NULL),
      mLastTrack(NULL) {
      mLastTrack(NULL),
      mFileMetaData(new MetaData) {
}

MPEG4Extractor::~MPEG4Extractor() {
@@ -169,20 +170,12 @@ MPEG4Extractor::~MPEG4Extractor() {
}

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

    status_t err;
    if ((err = readMetaData()) != OK) {
        return meta;
    }

    if (mHasVideo) {
        meta->setCString(kKeyMIMEType, "video/mp4");
    } else {
        meta->setCString(kKeyMIMEType, "audio/mp4");
        return new MetaData;
    }

    return meta;
    return mFileMetaData;
}

size_t MPEG4Extractor::countTracks() {
@@ -256,6 +249,12 @@ status_t MPEG4Extractor::readMetaData() {
    }

    if (mHaveMetadata) {
        if (mHasVideo) {
            mFileMetaData->setCString(kKeyMIMEType, "video/mp4");
        } else {
            mFileMetaData->setCString(kKeyMIMEType, "audio/mp4");
        }

        return OK;
    }

@@ -270,6 +269,41 @@ static void MakeFourCCString(uint32_t x, char *s) {
    s[4] = '\0';
}

struct PathAdder {
    PathAdder(Vector<uint32_t> *path, uint32_t chunkType)
        : mPath(path) {
        mPath->push(chunkType);
    }

    ~PathAdder() {
        mPath->pop();
    }

private:
    Vector<uint32_t> *mPath;

    PathAdder(const PathAdder &);
    PathAdder &operator=(const PathAdder &);
};

static bool underMetaDataPath(const Vector<uint32_t> &path) {
    return path.size() >= 5
        && path[0] == FOURCC('m', 'o', 'o', 'v')
        && path[1] == FOURCC('u', 'd', 't', 'a')
        && path[2] == FOURCC('m', 'e', 't', 'a')
        && path[3] == FOURCC('i', 'l', 's', 't');
}

// Given a time in seconds since Jan 1 1904, produce a human-readable string.
static void convertTimeToDate(int64_t time_1904, String8 *s) {
    time_t time_1970 = time_1904 - (((66 * 365 + 17) * 24) * 3600);

    char tmp[32];
    strftime(tmp, sizeof(tmp), "%Y%m%dT%H%M%S.000Z", gmtime(&time_1970));

    s->setTo(tmp);
}

status_t MPEG4Extractor::parseChunk(off_t *offset, int depth) {
    uint32_t hdr[2];
    if (mDataSource->readAt(*offset, hdr, 8) < 8) {
@@ -297,7 +331,8 @@ status_t MPEG4Extractor::parseChunk(off_t *offset, int depth) {

    char buffer[256];
    if (chunk_size <= sizeof(buffer)) {
        if (mDataSource->readAt(*offset, buffer, chunk_size) < chunk_size) {
        if (mDataSource->readAt(*offset, buffer, chunk_size)
                < (ssize_t)chunk_size) {
            return ERROR_IO;
        }

@@ -305,8 +340,25 @@ status_t MPEG4Extractor::parseChunk(off_t *offset, int depth) {
    }
#endif

    PathAdder autoAdder(&mPath, chunk_type);

    off_t chunk_data_size = *offset + chunk_size - data_offset;

    if (chunk_type != FOURCC('c', 'p', 'r', 't')
            && mPath.size() == 5 && underMetaDataPath(mPath)) {
        off_t stop_offset = *offset + chunk_size;
        *offset = data_offset;
        while (*offset < stop_offset) {
            status_t err = parseChunk(offset, depth + 1);
            if (err != OK) {
                return err;
            }
        }
        CHECK_EQ(*offset, stop_offset);

        return OK;
    }

    switch(chunk_type) {
        case FOURCC('m', 'o', 'o', 'v'):
        case FOURCC('t', 'r', 'a', 'k'):
@@ -319,6 +371,8 @@ status_t MPEG4Extractor::parseChunk(off_t *offset, int depth) {
        case FOURCC('t', 'r', 'a', 'f'):
        case FOURCC('m', 'f', 'r', 'a'):
        case FOURCC('s', 'k', 'i' ,'p'):
        case FOURCC('u', 'd', 't', 'a'):
        case FOURCC('i', 'l', 's', 't'):
        {
            off_t stop_offset = *offset + chunk_size;
            *offset = data_offset;
@@ -748,6 +802,76 @@ status_t MPEG4Extractor::parseChunk(off_t *offset, int depth) {
            break;
        }

        case FOURCC('m', 'e', 't', 'a'):
        {
            uint8_t buffer[4];
            CHECK(chunk_data_size >= (off_t)sizeof(buffer));
            if (mDataSource->readAt(
                        data_offset, buffer, 4) < 4) {
                return ERROR_IO;
            }

            if (U32_AT(buffer) != 0) {
                // Should be version 0, flags 0.
                return ERROR_MALFORMED;
            }

            off_t stop_offset = *offset + chunk_size;
            *offset = data_offset + sizeof(buffer);
            while (*offset < stop_offset) {
                status_t err = parseChunk(offset, depth + 1);
                if (err != OK) {
                    return err;
                }
            }
            CHECK_EQ(*offset, stop_offset);
            break;
        }

        case FOURCC('d', 'a', 't', 'a'):
        {
            if (mPath.size() == 6 && underMetaDataPath(mPath)) {
                status_t err = parseMetaData(data_offset, chunk_data_size);

                if (err != OK) {
                    return err;
                }
            }

            *offset += chunk_size;
            break;
        }

        case FOURCC('m', 'v', 'h', 'd'):
        {
            if (chunk_data_size < 12) {
                return ERROR_MALFORMED;
            }

            uint8_t header[12];
            if (mDataSource->readAt(
                        data_offset, header, sizeof(header))
                    < (ssize_t)sizeof(header)) {
                return ERROR_IO;
            }

            int64_t creationTime;
            if (header[0] == 1) {
                creationTime = U64_AT(&header[4]);
            } else {
                CHECK_EQ(header[0], 0);
                creationTime = U32_AT(&header[4]);
            }

            String8 s;
            convertTimeToDate(creationTime, &s);

            mFileMetaData->setCString(kKeyDate, s.string());

            *offset += chunk_size;
            break;
        }

        default:
        {
            *offset += chunk_size;
@@ -758,6 +882,102 @@ status_t MPEG4Extractor::parseChunk(off_t *offset, int depth) {
    return OK;
}

status_t MPEG4Extractor::parseMetaData(off_t offset, size_t size) {
    if (size < 4) {
        return ERROR_MALFORMED;
    }

    uint8_t *buffer = new uint8_t[size + 1];
    if (mDataSource->readAt(
                offset, buffer, size) != (ssize_t)size) {
        delete[] buffer;
        buffer = NULL;

        return ERROR_IO;
    }

    uint32_t flags = U32_AT(buffer);

    uint32_t metadataKey = 0;
    switch (mPath[4]) {
        case FOURCC(0xa9, 'a', 'l', 'b'):
        {
            metadataKey = kKeyAlbum;
            break;
        }
        case FOURCC(0xa9, 'A', 'R', 'T'):
        {
            metadataKey = kKeyArtist;
            break;
        }
        case FOURCC(0xa9, 'd', 'a', 'y'):
        {
            metadataKey = kKeyYear;
            break;
        }
        case FOURCC(0xa9, 'n', 'a', 'm'):
        {
            metadataKey = kKeyTitle;
            break;
        }
        case FOURCC(0xa9, 'w', 'r', 't'):
        {
            metadataKey = kKeyWriter;
            break;
        }
        case FOURCC('c', 'o', 'v', 'r'):
        {
            metadataKey = kKeyAlbumArt;
            break;
        }
        case FOURCC('g', 'n', 'r', 'e'):
        {
            metadataKey = kKeyGenre;
            break;
        }
        case FOURCC('t', 'r', 'k', 'n'):
        {
            if (size == 16 && flags == 0) {
                char tmp[16];
                sprintf(tmp, "%d/%d",
                        (int)buffer[size - 5], (int)buffer[size - 3]);

                printf("track: %s\n", tmp);
                mFileMetaData->setCString(kKeyCDTrackNumber, tmp);
            }
            break;
        }
        default:
            break;
    }

    if (size >= 8 && metadataKey) {
        if (metadataKey == kKeyAlbumArt) {
            mFileMetaData->setData(
                    kKeyAlbumArt, MetaData::TYPE_NONE,
                    buffer + 8, size - 8);
        } else if (metadataKey == kKeyGenre) {
            if (flags == 0) {
                // uint8_t
                char genre[10];
                sprintf(genre, "%d", (int)buffer[size - 1]);

                mFileMetaData->setCString(metadataKey, genre);
            }
        } else {
            buffer[size] = '\0';

            mFileMetaData->setCString(
                    metadataKey, (const char *)buffer + 8);
        }
    }

    delete[] buffer;
    buffer = NULL;

    return OK;
}

sp<MediaSource> MPEG4Extractor::getTrack(size_t index) {
    status_t err;
    if ((err = readMetaData()) != OK) {
+1 −1
Original line number Diff line number Diff line
@@ -315,6 +315,7 @@ void StagefrightMetadataRetriever::parseMetaData() {
        { kKeyGenre, METADATA_KEY_GENRE },
        { kKeyTitle, METADATA_KEY_TITLE },
        { kKeyYear, METADATA_KEY_YEAR },
        { kKeyWriter, METADATA_KEY_WRITER },
    };
    static const size_t kNumMapEntries = sizeof(kMap) / sizeof(kMap[0]);

@@ -357,7 +358,6 @@ void StagefrightMetadataRetriever::parseMetaData() {

    // The duration value is a string representing the duration in ms.
    sprintf(tmp, "%lld", (maxDurationUs + 500) / 1000);

    mMetaData.add(METADATA_KEY_DURATION, String8(tmp));
}

+5 −0
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@
#define MPEG4_EXTRACTOR_H_

#include <media/stagefright/MediaExtractor.h>
#include <utils/Vector.h>

namespace android {

@@ -55,10 +56,14 @@ private:

    Track *mFirstTrack, *mLastTrack;

    sp<MetaData> mFileMetaData;

    uint32_t mHandlerType;
    Vector<uint32_t> mPath;

    status_t readMetaData();
    status_t parseChunk(off_t *offset, int depth);
    status_t parseMetaData(off_t offset, size_t size);

    MPEG4Extractor(const MPEG4Extractor &);
    MPEG4Extractor &operator=(const MPEG4Extractor &);