diff options
author | Andreas Huber <andih@google.com> | 2010-01-11 15:35:19 -0800 |
---|---|---|
committer | Andreas Huber <andih@google.com> | 2010-01-12 09:14:15 -0800 |
commit | fc9ba09e3bb368f823d473f5e2bb9aa32dba6289 (patch) | |
tree | e7a6e0357e39c58d050b23a94b61f734e5578af7 /media/libstagefright/id3 | |
parent | 58e1f78683d9230932c4d5bee53b79fc685b5995 (diff) | |
download | frameworks_av-fc9ba09e3bb368f823d473f5e2bb9aa32dba6289.zip frameworks_av-fc9ba09e3bb368f823d473f5e2bb9aa32dba6289.tar.gz frameworks_av-fc9ba09e3bb368f823d473f5e2bb9aa32dba6289.tar.bz2 |
Squashed commit of the following:
commit f81bb1dac5ef107bb0d7d5d756fb1ffa532ba2cc
Author: Andreas Huber <andih@google.com>
Date: Mon Jan 11 14:55:56 2010 -0800
Support for duration metadata, midi and ogg-vorbis files (in mediascanner)
commit 0b1385a0dc156ce27985a1ff757c4c142fd7ec39
Author: Andreas Huber <andih@google.com>
Date: Mon Jan 11 14:20:45 2010 -0800
Refactor meta data logic. Container specific metadata is now also returned by the MediaExtractor.
commit f9818dfac39c96e5fefe8c8295e60580692d5990
Author: Andreas Huber <andih@google.com>
Date: Fri Jan 8 14:26:09 2010 -0800
A first pass at supporting metadata through ID3 tags.
commit 476e9e253633336ab790f943e2d6c0cd8991d76a
Author: Andreas Huber <andih@google.com>
Date: Thu Jan 7 15:48:44 2010 -0800
Initial checkin of ID3 (V2.2 and V2.3) parser for use in stagefright.
related-to-bug: 2295456
Diffstat (limited to 'media/libstagefright/id3')
-rw-r--r-- | media/libstagefright/id3/Android.mk | 27 | ||||
-rw-r--r-- | media/libstagefright/id3/ID3.cpp | 465 | ||||
-rw-r--r-- | media/libstagefright/id3/testid3.cpp | 156 |
3 files changed, 648 insertions, 0 deletions
diff --git a/media/libstagefright/id3/Android.mk b/media/libstagefright/id3/Android.mk new file mode 100644 index 0000000..3c47e2e --- /dev/null +++ b/media/libstagefright/id3/Android.mk @@ -0,0 +1,27 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + ID3.cpp + +LOCAL_MODULE := libstagefright_id3 + +include $(BUILD_STATIC_LIBRARY) + +################################################################################ + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + testid3.cpp + +LOCAL_SHARED_LIBRARIES := \ + libstagefright libutils libbinder + +LOCAL_STATIC_LIBRARIES := \ + libstagefright_id3 + +LOCAL_MODULE := testid3 + +include $(BUILD_EXECUTABLE) + diff --git a/media/libstagefright/id3/ID3.cpp b/media/libstagefright/id3/ID3.cpp new file mode 100644 index 0000000..2b3ef1a --- /dev/null +++ b/media/libstagefright/id3/ID3.cpp @@ -0,0 +1,465 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "ID3" +#include <utils/Log.h> + +#include "../include/ID3.h" + +#include <media/stagefright/DataSource.h> +#include <media/stagefright/MediaDebug.h> +#include <media/stagefright/Utils.h> +#include <utils/String8.h> + +namespace android { + +ID3::ID3(const sp<DataSource> &source) + : mIsValid(false), + mData(NULL), + mSize(0), + mFirstFrameOffset(0), + mVersion(ID3_UNKNOWN) { + mIsValid = parse(source); +} + +ID3::~ID3() { + if (mData) { + free(mData); + mData = NULL; + } +} + +bool ID3::isValid() const { + return mIsValid; +} + +ID3::Version ID3::version() const { + return mVersion; +} + +bool ID3::parse(const sp<DataSource> &source) { + struct id3_header { + char id[3]; + uint8_t version_major; + uint8_t version_minor; + uint8_t flags; + uint8_t enc_size[4]; + }; + + id3_header header; + if (source->readAt( + 0, &header, sizeof(header)) != (ssize_t)sizeof(header)) { + return false; + } + + if (memcmp(header.id, "ID3", 3)) { + return false; + } + + if (header.version_major == 0xff || header.version_minor == 0xff) { + return false; + } + + if (header.version_major == 2) { + if (header.flags & 0x3f) { + // We only support the 2 high bits, if any of the lower bits are + // set, we cannot guarantee to understand the tag format. + return false; + } + + if (header.flags & 0x40) { + // No compression scheme has been decided yet, ignore the + // tag if compression is indicated. + + return false; + } + } else if (header.version_major == 3) { + if (header.flags & 0x1f) { + // We only support the 3 high bits, if any of the lower bits are + // set, we cannot guarantee to understand the tag format. + return false; + } + } else { + return false; + } + + size_t size = 0; + for (int32_t i = 0; i < 4; ++i) { + if (header.enc_size[i] & 0x80) { + return false; + } + + size = (size << 7) | header.enc_size[i]; + } + + mData = (uint8_t *)malloc(size); + + if (mData == NULL) { + return false; + } + + mSize = size; + + if (source->readAt(sizeof(header), mData, mSize) != (ssize_t)mSize) { + return false; + } + + if (header.flags & 0x80) { + LOGI("removing unsynchronization"); + removeUnsynchronization(); + } + + mFirstFrameOffset = 0; + if (header.version_major == 3 && (header.flags & 0x40)) { + // Version 2.3 has an optional extended header. + + if (mSize < 4) { + return false; + } + + size_t extendedHeaderSize = U32_AT(&mData[0]) + 4; + + if (extendedHeaderSize > mSize) { + return false; + } + + mFirstFrameOffset = extendedHeaderSize; + + uint16_t extendedFlags = 0; + if (extendedHeaderSize >= 6) { + extendedFlags = U16_AT(&mData[4]); + + if (extendedHeaderSize >= 10) { + size_t paddingSize = U32_AT(&mData[6]); + + if (mFirstFrameOffset + paddingSize > mSize) { + return false; + } + + mSize -= paddingSize; + } + + if (extendedFlags & 0x8000) { + LOGI("have crc"); + } + } + } + + if (header.version_major == 2) { + mVersion = ID3_V2_2; + } else { + CHECK_EQ(header.version_major, 3); + mVersion = ID3_V2_3; + } + + return true; +} + +void ID3::removeUnsynchronization() { + for (size_t i = 0; i + 1 < mSize; ++i) { + if (mData[i] == 0xff && mData[i + 1] == 0x00) { + memmove(&mData[i + 1], &mData[i + 2], mSize - i - 2); + --mSize; + } + } +} + +ID3::Iterator::Iterator(const ID3 &parent, const char *id) + : mParent(parent), + mID(NULL), + mOffset(mParent.mFirstFrameOffset), + mFrameData(NULL), + mFrameSize(0) { + if (id) { + mID = strdup(id); + } + + findFrame(); +} + +ID3::Iterator::~Iterator() { + if (mID) { + free(mID); + mID = NULL; + } +} + +bool ID3::Iterator::done() const { + return mFrameData == NULL; +} + +void ID3::Iterator::next() { + if (mFrameData == NULL) { + return; + } + + mOffset += mFrameSize; + + findFrame(); +} + +void ID3::Iterator::getID(String8 *id) const { + id->setTo(""); + + if (mFrameData == NULL) { + return; + } + + if (mParent.mVersion == ID3_V2_2) { + id->setTo((const char *)&mParent.mData[mOffset], 3); + } else { + CHECK_EQ(mParent.mVersion, ID3_V2_3); + id->setTo((const char *)&mParent.mData[mOffset], 4); + } +} + +static void convertISO8859ToString8( + const uint8_t *data, size_t size, + String8 *s) { + size_t utf8len = 0; + for (size_t i = 0; i < size; ++i) { + if (data[i] < 0x80) { + ++utf8len; + } else { + utf8len += 2; + } + } + + if (utf8len == size) { + // Only ASCII characters present. + + s->setTo((const char *)data, size); + return; + } + + char *tmp = new char[utf8len]; + char *ptr = tmp; + for (size_t i = 0; i < size; ++i) { + if (data[i] < 0x80) { + *ptr++ = data[i]; + } else if (data[i] < 0xc0) { + *ptr++ = 0xc2; + *ptr++ = data[i]; + } else { + *ptr++ = 0xc3; + *ptr++ = data[i] - 64; + } + } + + s->setTo(tmp, utf8len); + + delete[] tmp; + tmp = NULL; +} + +void ID3::Iterator::getString(String8 *id) const { + id->setTo(""); + + if (mFrameData == NULL) { + return; + } + + size_t n = mFrameSize - getHeaderLength() - 1; + + if (*mFrameData == 0x00) { + // ISO 8859-1 + convertISO8859ToString8(mFrameData + 1, n, id); + } else { + // UCS-2 + id->setTo((const char16_t *)(mFrameData + 1), n); + } +} + +const uint8_t *ID3::Iterator::getData(size_t *length) const { + *length = 0; + + if (mFrameData == NULL) { + return NULL; + } + + *length = mFrameSize - getHeaderLength(); + + return mFrameData; +} + +size_t ID3::Iterator::getHeaderLength() const { + if (mParent.mVersion == ID3_V2_2) { + return 6; + } else { + CHECK_EQ(mParent.mVersion, ID3_V2_3); + return 10; + } +} + +void ID3::Iterator::findFrame() { + for (;;) { + mFrameData = NULL; + mFrameSize = 0; + + if (mParent.mVersion == ID3_V2_2) { + if (mOffset + 6 > mParent.mSize) { + return; + } + + if (!memcmp(&mParent.mData[mOffset], "\0\0\0", 3)) { + return; + } + + mFrameSize = + (mParent.mData[mOffset + 3] << 16) + | (mParent.mData[mOffset + 4] << 8) + | mParent.mData[mOffset + 5]; + + mFrameSize += 6; + + if (mOffset + mFrameSize > mParent.mSize) { + LOGV("partial frame at offset %d (size = %d, bytes-remaining = %d)", + mOffset, mFrameSize, mParent.mSize - mOffset - 6); + return; + } + + mFrameData = &mParent.mData[mOffset + 6]; + + if (!mID) { + break; + } + + char id[4]; + memcpy(id, &mParent.mData[mOffset], 3); + id[3] = '\0'; + + if (!strcmp(id, mID)) { + break; + } + } else { + CHECK_EQ(mParent.mVersion, ID3_V2_3); + + if (mOffset + 10 > mParent.mSize) { + return; + } + + if (!memcmp(&mParent.mData[mOffset], "\0\0\0\0", 4)) { + return; + } + + mFrameSize = 10 + U32_AT(&mParent.mData[mOffset + 4]); + + if (mOffset + mFrameSize > mParent.mSize) { + LOGV("partial frame at offset %d (size = %d, bytes-remaining = %d)", + mOffset, mFrameSize, mParent.mSize - mOffset - 10); + return; + } + + mFrameData = &mParent.mData[mOffset + 10]; + + if (!mID) { + break; + } + + char id[5]; + memcpy(id, &mParent.mData[mOffset], 4); + id[4] = '\0'; + + if (!strcmp(id, mID)) { + break; + } + } + + mOffset += mFrameSize; + } +} + +static size_t StringSize(const uint8_t *start, uint8_t encoding) { + if (encoding== 0x00) { + // ISO 8859-1 + return strlen((const char *)start) + 1; + } + + // UCS-2 + size_t n = 0; + while (start[n] != '\0' || start[n + 1] != '\0') { + n += 2; + } + + return n; +} + +const void * +ID3::getAlbumArt(size_t *length, String8 *mime) const { + *length = 0; + mime->setTo(""); + + Iterator it(*this, mVersion == ID3_V2_3 ? "APIC" : "PIC"); + + while (!it.done()) { + size_t size; + const uint8_t *data = it.getData(&size); + + if (mVersion == ID3_V2_3) { + uint8_t encoding = data[0]; + mime->setTo((const char *)&data[1]); + size_t mimeLen = strlen((const char *)&data[1]) + 1; + + uint8_t picType = data[1 + mimeLen]; +#if 0 + if (picType != 0x03) { + // Front Cover Art + it.next(); + continue; + } +#endif + + size_t descLen = StringSize(&data[2 + mimeLen], encoding); + + *length = size - 2 - mimeLen - descLen; + + return &data[2 + mimeLen + descLen]; + } else { + uint8_t encoding = data[0]; + + if (!memcmp(&data[1], "PNG", 3)) { + mime->setTo("image/png"); + } else if (!memcmp(&data[1], "JPG", 3)) { + mime->setTo("image/jpeg"); + } else if (!memcmp(&data[1], "-->", 3)) { + mime->setTo("text/plain"); + } else { + return NULL; + } + +#if 0 + uint8_t picType = data[4]; + if (picType != 0x03) { + // Front Cover Art + it.next(); + continue; + } +#endif + + size_t descLen = StringSize(&data[5], encoding); + + *length = size - 5 - descLen; + + return &data[5 + descLen]; + } + } + + return NULL; +} + + +} // namespace android diff --git a/media/libstagefright/id3/testid3.cpp b/media/libstagefright/id3/testid3.cpp new file mode 100644 index 0000000..305b065 --- /dev/null +++ b/media/libstagefright/id3/testid3.cpp @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "../include/ID3.h" + +#include <ctype.h> +#include <dirent.h> + +#include <binder/ProcessState.h> +#include <media/stagefright/FileSource.h> +#include <media/stagefright/MediaDebug.h> + +#define MAXPATHLEN 256 + +using namespace android; + +static void hexdump(const void *_data, size_t size) { + const uint8_t *data = (const uint8_t *)_data; + size_t offset = 0; + while (offset < size) { + printf("0x%04x ", offset); + + size_t n = size - offset; + if (n > 16) { + n = 16; + } + + for (size_t i = 0; i < 16; ++i) { + if (i == 8) { + printf(" "); + } + + if (offset + i < size) { + printf("%02x ", data[offset + i]); + } else { + printf(" "); + } + } + + printf(" "); + + for (size_t i = 0; i < n; ++i) { + if (isprint(data[offset + i])) { + printf("%c", data[offset + i]); + } else { + printf("."); + } + } + + printf("\n"); + + offset += 16; + } +} + +void scanFile(const char *path) { + sp<FileSource> file = new FileSource(path); + CHECK_EQ(file->initCheck(), OK); + + ID3 tag(file); + if (!tag.isValid()) { + printf("FAIL %s\n", path); + } else { + printf("SUCCESS %s\n", path); + + ID3::Iterator it(tag, NULL); + while (!it.done()) { + String8 id; + it.getID(&id); + + CHECK(id.length() > 0); + if (id[0] == 'T') { + String8 text; + it.getString(&text); + + printf(" found text frame '%s': %s\n", id.string(), text.string()); + } else { + printf(" found frame '%s'.\n", id.string()); + } + + it.next(); + } + + size_t dataSize; + String8 mime; + const void *data = tag.getAlbumArt(&dataSize, &mime); + + if (data) { + printf("found album art: size=%d mime='%s'\n", dataSize, + mime.string()); + + hexdump(data, dataSize > 128 ? 128 : dataSize); + } + } +} + +void scan(const char *path) { + DIR *dir = opendir(path); + + if (dir == NULL) { + return; + } + + rewinddir(dir); + + struct dirent *ent; + while ((ent = readdir(dir)) != NULL) { + if (!strcmp(".", ent->d_name) || !strcmp("..", ent->d_name)) { + continue; + } + + char newPath[MAXPATHLEN]; + strcpy(newPath, path); + strcat(newPath, "/"); + strcat(newPath, ent->d_name); + + if (ent->d_type == DT_DIR) { + scan(newPath); + } else if (ent->d_type == DT_REG) { + size_t len = strlen(ent->d_name); + + if (len >= 4 + && !strcasecmp(ent->d_name + len - 4, ".mp3")) { + scanFile(newPath); + } + } + } + + closedir(dir); + dir = NULL; +} + +int main(int argc, char **argv) { + android::ProcessState::self()->startThreadPool(); + + DataSource::RegisterDefaultSniffers(); + + for (int i = 1; i < argc; ++i) { + scan(argv[i]); + } + + return 0; +} |