Support for ogg(vorbis) metadata in stagefright including album art.
Change-Id: I0c8c0136cebe2d2d97caabb7bc0c65be86329dbb related-to-bug: 2713414
This commit is contained in:
@@ -78,6 +78,8 @@ struct MyVorbisExtractor {
|
|||||||
|
|
||||||
void init();
|
void init();
|
||||||
|
|
||||||
|
sp<MetaData> getFileMetaData() { return mFileMeta; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct Page {
|
struct Page {
|
||||||
uint64_t mGranulePosition;
|
uint64_t mGranulePosition;
|
||||||
@@ -100,6 +102,7 @@ private:
|
|||||||
vorbis_comment mVc;
|
vorbis_comment mVc;
|
||||||
|
|
||||||
sp<MetaData> mMeta;
|
sp<MetaData> mMeta;
|
||||||
|
sp<MetaData> mFileMeta;
|
||||||
|
|
||||||
ssize_t readPage(off_t offset, Page *page);
|
ssize_t readPage(off_t offset, Page *page);
|
||||||
status_t findNextPage(off_t startOffset, off_t *pageOffset);
|
status_t findNextPage(off_t startOffset, off_t *pageOffset);
|
||||||
@@ -107,6 +110,9 @@ private:
|
|||||||
void verifyHeader(
|
void verifyHeader(
|
||||||
MediaBuffer *buffer, uint8_t type);
|
MediaBuffer *buffer, uint8_t type);
|
||||||
|
|
||||||
|
void parseFileMetaData();
|
||||||
|
void extractAlbumArt(const void *data, size_t size);
|
||||||
|
|
||||||
MyVorbisExtractor(const MyVorbisExtractor &);
|
MyVorbisExtractor(const MyVorbisExtractor &);
|
||||||
MyVorbisExtractor &operator=(const MyVorbisExtractor &);
|
MyVorbisExtractor &operator=(const MyVorbisExtractor &);
|
||||||
};
|
};
|
||||||
@@ -188,9 +194,14 @@ MyVorbisExtractor::MyVorbisExtractor(const sp<DataSource> &source)
|
|||||||
mNextLaceIndex(0),
|
mNextLaceIndex(0),
|
||||||
mFirstDataOffset(-1) {
|
mFirstDataOffset(-1) {
|
||||||
mCurrentPage.mNumSegments = 0;
|
mCurrentPage.mNumSegments = 0;
|
||||||
|
|
||||||
|
vorbis_info_init(&mVi);
|
||||||
|
vorbis_comment_init(&mVc);
|
||||||
}
|
}
|
||||||
|
|
||||||
MyVorbisExtractor::~MyVorbisExtractor() {
|
MyVorbisExtractor::~MyVorbisExtractor() {
|
||||||
|
vorbis_comment_clear(&mVc);
|
||||||
|
vorbis_info_clear(&mVi);
|
||||||
}
|
}
|
||||||
|
|
||||||
sp<MetaData> MyVorbisExtractor::getFormat() const {
|
sp<MetaData> MyVorbisExtractor::getFormat() const {
|
||||||
@@ -305,7 +316,7 @@ ssize_t MyVorbisExtractor::readPage(off_t offset, Page *page) {
|
|||||||
tmp.append(x);
|
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;
|
return sizeof(header) + page->mNumSegments + totalSize;
|
||||||
}
|
}
|
||||||
@@ -422,10 +433,6 @@ status_t MyVorbisExtractor::readNextPacket(MediaBuffer **out) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MyVorbisExtractor::init() {
|
void MyVorbisExtractor::init() {
|
||||||
vorbis_info_init(&mVi);
|
|
||||||
|
|
||||||
vorbis_comment mVc;
|
|
||||||
|
|
||||||
mMeta = new MetaData;
|
mMeta = new MetaData;
|
||||||
mMeta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_VORBIS);
|
mMeta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_VORBIS);
|
||||||
|
|
||||||
@@ -509,6 +516,8 @@ void MyVorbisExtractor::verifyHeader(
|
|||||||
case 3:
|
case 3:
|
||||||
{
|
{
|
||||||
CHECK_EQ(0, _vorbis_unpack_comment(&mVc, &bits));
|
CHECK_EQ(0, _vorbis_unpack_comment(&mVc, &bits));
|
||||||
|
|
||||||
|
parseFileMetaData();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -530,6 +539,192 @@ uint64_t MyVorbisExtractor::approxBitrate() {
|
|||||||
return (mVi.bitrate_lower + mVi.bitrate_upper) / 2;
|
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)
|
OggExtractor::OggExtractor(const sp<DataSource> &source)
|
||||||
@@ -570,15 +765,7 @@ sp<MetaData> OggExtractor::getTrackMetaData(
|
|||||||
}
|
}
|
||||||
|
|
||||||
sp<MetaData> OggExtractor::getMetaData() {
|
sp<MetaData> OggExtractor::getMetaData() {
|
||||||
sp<MetaData> meta = new MetaData;
|
return mImpl->getFileMetaData();
|
||||||
|
|
||||||
if (mInitCheck != OK) {
|
|
||||||
return meta;
|
|
||||||
}
|
|
||||||
|
|
||||||
meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_CONTAINER_OGG);
|
|
||||||
|
|
||||||
return meta;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SniffOgg(
|
bool SniffOgg(
|
||||||
|
|||||||
@@ -26,10 +26,6 @@
|
|||||||
// Sonivox includes
|
// Sonivox includes
|
||||||
#include <libsonivox/eas.h>
|
#include <libsonivox/eas.h>
|
||||||
|
|
||||||
// Ogg Vorbis includes
|
|
||||||
#include <Tremolo/ivorbiscodec.h>
|
|
||||||
#include <Tremolo/ivorbisfile.h>
|
|
||||||
|
|
||||||
namespace android {
|
namespace android {
|
||||||
|
|
||||||
StagefrightMediaScanner::StagefrightMediaScanner()
|
StagefrightMediaScanner::StagefrightMediaScanner()
|
||||||
@@ -103,48 +99,6 @@ static status_t HandleMIDI(
|
|||||||
return OK;
|
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(
|
status_t StagefrightMediaScanner::processFile(
|
||||||
const char *path, const char *mimeType,
|
const char *path, const char *mimeType,
|
||||||
MediaScannerClient &client) {
|
MediaScannerClient &client) {
|
||||||
@@ -176,10 +130,6 @@ status_t StagefrightMediaScanner::processFile(
|
|||||||
return HandleMIDI(path, &client);
|
return HandleMIDI(path, &client);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!strcasecmp(extension, ".ogg")) {
|
|
||||||
return HandleOGG(path, &client);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mRetriever->setDataSource(path) == OK
|
if (mRetriever->setDataSource(path) == OK
|
||||||
&& mRetriever->setMode(
|
&& mRetriever->setMode(
|
||||||
METADATA_MODE_METADATA_RETRIEVAL_ONLY) == OK) {
|
METADATA_MODE_METADATA_RETRIEVAL_ONLY) == OK) {
|
||||||
|
|||||||
Reference in New Issue
Block a user