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
This commit is contained in:
Andreas Huber
2010-01-11 15:35:19 -08:00
parent 93aea69649
commit aee3c6394a
18 changed files with 1193 additions and 63 deletions

View File

@@ -31,6 +31,8 @@ class String8;
class DataSource : public RefBase {
public:
static sp<DataSource> CreateFromURI(const char *uri);
DataSource() {}
virtual status_t initCheck() const = 0;

View File

@@ -43,6 +43,10 @@ public:
virtual sp<MetaData> getTrackMetaData(
size_t index, uint32_t flags = 0) = 0;
// Return container specific meta-data. The default implementation
// returns an empty metadata object.
virtual sp<MetaData> getMetaData();
protected:
MediaExtractor() {}
virtual ~MediaExtractor() {}

View File

@@ -48,6 +48,15 @@ enum {
kKeyBufferID = 'bfID',
kKeyMaxInputSize = 'inpS',
kKeyThumbnailTime = 'thbT', // int64_t (usecs)
kKeyAlbum = 'albu', // cstring
kKeyArtist = 'arti', // cstring
kKeyComposer = 'comp', // cstring
kKeyGenre = 'genr', // cstring
kKeyTitle = 'titl', // cstring
kKeyYear = 'year', // cstring
kKeyAlbumArt = 'albA', // compressed image data
kKeyAlbumArtMIME = 'alAM', // cstring
};
enum {

View File

@@ -44,14 +44,17 @@ endif
LOCAL_C_INCLUDES:= \
$(JNI_H_INCLUDE) \
$(TOP)/external/opencore/extern_libs_v2/khronos/openmax/include \
$(TOP)/external/opencore/android
$(TOP)/external/opencore/android \
$(TOP)/external/tremor/Tremor
LOCAL_SHARED_LIBRARIES := \
libbinder \
libmedia \
libutils \
libcutils \
libui
libui \
libsonivox \
libvorbisidec
ifeq ($(BUILD_WITH_FULL_STAGEFRIGHT),true)
@@ -62,7 +65,8 @@ LOCAL_STATIC_LIBRARIES := \
libstagefright_amrwbdec \
libstagefright_avcdec \
libstagefright_m4vh263dec \
libstagefright_mp3dec
libstagefright_mp3dec \
libstagefright_id3
LOCAL_SHARED_LIBRARIES += \
libstagefright_amrnb_common \

View File

@@ -133,13 +133,14 @@ void AudioPlayer::stop() {
if (mAudioSink.get() != NULL) {
mAudioSink->stop();
mAudioSink->close();
} else {
mAudioTrack->stop();
delete mAudioTrack;
mAudioTrack = NULL;
}
// Make sure to release any buffer we hold onto so that the
// source is able to stop().
if (mInputBuffer != NULL) {
@@ -150,7 +151,7 @@ void AudioPlayer::stop() {
}
mSource->stop();
mNumFramesPlayed = 0;
mPositionTimeMediaUs = -1;
mPositionTimeRealUs = -1;
@@ -259,7 +260,7 @@ void AudioPlayer::fillBuffer(void *data, size_t size) {
mInputBuffer->set_range(mInputBuffer->range_offset() + copy,
mInputBuffer->range_length() - copy);
size_done += copy;
size_remaining -= copy;
}

View File

@@ -19,7 +19,10 @@
#include "include/MPEG4Extractor.h"
#include "include/WAVExtractor.h"
#include <media/stagefright/CachingDataSource.h>
#include <media/stagefright/DataSource.h>
#include <media/stagefright/FileSource.h>
#include <media/stagefright/HTTPDataSource.h>
#include <media/stagefright/MediaErrors.h>
#include <utils/String8.h>
@@ -91,4 +94,24 @@ void DataSource::RegisterDefaultSniffers() {
RegisterSniffer(SniffWAV);
}
// static
sp<DataSource> DataSource::CreateFromURI(const char *uri) {
sp<DataSource> source;
if (!strncasecmp("file://", uri, 7)) {
source = new FileSource(uri + 7);
} else if (!strncasecmp("http://", uri, 7)) {
source = new HTTPDataSource(uri);
source = new CachingDataSource(source, 64 * 1024, 10);
} else {
// Assume it's a filename.
source = new FileSource(uri);
}
if (source == NULL || source->initCheck() != OK) {
return NULL;
}
return source;
}
} // namespace android

View File

@@ -58,7 +58,10 @@ ssize_t FileSource::readAt(off_t offset, void *data, size_t size) {
}
int err = fseeko(mFile, offset + mOffset, SEEK_SET);
CHECK(err != -1);
if (err < 0) {
LOGE("seek to %lld failed", offset + mOffset);
return UNKNOWN_ERROR;
}
return fread(data, 1, size, mFile);
}

View File

@@ -20,6 +20,8 @@
#include "include/MP3Extractor.h"
#include "include/ID3.h"
#include <media/stagefright/DataSource.h>
#include <media/stagefright/MediaBuffer.h>
#include <media/stagefright/MediaBufferGroup.h>
@@ -667,7 +669,7 @@ status_t MP3Source::read(
}
// Lost sync.
LOGW("lost sync!\n");
LOGV("lost sync!\n");
off_t pos = mCurrentPos;
if (!Resync(mDataSource, mFixedHeader, &pos, NULL)) {
@@ -706,6 +708,63 @@ status_t MP3Source::read(
return OK;
}
sp<MetaData> MP3Extractor::getMetaData() {
sp<MetaData> meta = new MetaData;
meta->setCString(kKeyMIMEType, "audio/mpeg");
ID3 id3(mDataSource);
if (!id3.isValid()) {
return meta;
}
struct Map {
int key;
const char *tag1;
const char *tag2;
};
static const Map kMap[] = {
{ kKeyAlbum, "TALB", "TAL" },
{ kKeyArtist, "TPE1", "TP1" },
{ kKeyComposer, "TCOM", "TCM" },
{ kKeyGenre, "TCON", "TCO" },
{ kKeyTitle, "TALB", "TAL" },
{ kKeyYear, "TYE", "TYER" },
};
static const size_t kNumMapEntries = sizeof(kMap) / sizeof(kMap[0]);
for (size_t i = 0; i < kNumMapEntries; ++i) {
ID3::Iterator *it = new ID3::Iterator(id3, kMap[i].tag1);
if (it->done()) {
delete it;
it = new ID3::Iterator(id3, kMap[i].tag2);
}
if (it->done()) {
delete it;
continue;
}
String8 s;
it->getString(&s);
delete it;
meta->setCString(kMap[i].key, s);
}
size_t dataSize;
String8 mime;
const void *data = id3.getAlbumArt(&dataSize, &mime);
if (data) {
meta->setData(kKeyAlbumArt, MetaData::TYPE_NONE, data, dataSize);
meta->setCString(kKeyAlbumArtMIME, mime.string());
}
return meta;
}
bool SniffMP3(
const sp<DataSource> &source, String8 *mimeType, float *confidence) {
off_t pos = 0;

View File

@@ -23,16 +23,18 @@
#include "include/MPEG4Extractor.h"
#include "include/WAVExtractor.h"
#include <media/stagefright/CachingDataSource.h>
#include <media/stagefright/DataSource.h>
#include <media/stagefright/FileSource.h>
#include <media/stagefright/HTTPDataSource.h>
#include <media/stagefright/MediaDefs.h>
#include <media/stagefright/MediaExtractor.h>
#include <media/stagefright/MetaData.h>
#include <utils/String8.h>
namespace android {
sp<MetaData> MediaExtractor::getMetaData() {
return new MetaData;
}
// static
sp<MediaExtractor> MediaExtractor::Create(
const sp<DataSource> &source, const char *mime) {
@@ -40,7 +42,7 @@ sp<MediaExtractor> MediaExtractor::Create(
if (mime == NULL) {
float confidence;
if (!source->sniff(&tmp, &confidence)) {
LOGE("FAILED to autodetect media content.");
LOGV("FAILED to autodetect media content.");
return NULL;
}
@@ -68,16 +70,7 @@ sp<MediaExtractor> MediaExtractor::Create(
// static
sp<MediaExtractor> MediaExtractor::CreateFromURI(
const char *uri, const char *mime) {
sp<DataSource> source;
if (!strncasecmp("file://", uri, 7)) {
source = new FileSource(uri + 7);
} else if (!strncasecmp("http://", uri, 7)) {
source = new HTTPDataSource(uri);
source = new CachingDataSource(source, 64 * 1024, 10);
} else {
// Assume it's a filename.
source = new FileSource(uri);
}
sp<DataSource> source = DataSource::CreateFromURI(uri);
if (source == NULL || source->initCheck() != OK) {
return NULL;

View File

@@ -14,10 +14,21 @@
* limitations under the License.
*/
//#define LOG_NDEBUG 0
#define LOG_TAG "StagefrightMediaScanner"
#include <utils/Log.h>
#include <media/stagefright/StagefrightMediaScanner.h>
#include "include/StagefrightMetadataRetriever.h"
// Sonivox includes
#include <libsonivox/eas.h>
// Ogg Vorbis includes
#include "ivorbiscodec.h"
#include "ivorbisfile.h"
namespace android {
StagefrightMediaScanner::StagefrightMediaScanner()
@@ -26,12 +37,145 @@ StagefrightMediaScanner::StagefrightMediaScanner()
StagefrightMediaScanner::~StagefrightMediaScanner() {}
static bool FileHasAcceptableExtension(const char *extension) {
static const char *kValidExtensions[] = {
".mp3", ".mp4", ".m4a", ".3gp", ".3gpp", ".3g2", ".3gpp2",
".mpeg", ".ogg", ".mid", ".smf", ".imy", ".wma", ".aac",
".wav", ".amr", ".midi", ".xmf", ".rtttl", ".rtx", ".ota"
};
static const size_t kNumValidExtensions =
sizeof(kValidExtensions) / sizeof(kValidExtensions[0]);
for (size_t i = 0; i < kNumValidExtensions; ++i) {
if (!strcasecmp(extension, kValidExtensions[i])) {
return true;
}
}
return false;
}
static status_t HandleMIDI(
const char *filename, MediaScannerClient *client) {
// get the library configuration and do sanity check
const S_EAS_LIB_CONFIG* pLibConfig = EAS_Config();
if ((pLibConfig == NULL) || (LIB_VERSION != pLibConfig->libVersion)) {
LOGE("EAS library/header mismatch\n");
return UNKNOWN_ERROR;
}
EAS_I32 temp;
// spin up a new EAS engine
EAS_DATA_HANDLE easData = NULL;
EAS_HANDLE easHandle = NULL;
EAS_RESULT result = EAS_Init(&easData);
if (result == EAS_SUCCESS) {
EAS_FILE file;
file.path = filename;
file.fd = 0;
file.offset = 0;
file.length = 0;
result = EAS_OpenFile(easData, &file, &easHandle);
}
if (result == EAS_SUCCESS) {
result = EAS_Prepare(easData, easHandle);
}
if (result == EAS_SUCCESS) {
result = EAS_ParseMetaData(easData, easHandle, &temp);
}
if (easHandle) {
EAS_CloseFile(easData, easHandle);
}
if (easData) {
EAS_Shutdown(easData);
}
if (result != EAS_SUCCESS) {
return UNKNOWN_ERROR;
}
char buffer[20];
sprintf(buffer, "%ld", temp);
if (!client->addStringTag("duration", buffer)) return UNKNOWN_ERROR;
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) {
client.setLocale(locale());
client.beginFile();
const char *extension = strrchr(path, '.');
if (!extension) {
return UNKNOWN_ERROR;
}
if (!FileHasAcceptableExtension(extension)) {
client.endFile();
return UNKNOWN_ERROR;
}
if (!strcasecmp(extension, ".mid")
|| !strcasecmp(extension, ".smf")
|| !strcasecmp(extension, ".imy")
|| !strcasecmp(extension, ".midi")
|| !strcasecmp(extension, ".xmf")
|| !strcasecmp(extension, ".rtttl")
|| !strcasecmp(extension, ".rtx")
|| !strcasecmp(extension, ".ota")) {
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) {
@@ -66,12 +210,27 @@ status_t StagefrightMediaScanner::processFile(
}
char *StagefrightMediaScanner::extractAlbumArt(int fd) {
if (mRetriever->setDataSource(fd, 0, 0) == OK
off_t size = lseek(fd, 0, SEEK_END);
if (size < 0) {
return NULL;
}
lseek(fd, 0, SEEK_SET);
if (mRetriever->setDataSource(fd, 0, size) == OK
&& mRetriever->setMode(
METADATA_MODE_FRAME_CAPTURE_ONLY) == OK) {
MediaAlbumArt *art = mRetriever->extractAlbumArt();
// TODO: figure out what format the result should be in.
if (art != NULL) {
char *data = (char *)malloc(art->mSize + 4);
*(int32_t *)data = art->mSize;
memcpy(&data[4], art->mData, art->mSize);
delete art;
art = NULL;
return data;
}
}
return NULL;

View File

@@ -1,19 +1,18 @@
/*
**
** Copyright 2009, 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.
*/
* Copyright (C) 2009 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 "StagefrightMetadataRetriever"
@@ -33,7 +32,9 @@
namespace android {
StagefrightMetadataRetriever::StagefrightMetadataRetriever() {
StagefrightMetadataRetriever::StagefrightMetadataRetriever()
: mParsedMetaData(false),
mAlbumArt(NULL) {
LOGV("StagefrightMetadataRetriever()");
DataSource::RegisterDefaultSniffers();
@@ -42,23 +43,66 @@ StagefrightMetadataRetriever::StagefrightMetadataRetriever() {
StagefrightMetadataRetriever::~StagefrightMetadataRetriever() {
LOGV("~StagefrightMetadataRetriever()");
delete mAlbumArt;
mAlbumArt = NULL;
mClient.disconnect();
}
status_t StagefrightMetadataRetriever::setDataSource(const char *uri) {
LOGV("setDataSource(%s)", uri);
mExtractor = MediaExtractor::CreateFromURI(uri);
mParsedMetaData = false;
mMetaData.clear();
delete mAlbumArt;
mAlbumArt = NULL;
return mExtractor.get() != NULL ? OK : UNKNOWN_ERROR;
mSource = DataSource::CreateFromURI(uri);
if (mSource == NULL) {
return UNKNOWN_ERROR;
}
mExtractor = MediaExtractor::Create(mSource);
if (mExtractor == NULL) {
mSource.clear();
return UNKNOWN_ERROR;
}
return OK;
}
// Warning caller retains ownership of the filedescriptor! Dup it if necessary.
status_t StagefrightMetadataRetriever::setDataSource(
int fd, int64_t offset, int64_t length) {
fd = dup(fd);
LOGV("setDataSource(%d, %lld, %lld)", fd, offset, length);
mExtractor = MediaExtractor::Create(
new FileSource(fd, offset, length));
mParsedMetaData = false;
mMetaData.clear();
delete mAlbumArt;
mAlbumArt = NULL;
mSource = new FileSource(fd, offset, length);
status_t err;
if ((err = mSource->initCheck()) != OK) {
mSource.clear();
return err;
}
mExtractor = MediaExtractor::Create(mSource);
if (mExtractor == NULL) {
mSource.clear();
return UNKNOWN_ERROR;
}
return OK;
}
@@ -184,14 +228,98 @@ VideoFrame *StagefrightMetadataRetriever::captureFrame() {
MediaAlbumArt *StagefrightMetadataRetriever::extractAlbumArt() {
LOGV("extractAlbumArt (extractor: %s)", mExtractor.get() != NULL ? "YES" : "NO");
if (mExtractor == NULL) {
return NULL;
}
if (!mParsedMetaData) {
parseMetaData();
mParsedMetaData = true;
}
if (mAlbumArt) {
return new MediaAlbumArt(*mAlbumArt);
}
return NULL;
}
const char *StagefrightMetadataRetriever::extractMetadata(int keyCode) {
LOGV("extractMetadata %d (extractor: %s)",
keyCode, mExtractor.get() != NULL ? "YES" : "NO");
LOGV("extractMetadata %d", keyCode);
return NULL;
if (mExtractor == NULL) {
return NULL;
}
if (!mParsedMetaData) {
parseMetaData();
mParsedMetaData = true;
}
ssize_t index = mMetaData.indexOfKey(keyCode);
if (index < 0) {
return NULL;
}
return strdup(mMetaData.valueAt(index).string());
}
void StagefrightMetadataRetriever::parseMetaData() {
sp<MetaData> meta = mExtractor->getMetaData();
struct Map {
int from;
int to;
};
static const Map kMap[] = {
{ kKeyAlbum, METADATA_KEY_ALBUM },
{ kKeyArtist, METADATA_KEY_ARTIST },
{ kKeyComposer, METADATA_KEY_COMPOSER },
{ kKeyGenre, METADATA_KEY_GENRE },
{ kKeyTitle, METADATA_KEY_TITLE },
{ kKeyYear, METADATA_KEY_YEAR },
};
static const size_t kNumMapEntries = sizeof(kMap) / sizeof(kMap[0]);
for (size_t i = 0; i < kNumMapEntries; ++i) {
const char *value;
if (meta->findCString(kMap[i].from, &value)) {
mMetaData.add(kMap[i].to, String8(value));
}
}
const void *data;
uint32_t type;
size_t dataSize;
if (meta->findData(kKeyAlbumArt, &type, &data, &dataSize)) {
mAlbumArt = new MediaAlbumArt;
mAlbumArt->mSize = dataSize;
mAlbumArt->mData = new uint8_t[dataSize];
memcpy(mAlbumArt->mData, data, dataSize);
}
// The overall duration is the duration of the longest track.
int64_t maxDurationUs = 0;
for (size_t i = 0; i < mExtractor->countTracks(); ++i) {
sp<MetaData> trackMeta = mExtractor->getTrackMetaData(i);
int64_t durationUs;
if (trackMeta->findInt64(kKeyDuration, &durationUs)) {
if (durationUs > maxDurationUs) {
maxDurationUs = durationUs;
}
}
}
// The duration value is a string representing the duration in ms.
char tmp[32];
sprintf(tmp, "%lld", (maxDurationUs + 500) / 1000);
mMetaData.add(METADATA_KEY_DURATION, String8(tmp));
}
} // namespace android

View File

@@ -383,7 +383,7 @@ status_t AVCDecoder::read(
return OK;
} else {
LOGE("failed to decode frame (res = %d)", res);
LOGV("failed to decode frame (res = %d)", res);
return UNKNOWN_ERROR;
}
}

View File

@@ -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)

View File

@@ -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

View File

@@ -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;
}

View File

@@ -0,0 +1,87 @@
/*
* 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.
*/
#ifndef ID3_H_
#define ID3_H_
#include <utils/RefBase.h>
namespace android {
struct DataSource;
struct String8;
struct ID3 {
enum Version {
ID3_UNKNOWN,
ID3_V2_2,
ID3_V2_3
};
ID3(const sp<DataSource> &source);
~ID3();
bool isValid() const;
Version version() const;
const void *getAlbumArt(size_t *length, String8 *mime) const;
struct Iterator {
Iterator(const ID3 &parent, const char *id);
~Iterator();
bool done() const;
void getID(String8 *id) const;
void getString(String8 *s) const;
const uint8_t *getData(size_t *length) const;
void next();
private:
const ID3 &mParent;
char *mID;
size_t mOffset;
const uint8_t *mFrameData;
size_t mFrameSize;
void findFrame();
size_t getHeaderLength() const;
Iterator(const Iterator &);
Iterator &operator=(const Iterator &);
};
private:
bool mIsValid;
uint8_t *mData;
size_t mSize;
size_t mFirstFrameOffset;
Version mVersion;
bool parse(const sp<DataSource> &source);
void removeUnsynchronization();
ID3(const ID3 &);
ID3 &operator=(const ID3 &);
};
} // namespace android
#endif // ID3_H_

View File

@@ -34,6 +34,8 @@ public:
virtual sp<MediaSource> getTrack(size_t index);
virtual sp<MetaData> getTrackMetaData(size_t index, uint32_t flags);
virtual sp<MetaData> getMetaData();
protected:
virtual ~MP3Extractor();

View File

@@ -1,19 +1,18 @@
/*
**
** Copyright 2009, 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.
*/
* 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.
*/
#ifndef STAGEFRIGHT_METADATA_RETRIEVER_H_
@@ -22,9 +21,11 @@
#include <media/MediaMetadataRetrieverInterface.h>
#include <media/stagefright/OMXClient.h>
#include <utils/KeyedVector.h>
namespace android {
struct DataSource;
class MediaExtractor;
struct StagefrightMetadataRetriever : public MediaMetadataRetrieverInterface {
@@ -40,8 +41,15 @@ struct StagefrightMetadataRetriever : public MediaMetadataRetrieverInterface {
private:
OMXClient mClient;
sp<DataSource> mSource;
sp<MediaExtractor> mExtractor;
bool mParsedMetaData;
KeyedVector<int, String8> mMetaData;
MediaAlbumArt *mAlbumArt;
void parseMetaData();
StagefrightMetadataRetriever(const StagefrightMetadataRetriever &);
StagefrightMetadataRetriever &operator=(