summaryrefslogtreecommitdiffstats
path: root/media/libstagefright/id3
diff options
context:
space:
mode:
authorAndreas Huber <andih@google.com>2010-01-11 15:35:19 -0800
committerAndreas Huber <andih@google.com>2010-01-12 09:14:15 -0800
commitfc9ba09e3bb368f823d473f5e2bb9aa32dba6289 (patch)
treee7a6e0357e39c58d050b23a94b61f734e5578af7 /media/libstagefright/id3
parent58e1f78683d9230932c4d5bee53b79fc685b5995 (diff)
downloadframeworks_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.mk27
-rw-r--r--media/libstagefright/id3/ID3.cpp465
-rw-r--r--media/libstagefright/id3/testid3.cpp156
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;
+}