blob: 7f478f764bf5fa9ab26543d2fbc124baec13dc18 [file] [log] [blame]
/*
* 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/foundation/ADebug.h>
#include <media/stagefright/DataSource.h>
#include <media/stagefright/Utils.h>
#include <utils/String8.h>
#include <byteswap.h>
namespace android {
static const size_t kMaxMetadataSize = 3 * 1024 * 1024;
struct MemorySource : public DataSource {
MemorySource(const uint8_t *data, size_t size)
: mData(data),
mSize(size) {
}
virtual status_t initCheck() const {
return OK;
}
virtual ssize_t readAt(off64_t offset, void *data, size_t size) {
off64_t available = (offset >= mSize) ? 0ll : mSize - offset;
size_t copy = (available > size) ? size : available;
memcpy(data, mData + offset, copy);
return copy;
}
private:
const uint8_t *mData;
size_t mSize;
DISALLOW_EVIL_CONSTRUCTORS(MemorySource);
};
ID3::ID3(const sp<DataSource> &source, bool ignoreV1)
: mIsValid(false),
mData(NULL),
mSize(0),
mFirstFrameOffset(0),
mVersion(ID3_UNKNOWN),
mRawSize(0) {
mIsValid = parseV2(source);
if (!mIsValid && !ignoreV1) {
mIsValid = parseV1(source);
}
}
ID3::ID3(const uint8_t *data, size_t size, bool ignoreV1)
: mIsValid(false),
mData(NULL),
mSize(0),
mFirstFrameOffset(0),
mVersion(ID3_UNKNOWN),
mRawSize(0) {
sp<MemorySource> source = new MemorySource(data, size);
mIsValid = parseV2(source);
if (!mIsValid && !ignoreV1) {
mIsValid = parseV1(source);
}
}
ID3::~ID3() {
if (mData) {
free(mData);
mData = NULL;
}
}
bool ID3::isValid() const {
return mIsValid;
}
ID3::Version ID3::version() const {
return mVersion;
}
// static
bool ID3::ParseSyncsafeInteger(const uint8_t encoded[4], size_t *x) {
*x = 0;
for (int32_t i = 0; i < 4; ++i) {
if (encoded[i] & 0x80) {
return false;
}
*x = ((*x) << 7) | encoded[i];
}
return true;
}
bool ID3::parseV2(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 if (header.version_major == 4) {
if (header.flags & 0x0f) {
// The lower 4 bits are undefined in this spec.
return false;
}
} else {
return false;
}
size_t size;
if (!ParseSyncsafeInteger(header.enc_size, &size)) {
return false;
}
if (size > kMaxMetadataSize) {
ALOGE("skipping huge ID3 metadata of size %d", size);
return false;
}
mData = (uint8_t *)malloc(size);
if (mData == NULL) {
return false;
}
mSize = size;
mRawSize = mSize + sizeof(header);
if (source->readAt(sizeof(header), mData, mSize) != (ssize_t)mSize) {
free(mData);
mData = NULL;
return false;
}
if (header.version_major == 4) {
void *copy = malloc(size);
memcpy(copy, mData, size);
bool success = removeUnsynchronizationV2_4(false /* iTunesHack */);
if (!success) {
memcpy(mData, copy, size);
mSize = size;
success = removeUnsynchronizationV2_4(true /* iTunesHack */);
if (success) {
ALOGV("Had to apply the iTunes hack to parse this ID3 tag");
}
}
free(copy);
copy = NULL;
if (!success) {
free(mData);
mData = NULL;
return false;
}
} else if (header.flags & 0x80) {
ALOGV("removing unsynchronization");
removeUnsynchronization();
}
mFirstFrameOffset = 0;
if (header.version_major == 3 && (header.flags & 0x40)) {
// Version 2.3 has an optional extended header.
if (mSize < 4) {
free(mData);
mData = NULL;
return false;
}
size_t extendedHeaderSize = U32_AT(&mData[0]) + 4;
if (extendedHeaderSize > mSize) {
free(mData);
mData = NULL;
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) {
free(mData);
mData = NULL;
return false;
}
mSize -= paddingSize;
}
if (extendedFlags & 0x8000) {
ALOGV("have crc");
}
}
} else if (header.version_major == 4 && (header.flags & 0x40)) {
// Version 2.4 has an optional extended header, that's different
// from Version 2.3's...
if (mSize < 4) {
free(mData);
mData = NULL;
return false;
}
size_t ext_size;
if (!ParseSyncsafeInteger(mData, &ext_size)) {
free(mData);
mData = NULL;
return false;
}
if (ext_size < 6 || ext_size > mSize) {
free(mData);
mData = NULL;
return false;
}
mFirstFrameOffset = ext_size;
}
if (header.version_major == 2) {
mVersion = ID3_V2_2;
} else if (header.version_major == 3) {
mVersion = ID3_V2_3;
} else {
CHECK_EQ(header.version_major, 4);
mVersion = ID3_V2_4;
}
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;
}
}
}
static void WriteSyncsafeInteger(uint8_t *dst, size_t x) {
for (size_t i = 0; i < 4; ++i) {
dst[3 - i] = (x & 0x7f);
x >>= 7;
}
}
bool ID3::removeUnsynchronizationV2_4(bool iTunesHack) {
size_t oldSize = mSize;
size_t offset = 0;
while (offset + 10 <= mSize) {
if (!memcmp(&mData[offset], "\0\0\0\0", 4)) {
break;
}
size_t dataSize;
if (iTunesHack) {
dataSize = U32_AT(&mData[offset + 4]);
} else if (!ParseSyncsafeInteger(&mData[offset + 4], &dataSize)) {
return false;
}
if (offset + dataSize + 10 > mSize) {
return false;
}
uint16_t flags = U16_AT(&mData[offset + 8]);
uint16_t prevFlags = flags;
if (flags & 1) {
// Strip data length indicator
memmove(&mData[offset + 10], &mData[offset + 14], mSize - offset - 14);
mSize -= 4;
dataSize -= 4;
flags &= ~1;
}
if (flags & 2) {
// This file has "unsynchronization", so we have to replace occurrences
// of 0xff 0x00 with just 0xff in order to get the real data.
size_t readOffset = offset + 11;
size_t writeOffset = offset + 11;
for (size_t i = 0; i + 1 < dataSize; ++i) {
if (mData[readOffset - 1] == 0xff
&& mData[readOffset] == 0x00) {
++readOffset;
--mSize;
--dataSize;
}
mData[writeOffset++] = mData[readOffset++];
}
// move the remaining data following this frame
memmove(&mData[writeOffset], &mData[readOffset], oldSize - readOffset);
flags &= ~2;
}
if (flags != prevFlags || iTunesHack) {
WriteSyncsafeInteger(&mData[offset + 4], dataSize);
mData[offset + 8] = flags >> 8;
mData[offset + 9] = flags & 0xff;
}
offset += 10 + dataSize;
}
memset(&mData[mSize], 0, oldSize - mSize);
return true;
}
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 if (mParent.mVersion == ID3_V2_3 || mParent.mVersion == ID3_V2_4) {
id->setTo((const char *)&mParent.mData[mOffset], 4);
} else {
CHECK(mParent.mVersion == ID3_V1 || mParent.mVersion == ID3_V1_1);
switch (mOffset) {
case 3:
id->setTo("TT2");
break;
case 33:
id->setTo("TP1");
break;
case 63:
id->setTo("TAL");
break;
case 93:
id->setTo("TYE");
break;
case 97:
id->setTo("COM");
break;
case 126:
id->setTo("TRK");
break;
case 127:
id->setTo("TCO");
break;
default:
CHECK(!"should not be here.");
break;
}
}
}
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] == '\0') {
size = i;
break;
} else 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] == '\0') {
break;
} else 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;
}
// the 2nd argument is used to get the data following the \0 in a comment field
void ID3::Iterator::getString(String8 *id, String8 *comment) const {
getstring(id, false);
if (comment != NULL) {
getstring(comment, true);
}
}
// comment fields (COM/COMM) contain an initial short descriptor, followed by \0,
// followed by more data. The data following the \0 can be retrieved by setting
// "otherdata" to true.
void ID3::Iterator::getstring(String8 *id, bool otherdata) const {
id->setTo("");
const uint8_t *frameData = mFrameData;
if (frameData == NULL) {
return;
}
uint8_t encoding = *frameData;
if (mParent.mVersion == ID3_V1 || mParent.mVersion == ID3_V1_1) {
if (mOffset == 126 || mOffset == 127) {
// Special treatment for the track number and genre.
char tmp[16];
sprintf(tmp, "%d", (int)*frameData);
id->setTo(tmp);
return;
}
convertISO8859ToString8(frameData, mFrameSize, id);
return;
}
size_t n = mFrameSize - getHeaderLength() - 1;
if (otherdata) {
// skip past the encoding, language, and the 0 separator
frameData += 4;
int32_t i = n - 4;
while(--i >= 0 && *++frameData != 0) ;
int skipped = (frameData - mFrameData);
if (skipped >= (int)n) {
return;
}
n -= skipped;
}
if (encoding == 0x00) {
// ISO 8859-1
convertISO8859ToString8(frameData + 1, n, id);
} else if (encoding == 0x03) {
// UTF-8
id->setTo((const char *)(frameData + 1), n);
} else if (encoding == 0x02) {
// UTF-16 BE, no byte order mark.
// API wants number of characters, not number of bytes...
int len = n / 2;
const char16_t *framedata = (const char16_t *) (frameData + 1);
char16_t *framedatacopy = NULL;
#if BYTE_ORDER == LITTLE_ENDIAN
framedatacopy = new char16_t[len];
for (int i = 0; i < len; i++) {
framedatacopy[i] = bswap_16(framedata[i]);
}
framedata = framedatacopy;
#endif
id->setTo(framedata, len);
if (framedatacopy != NULL) {
delete[] framedatacopy;
}
} else {
// UCS-2
// API wants number of characters, not number of bytes...
int len = n / 2;
const char16_t *framedata = (const char16_t *) (frameData + 1);
char16_t *framedatacopy = NULL;
if (*framedata == 0xfffe) {
// endianness marker doesn't match host endianness, convert
framedatacopy = new char16_t[len];
for (int i = 0; i < len; i++) {
framedatacopy[i] = bswap_16(framedata[i]);
}
framedata = framedatacopy;
}
// If the string starts with an endianness marker, skip it
if (*framedata == 0xfeff) {
framedata++;
len--;
}
id->setTo(framedata, len);
if (framedatacopy != NULL) {
delete[] framedatacopy;
}
}
}
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 if (mParent.mVersion == ID3_V2_3 || mParent.mVersion == ID3_V2_4) {
return 10;
} else {
CHECK(mParent.mVersion == ID3_V1 || mParent.mVersion == ID3_V1_1);
return 0;
}
}
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) {
ALOGV("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 if (mParent.mVersion == ID3_V2_3
|| mParent.mVersion == ID3_V2_4) {
if (mOffset + 10 > mParent.mSize) {
return;
}
if (!memcmp(&mParent.mData[mOffset], "\0\0\0\0", 4)) {
return;
}
size_t baseSize;
if (mParent.mVersion == ID3_V2_4) {
if (!ParseSyncsafeInteger(
&mParent.mData[mOffset + 4], &baseSize)) {
return;
}
} else {
baseSize = U32_AT(&mParent.mData[mOffset + 4]);
}
mFrameSize = 10 + baseSize;
if (mOffset + mFrameSize > mParent.mSize) {
ALOGV("partial frame at offset %d (size = %d, bytes-remaining = %d)",
mOffset, mFrameSize, mParent.mSize - mOffset - 10);
return;
}
uint16_t flags = U16_AT(&mParent.mData[mOffset + 8]);
if ((mParent.mVersion == ID3_V2_4 && (flags & 0x000c))
|| (mParent.mVersion == ID3_V2_3 && (flags & 0x00c0))) {
// Compression or encryption are not supported at this time.
// Per-frame unsynchronization and data-length indicator
// have already been taken care of.
ALOGV("Skipping unsupported frame (compression, encryption "
"or per-frame unsynchronization flagged");
mOffset += mFrameSize;
continue;
}
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;
}
} else {
CHECK(mParent.mVersion == ID3_V1 || mParent.mVersion == ID3_V1_1);
if (mOffset >= mParent.mSize) {
return;
}
mFrameData = &mParent.mData[mOffset];
switch (mOffset) {
case 3:
case 33:
case 63:
mFrameSize = 30;
break;
case 93:
mFrameSize = 4;
break;
case 97:
if (mParent.mVersion == ID3_V1) {
mFrameSize = 30;
} else {
mFrameSize = 29;
}
break;
case 126:
mFrameSize = 1;
break;
case 127:
mFrameSize = 1;
break;
default:
CHECK(!"Should not be here, invalid offset.");
break;
}
if (!mID) {
break;
}
String8 id;
getID(&id);
if (id == mID) {
break;
}
}
mOffset += mFrameSize;
}
}
static size_t StringSize(const uint8_t *start, uint8_t encoding) {
if (encoding == 0x00 || encoding == 0x03) {
// ISO 8859-1 or UTF-8
return strlen((const char *)start) + 1;
}
// UCS-2
size_t n = 0;
while (start[n] != '\0' || start[n + 1] != '\0') {
n += 2;
}
// Add size of null termination.
return n + 2;
}
const void *
ID3::getAlbumArt(size_t *length, String8 *mime) const {
*length = 0;
mime->setTo("");
Iterator it(
*this,
(mVersion == ID3_V2_3 || mVersion == ID3_V2_4) ? "APIC" : "PIC");
while (!it.done()) {
size_t size;
const uint8_t *data = it.getData(&size);
if (mVersion == ID3_V2_3 || mVersion == ID3_V2_4) {
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);
if (size < 2 ||
size - 2 < mimeLen ||
size - 2 - mimeLen < descLen) {
ALOGW("bogus album art sizes");
return NULL;
}
*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;
}
bool ID3::parseV1(const sp<DataSource> &source) {
const size_t V1_TAG_SIZE = 128;
off64_t size;
if (source->getSize(&size) != OK || size < (off64_t)V1_TAG_SIZE) {
return false;
}
mData = (uint8_t *)malloc(V1_TAG_SIZE);
if (source->readAt(size - V1_TAG_SIZE, mData, V1_TAG_SIZE)
!= (ssize_t)V1_TAG_SIZE) {
free(mData);
mData = NULL;
return false;
}
if (memcmp("TAG", mData, 3)) {
free(mData);
mData = NULL;
return false;
}
mSize = V1_TAG_SIZE;
mFirstFrameOffset = 3;
if (mData[V1_TAG_SIZE - 3] != 0) {
mVersion = ID3_V1;
} else {
mVersion = ID3_V1_1;
}
return true;
}
} // namespace android