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:
@@ -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;
|
||||
|
||||
@@ -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() {}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
27
media/libstagefright/id3/Android.mk
Normal file
27
media/libstagefright/id3/Android.mk
Normal 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)
|
||||
|
||||
465
media/libstagefright/id3/ID3.cpp
Normal file
465
media/libstagefright/id3/ID3.cpp
Normal 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
|
||||
156
media/libstagefright/id3/testid3.cpp
Normal file
156
media/libstagefright/id3/testid3.cpp
Normal 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;
|
||||
}
|
||||
87
media/libstagefright/include/ID3.h
Normal file
87
media/libstagefright/include/ID3.h
Normal 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_
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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=(
|
||||
|
||||
Reference in New Issue
Block a user