diff options
Diffstat (limited to 'media')
-rw-r--r-- | media/libstagefright/Android.mk | 10 | ||||
-rw-r--r-- | media/libstagefright/AudioPlayer.cpp | 7 | ||||
-rw-r--r-- | media/libstagefright/DataSource.cpp | 23 | ||||
-rw-r--r-- | media/libstagefright/FileSource.cpp | 5 | ||||
-rw-r--r-- | media/libstagefright/MP3Extractor.cpp | 61 | ||||
-rw-r--r-- | media/libstagefright/MediaExtractor.cpp | 21 | ||||
-rw-r--r-- | media/libstagefright/StagefrightMediaScanner.cpp | 163 | ||||
-rw-r--r-- | media/libstagefright/StagefrightMetadataRetriever.cpp | 174 | ||||
-rw-r--r-- | media/libstagefright/codecs/avc/dec/AVCDecoder.cpp | 2 | ||||
-rw-r--r-- | media/libstagefright/id3/Android.mk | 27 | ||||
-rw-r--r-- | media/libstagefright/id3/ID3.cpp | 465 | ||||
-rw-r--r-- | media/libstagefright/id3/testid3.cpp | 156 | ||||
-rw-r--r-- | media/libstagefright/include/ID3.h | 87 | ||||
-rw-r--r-- | media/libstagefright/include/MP3Extractor.h | 2 | ||||
-rw-r--r-- | media/libstagefright/include/StagefrightMetadataRetriever.h | 38 |
15 files changed, 1178 insertions, 63 deletions
diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk index 30b4506..e36e78c 100644 --- a/media/libstagefright/Android.mk +++ b/media/libstagefright/Android.mk @@ -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 \ diff --git a/media/libstagefright/AudioPlayer.cpp b/media/libstagefright/AudioPlayer.cpp index 4280683..14842c0 100644 --- a/media/libstagefright/AudioPlayer.cpp +++ b/media/libstagefright/AudioPlayer.cpp @@ -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; } diff --git a/media/libstagefright/DataSource.cpp b/media/libstagefright/DataSource.cpp index 2a6dbc4..741e5e0 100644 --- a/media/libstagefright/DataSource.cpp +++ b/media/libstagefright/DataSource.cpp @@ -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 diff --git a/media/libstagefright/FileSource.cpp b/media/libstagefright/FileSource.cpp index 37c2450..b6f1af2 100644 --- a/media/libstagefright/FileSource.cpp +++ b/media/libstagefright/FileSource.cpp @@ -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); } diff --git a/media/libstagefright/MP3Extractor.cpp b/media/libstagefright/MP3Extractor.cpp index b8e76fd..5df1e00 100644 --- a/media/libstagefright/MP3Extractor.cpp +++ b/media/libstagefright/MP3Extractor.cpp @@ -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; diff --git a/media/libstagefright/MediaExtractor.cpp b/media/libstagefright/MediaExtractor.cpp index 9d3deb7..e46f00e 100644 --- a/media/libstagefright/MediaExtractor.cpp +++ b/media/libstagefright/MediaExtractor.cpp @@ -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; diff --git a/media/libstagefright/StagefrightMediaScanner.cpp b/media/libstagefright/StagefrightMediaScanner.cpp index 9b41929..4815db2 100644 --- a/media/libstagefright/StagefrightMediaScanner.cpp +++ b/media/libstagefright/StagefrightMediaScanner.cpp @@ -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; diff --git a/media/libstagefright/StagefrightMetadataRetriever.cpp b/media/libstagefright/StagefrightMetadataRetriever.cpp index 128e776..be4a9d9 100644 --- a/media/libstagefright/StagefrightMetadataRetriever.cpp +++ b/media/libstagefright/StagefrightMetadataRetriever.cpp @@ -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; + + mSource = DataSource::CreateFromURI(uri); + + if (mSource == NULL) { + return UNKNOWN_ERROR; + } + + mExtractor = MediaExtractor::Create(mSource); + + if (mExtractor == NULL) { + mSource.clear(); - return mExtractor.get() != NULL ? OK : UNKNOWN_ERROR; + 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 diff --git a/media/libstagefright/codecs/avc/dec/AVCDecoder.cpp b/media/libstagefright/codecs/avc/dec/AVCDecoder.cpp index 229e933..d874224 100644 --- a/media/libstagefright/codecs/avc/dec/AVCDecoder.cpp +++ b/media/libstagefright/codecs/avc/dec/AVCDecoder.cpp @@ -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; } } diff --git a/media/libstagefright/id3/Android.mk b/media/libstagefright/id3/Android.mk new file mode 100644 index 0000000..3c47e2e --- /dev/null +++ b/media/libstagefright/id3/Android.mk @@ -0,0 +1,27 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + ID3.cpp + +LOCAL_MODULE := libstagefright_id3 + +include $(BUILD_STATIC_LIBRARY) + +################################################################################ + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + testid3.cpp + +LOCAL_SHARED_LIBRARIES := \ + libstagefright libutils libbinder + +LOCAL_STATIC_LIBRARIES := \ + libstagefright_id3 + +LOCAL_MODULE := testid3 + +include $(BUILD_EXECUTABLE) + diff --git a/media/libstagefright/id3/ID3.cpp b/media/libstagefright/id3/ID3.cpp new file mode 100644 index 0000000..2b3ef1a --- /dev/null +++ b/media/libstagefright/id3/ID3.cpp @@ -0,0 +1,465 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "ID3" +#include <utils/Log.h> + +#include "../include/ID3.h" + +#include <media/stagefright/DataSource.h> +#include <media/stagefright/MediaDebug.h> +#include <media/stagefright/Utils.h> +#include <utils/String8.h> + +namespace android { + +ID3::ID3(const sp<DataSource> &source) + : mIsValid(false), + mData(NULL), + mSize(0), + mFirstFrameOffset(0), + mVersion(ID3_UNKNOWN) { + mIsValid = parse(source); +} + +ID3::~ID3() { + if (mData) { + free(mData); + mData = NULL; + } +} + +bool ID3::isValid() const { + return mIsValid; +} + +ID3::Version ID3::version() const { + return mVersion; +} + +bool ID3::parse(const sp<DataSource> &source) { + struct id3_header { + char id[3]; + uint8_t version_major; + uint8_t version_minor; + uint8_t flags; + uint8_t enc_size[4]; + }; + + id3_header header; + if (source->readAt( + 0, &header, sizeof(header)) != (ssize_t)sizeof(header)) { + return false; + } + + if (memcmp(header.id, "ID3", 3)) { + return false; + } + + if (header.version_major == 0xff || header.version_minor == 0xff) { + return false; + } + + if (header.version_major == 2) { + if (header.flags & 0x3f) { + // We only support the 2 high bits, if any of the lower bits are + // set, we cannot guarantee to understand the tag format. + return false; + } + + if (header.flags & 0x40) { + // No compression scheme has been decided yet, ignore the + // tag if compression is indicated. + + return false; + } + } else if (header.version_major == 3) { + if (header.flags & 0x1f) { + // We only support the 3 high bits, if any of the lower bits are + // set, we cannot guarantee to understand the tag format. + return false; + } + } else { + return false; + } + + size_t size = 0; + for (int32_t i = 0; i < 4; ++i) { + if (header.enc_size[i] & 0x80) { + return false; + } + + size = (size << 7) | header.enc_size[i]; + } + + mData = (uint8_t *)malloc(size); + + if (mData == NULL) { + return false; + } + + mSize = size; + + if (source->readAt(sizeof(header), mData, mSize) != (ssize_t)mSize) { + return false; + } + + if (header.flags & 0x80) { + LOGI("removing unsynchronization"); + removeUnsynchronization(); + } + + mFirstFrameOffset = 0; + if (header.version_major == 3 && (header.flags & 0x40)) { + // Version 2.3 has an optional extended header. + + if (mSize < 4) { + return false; + } + + size_t extendedHeaderSize = U32_AT(&mData[0]) + 4; + + if (extendedHeaderSize > mSize) { + return false; + } + + mFirstFrameOffset = extendedHeaderSize; + + uint16_t extendedFlags = 0; + if (extendedHeaderSize >= 6) { + extendedFlags = U16_AT(&mData[4]); + + if (extendedHeaderSize >= 10) { + size_t paddingSize = U32_AT(&mData[6]); + + if (mFirstFrameOffset + paddingSize > mSize) { + return false; + } + + mSize -= paddingSize; + } + + if (extendedFlags & 0x8000) { + LOGI("have crc"); + } + } + } + + if (header.version_major == 2) { + mVersion = ID3_V2_2; + } else { + CHECK_EQ(header.version_major, 3); + mVersion = ID3_V2_3; + } + + return true; +} + +void ID3::removeUnsynchronization() { + for (size_t i = 0; i + 1 < mSize; ++i) { + if (mData[i] == 0xff && mData[i + 1] == 0x00) { + memmove(&mData[i + 1], &mData[i + 2], mSize - i - 2); + --mSize; + } + } +} + +ID3::Iterator::Iterator(const ID3 &parent, const char *id) + : mParent(parent), + mID(NULL), + mOffset(mParent.mFirstFrameOffset), + mFrameData(NULL), + mFrameSize(0) { + if (id) { + mID = strdup(id); + } + + findFrame(); +} + +ID3::Iterator::~Iterator() { + if (mID) { + free(mID); + mID = NULL; + } +} + +bool ID3::Iterator::done() const { + return mFrameData == NULL; +} + +void ID3::Iterator::next() { + if (mFrameData == NULL) { + return; + } + + mOffset += mFrameSize; + + findFrame(); +} + +void ID3::Iterator::getID(String8 *id) const { + id->setTo(""); + + if (mFrameData == NULL) { + return; + } + + if (mParent.mVersion == ID3_V2_2) { + id->setTo((const char *)&mParent.mData[mOffset], 3); + } else { + CHECK_EQ(mParent.mVersion, ID3_V2_3); + id->setTo((const char *)&mParent.mData[mOffset], 4); + } +} + +static void convertISO8859ToString8( + const uint8_t *data, size_t size, + String8 *s) { + size_t utf8len = 0; + for (size_t i = 0; i < size; ++i) { + if (data[i] < 0x80) { + ++utf8len; + } else { + utf8len += 2; + } + } + + if (utf8len == size) { + // Only ASCII characters present. + + s->setTo((const char *)data, size); + return; + } + + char *tmp = new char[utf8len]; + char *ptr = tmp; + for (size_t i = 0; i < size; ++i) { + if (data[i] < 0x80) { + *ptr++ = data[i]; + } else if (data[i] < 0xc0) { + *ptr++ = 0xc2; + *ptr++ = data[i]; + } else { + *ptr++ = 0xc3; + *ptr++ = data[i] - 64; + } + } + + s->setTo(tmp, utf8len); + + delete[] tmp; + tmp = NULL; +} + +void ID3::Iterator::getString(String8 *id) const { + id->setTo(""); + + if (mFrameData == NULL) { + return; + } + + size_t n = mFrameSize - getHeaderLength() - 1; + + if (*mFrameData == 0x00) { + // ISO 8859-1 + convertISO8859ToString8(mFrameData + 1, n, id); + } else { + // UCS-2 + id->setTo((const char16_t *)(mFrameData + 1), n); + } +} + +const uint8_t *ID3::Iterator::getData(size_t *length) const { + *length = 0; + + if (mFrameData == NULL) { + return NULL; + } + + *length = mFrameSize - getHeaderLength(); + + return mFrameData; +} + +size_t ID3::Iterator::getHeaderLength() const { + if (mParent.mVersion == ID3_V2_2) { + return 6; + } else { + CHECK_EQ(mParent.mVersion, ID3_V2_3); + return 10; + } +} + +void ID3::Iterator::findFrame() { + for (;;) { + mFrameData = NULL; + mFrameSize = 0; + + if (mParent.mVersion == ID3_V2_2) { + if (mOffset + 6 > mParent.mSize) { + return; + } + + if (!memcmp(&mParent.mData[mOffset], "\0\0\0", 3)) { + return; + } + + mFrameSize = + (mParent.mData[mOffset + 3] << 16) + | (mParent.mData[mOffset + 4] << 8) + | mParent.mData[mOffset + 5]; + + mFrameSize += 6; + + if (mOffset + mFrameSize > mParent.mSize) { + LOGV("partial frame at offset %d (size = %d, bytes-remaining = %d)", + mOffset, mFrameSize, mParent.mSize - mOffset - 6); + return; + } + + mFrameData = &mParent.mData[mOffset + 6]; + + if (!mID) { + break; + } + + char id[4]; + memcpy(id, &mParent.mData[mOffset], 3); + id[3] = '\0'; + + if (!strcmp(id, mID)) { + break; + } + } else { + CHECK_EQ(mParent.mVersion, ID3_V2_3); + + if (mOffset + 10 > mParent.mSize) { + return; + } + + if (!memcmp(&mParent.mData[mOffset], "\0\0\0\0", 4)) { + return; + } + + mFrameSize = 10 + U32_AT(&mParent.mData[mOffset + 4]); + + if (mOffset + mFrameSize > mParent.mSize) { + LOGV("partial frame at offset %d (size = %d, bytes-remaining = %d)", + mOffset, mFrameSize, mParent.mSize - mOffset - 10); + return; + } + + mFrameData = &mParent.mData[mOffset + 10]; + + if (!mID) { + break; + } + + char id[5]; + memcpy(id, &mParent.mData[mOffset], 4); + id[4] = '\0'; + + if (!strcmp(id, mID)) { + break; + } + } + + mOffset += mFrameSize; + } +} + +static size_t StringSize(const uint8_t *start, uint8_t encoding) { + if (encoding== 0x00) { + // ISO 8859-1 + return strlen((const char *)start) + 1; + } + + // UCS-2 + size_t n = 0; + while (start[n] != '\0' || start[n + 1] != '\0') { + n += 2; + } + + return n; +} + +const void * +ID3::getAlbumArt(size_t *length, String8 *mime) const { + *length = 0; + mime->setTo(""); + + Iterator it(*this, mVersion == ID3_V2_3 ? "APIC" : "PIC"); + + while (!it.done()) { + size_t size; + const uint8_t *data = it.getData(&size); + + if (mVersion == ID3_V2_3) { + uint8_t encoding = data[0]; + mime->setTo((const char *)&data[1]); + size_t mimeLen = strlen((const char *)&data[1]) + 1; + + uint8_t picType = data[1 + mimeLen]; +#if 0 + if (picType != 0x03) { + // Front Cover Art + it.next(); + continue; + } +#endif + + size_t descLen = StringSize(&data[2 + mimeLen], encoding); + + *length = size - 2 - mimeLen - descLen; + + return &data[2 + mimeLen + descLen]; + } else { + uint8_t encoding = data[0]; + + if (!memcmp(&data[1], "PNG", 3)) { + mime->setTo("image/png"); + } else if (!memcmp(&data[1], "JPG", 3)) { + mime->setTo("image/jpeg"); + } else if (!memcmp(&data[1], "-->", 3)) { + mime->setTo("text/plain"); + } else { + return NULL; + } + +#if 0 + uint8_t picType = data[4]; + if (picType != 0x03) { + // Front Cover Art + it.next(); + continue; + } +#endif + + size_t descLen = StringSize(&data[5], encoding); + + *length = size - 5 - descLen; + + return &data[5 + descLen]; + } + } + + return NULL; +} + + +} // namespace android diff --git a/media/libstagefright/id3/testid3.cpp b/media/libstagefright/id3/testid3.cpp new file mode 100644 index 0000000..305b065 --- /dev/null +++ b/media/libstagefright/id3/testid3.cpp @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "../include/ID3.h" + +#include <ctype.h> +#include <dirent.h> + +#include <binder/ProcessState.h> +#include <media/stagefright/FileSource.h> +#include <media/stagefright/MediaDebug.h> + +#define MAXPATHLEN 256 + +using namespace android; + +static void hexdump(const void *_data, size_t size) { + const uint8_t *data = (const uint8_t *)_data; + size_t offset = 0; + while (offset < size) { + printf("0x%04x ", offset); + + size_t n = size - offset; + if (n > 16) { + n = 16; + } + + for (size_t i = 0; i < 16; ++i) { + if (i == 8) { + printf(" "); + } + + if (offset + i < size) { + printf("%02x ", data[offset + i]); + } else { + printf(" "); + } + } + + printf(" "); + + for (size_t i = 0; i < n; ++i) { + if (isprint(data[offset + i])) { + printf("%c", data[offset + i]); + } else { + printf("."); + } + } + + printf("\n"); + + offset += 16; + } +} + +void scanFile(const char *path) { + sp<FileSource> file = new FileSource(path); + CHECK_EQ(file->initCheck(), OK); + + ID3 tag(file); + if (!tag.isValid()) { + printf("FAIL %s\n", path); + } else { + printf("SUCCESS %s\n", path); + + ID3::Iterator it(tag, NULL); + while (!it.done()) { + String8 id; + it.getID(&id); + + CHECK(id.length() > 0); + if (id[0] == 'T') { + String8 text; + it.getString(&text); + + printf(" found text frame '%s': %s\n", id.string(), text.string()); + } else { + printf(" found frame '%s'.\n", id.string()); + } + + it.next(); + } + + size_t dataSize; + String8 mime; + const void *data = tag.getAlbumArt(&dataSize, &mime); + + if (data) { + printf("found album art: size=%d mime='%s'\n", dataSize, + mime.string()); + + hexdump(data, dataSize > 128 ? 128 : dataSize); + } + } +} + +void scan(const char *path) { + DIR *dir = opendir(path); + + if (dir == NULL) { + return; + } + + rewinddir(dir); + + struct dirent *ent; + while ((ent = readdir(dir)) != NULL) { + if (!strcmp(".", ent->d_name) || !strcmp("..", ent->d_name)) { + continue; + } + + char newPath[MAXPATHLEN]; + strcpy(newPath, path); + strcat(newPath, "/"); + strcat(newPath, ent->d_name); + + if (ent->d_type == DT_DIR) { + scan(newPath); + } else if (ent->d_type == DT_REG) { + size_t len = strlen(ent->d_name); + + if (len >= 4 + && !strcasecmp(ent->d_name + len - 4, ".mp3")) { + scanFile(newPath); + } + } + } + + closedir(dir); + dir = NULL; +} + +int main(int argc, char **argv) { + android::ProcessState::self()->startThreadPool(); + + DataSource::RegisterDefaultSniffers(); + + for (int i = 1; i < argc; ++i) { + scan(argv[i]); + } + + return 0; +} diff --git a/media/libstagefright/include/ID3.h b/media/libstagefright/include/ID3.h new file mode 100644 index 0000000..79931ac --- /dev/null +++ b/media/libstagefright/include/ID3.h @@ -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_ + diff --git a/media/libstagefright/include/MP3Extractor.h b/media/libstagefright/include/MP3Extractor.h index b5a6b3c..3ce6df3 100644 --- a/media/libstagefright/include/MP3Extractor.h +++ b/media/libstagefright/include/MP3Extractor.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(); diff --git a/media/libstagefright/include/StagefrightMetadataRetriever.h b/media/libstagefright/include/StagefrightMetadataRetriever.h index 16127d7..b80387f 100644 --- a/media/libstagefright/include/StagefrightMetadataRetriever.h +++ b/media/libstagefright/include/StagefrightMetadataRetriever.h @@ -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=( |