am c6c62e12
: Support for ogg(vorbis) metadata in stagefright including album art.
This commit is contained in:
@ -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(
|
||||
|
@ -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) {
|
||||
|
Reference in New Issue
Block a user