am c6c62e12: Support for ogg(vorbis) metadata in stagefright including album art.

This commit is contained in:
Andreas Huber
2010-05-25 11:33:39 -07:00
committed by Android Git Automerger
2 changed files with 201 additions and 64 deletions

View File

@ -78,6 +78,8 @@ struct MyVorbisExtractor {
void init();
sp<MetaData> getFileMetaData() { return mFileMeta; }
private:
struct Page {
uint64_t mGranulePosition;
@ -100,6 +102,7 @@ private:
vorbis_comment mVc;
sp<MetaData> mMeta;
sp<MetaData> mFileMeta;
ssize_t readPage(off_t offset, Page *page);
status_t findNextPage(off_t startOffset, off_t *pageOffset);
@ -107,6 +110,9 @@ private:
void verifyHeader(
MediaBuffer *buffer, uint8_t type);
void parseFileMetaData();
void extractAlbumArt(const void *data, size_t size);
MyVorbisExtractor(const MyVorbisExtractor &);
MyVorbisExtractor &operator=(const MyVorbisExtractor &);
};
@ -188,9 +194,14 @@ MyVorbisExtractor::MyVorbisExtractor(const sp<DataSource> &source)
mNextLaceIndex(0),
mFirstDataOffset(-1) {
mCurrentPage.mNumSegments = 0;
vorbis_info_init(&mVi);
vorbis_comment_init(&mVc);
}
MyVorbisExtractor::~MyVorbisExtractor() {
vorbis_comment_clear(&mVc);
vorbis_info_clear(&mVi);
}
sp<MetaData> MyVorbisExtractor::getFormat() const {
@ -305,7 +316,7 @@ ssize_t MyVorbisExtractor::readPage(off_t offset, Page *page) {
tmp.append(x);
}
LOGV("%c %s", page->mFlags & 1 ? '+' : ' ', tmp.string());
// LOGV("%c %s", page->mFlags & 1 ? '+' : ' ', tmp.string());
return sizeof(header) + page->mNumSegments + totalSize;
}
@ -422,10 +433,6 @@ status_t MyVorbisExtractor::readNextPacket(MediaBuffer **out) {
}
void MyVorbisExtractor::init() {
vorbis_info_init(&mVi);
vorbis_comment mVc;
mMeta = new MetaData;
mMeta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_VORBIS);
@ -509,6 +516,8 @@ void MyVorbisExtractor::verifyHeader(
case 3:
{
CHECK_EQ(0, _vorbis_unpack_comment(&mVc, &bits));
parseFileMetaData();
break;
}
@ -530,6 +539,192 @@ uint64_t MyVorbisExtractor::approxBitrate() {
return (mVi.bitrate_lower + mVi.bitrate_upper) / 2;
}
void MyVorbisExtractor::parseFileMetaData() {
mFileMeta = new MetaData;
mFileMeta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_CONTAINER_OGG);
struct {
const char *const mTag;
uint32_t mKey;
} kMap[] = {
{ "TITLE", kKeyTitle },
{ "ARTIST", kKeyArtist },
{ "ALBUM", kKeyAlbum },
{ "COMPOSER", kKeyComposer },
{ "GENRE", kKeyGenre },
{ "AUTHOR", kKeyAuthor },
{ "TRACKNUMBER", kKeyCDTrackNumber },
{ "DISCNUMBER", kKeyDiscNumber },
{ "DATE", kKeyDate },
{ "LYRICIST", kKeyWriter },
{ "METADATA_BLOCK_PICTURE", kKeyAlbumArt },
};
for (int i = 0; i < mVc.comments; ++i) {
const char *comment = mVc.user_comments[i];
for (size_t j = 0; j < sizeof(kMap) / sizeof(kMap[0]); ++j) {
size_t tagLen = strlen(kMap[j].mTag);
if (!strncasecmp(kMap[j].mTag, comment, tagLen)
&& comment[tagLen] == '=') {
if (kMap[j].mKey == kKeyAlbumArt) {
extractAlbumArt(
&comment[tagLen + 1],
mVc.comment_lengths[i] - tagLen - 1);
} else {
mFileMeta->setCString(kMap[j].mKey, &comment[tagLen + 1]);
}
}
}
}
#if 0
for (int i = 0; i < mVc.comments; ++i) {
LOGI("comment #%d: '%s'", i + 1, mVc.user_comments[i]);
}
#endif
}
// The returned buffer should be free()d.
static uint8_t *DecodeBase64(const char *s, size_t size, size_t *outSize) {
*outSize = 0;
if ((size % 4) != 0) {
return NULL;
}
size_t n = size;
size_t padding = 0;
if (n >= 1 && s[n - 1] == '=') {
padding = 1;
if (n >= 2 && s[n - 2] == '=') {
padding = 2;
}
}
size_t outLen = 3 * size / 4 - padding;
*outSize = outLen;
void *buffer = malloc(outLen);
uint8_t *out = (uint8_t *)buffer;
size_t j = 0;
uint32_t accum = 0;
for (size_t i = 0; i < n; ++i) {
char c = s[i];
unsigned value;
if (c >= 'A' && c <= 'Z') {
value = c - 'A';
} else if (c >= 'a' && c <= 'z') {
value = 26 + c - 'a';
} else if (c >= '0' && c <= '9') {
value = 52 + c - '0';
} else if (c == '+') {
value = 62;
} else if (c == '/') {
value = 63;
} else if (c != '=') {
return NULL;
} else {
if (i < n - padding) {
return NULL;
}
value = 0;
}
accum = (accum << 6) | value;
if (((i + 1) % 4) == 0) {
out[j++] = (accum >> 16);
if (j < outLen) { out[j++] = (accum >> 8) & 0xff; }
if (j < outLen) { out[j++] = accum & 0xff; }
accum = 0;
}
}
return (uint8_t *)buffer;
}
void MyVorbisExtractor::extractAlbumArt(const void *data, size_t size) {
LOGV("extractAlbumArt from '%s'", (const char *)data);
size_t flacSize;
uint8_t *flac = DecodeBase64((const char *)data, size, &flacSize);
if (flac == NULL) {
LOGE("malformed base64 encoded data.");
return;
}
LOGV("got flac of size %d", flacSize);
uint32_t picType;
uint32_t typeLen;
uint32_t descLen;
uint32_t dataLen;
char type[128];
if (flacSize < 8) {
goto exit;
}
picType = U32_AT(flac);
if (picType != 3) {
// This is not a front cover.
goto exit;
}
typeLen = U32_AT(&flac[4]);
if (typeLen + 1 > sizeof(type)) {
goto exit;
}
if (flacSize < 8 + typeLen) {
goto exit;
}
memcpy(type, &flac[8], typeLen);
type[typeLen] = '\0';
LOGV("picType = %d, type = '%s'", picType, type);
if (!strcmp(type, "-->")) {
// This is not inline cover art, but an external url instead.
goto exit;
}
descLen = U32_AT(&flac[8 + typeLen]);
if (flacSize < 32 + typeLen + descLen) {
goto exit;
}
dataLen = U32_AT(&flac[8 + typeLen + 4 + descLen + 16]);
if (flacSize < 32 + typeLen + descLen + dataLen) {
goto exit;
}
LOGV("got image data, %d trailing bytes",
flacSize - 32 - typeLen - descLen - dataLen);
mFileMeta->setData(
kKeyAlbumArt, 0, &flac[8 + typeLen + 4 + descLen + 20], dataLen);
mFileMeta->setCString(kKeyAlbumArtMIME, type);
exit:
free(flac);
flac = NULL;
}
////////////////////////////////////////////////////////////////////////////////
OggExtractor::OggExtractor(const sp<DataSource> &source)
@ -570,15 +765,7 @@ sp<MetaData> OggExtractor::getTrackMetaData(
}
sp<MetaData> OggExtractor::getMetaData() {
sp<MetaData> meta = new MetaData;
if (mInitCheck != OK) {
return meta;
}
meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_CONTAINER_OGG);
return meta;
return mImpl->getFileMetaData();
}
bool SniffOgg(

View File

@ -26,10 +26,6 @@
// Sonivox includes
#include <libsonivox/eas.h>
// Ogg Vorbis includes
#include <Tremolo/ivorbiscodec.h>
#include <Tremolo/ivorbisfile.h>
namespace android {
StagefrightMediaScanner::StagefrightMediaScanner()
@ -103,48 +99,6 @@ static status_t HandleMIDI(
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) {
@ -176,10 +130,6 @@ status_t StagefrightMediaScanner::processFile(
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) {