diff options
author | Andreas Huber <andih@google.com> | 2010-11-09 14:59:13 -0800 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2010-11-09 14:59:13 -0800 |
commit | 40e8dc171f07c1d7e0f68ccf6af9313522d0520d (patch) | |
tree | 2c0f30b47c350dfeb51dcafb8004caf927ae6f45 | |
parent | fb55ba980769eb312f9091b6ab30fef2e2b04930 (diff) | |
parent | 1bd9949815b93b5a741c0f2533bf50d32542dbe5 (diff) | |
download | frameworks_base-40e8dc171f07c1d7e0f68ccf6af9313522d0520d.zip frameworks_base-40e8dc171f07c1d7e0f68ccf6af9313522d0520d.tar.gz frameworks_base-40e8dc171f07c1d7e0f68ccf6af9313522d0520d.tar.bz2 |
Merge "Some refactoring and added support for another form of vbr headers."
-rw-r--r-- | media/libstagefright/Android.mk | 2 | ||||
-rw-r--r-- | media/libstagefright/MP3Extractor.cpp | 261 | ||||
-rw-r--r-- | media/libstagefright/VBRISeeker.cpp | 164 | ||||
-rw-r--r-- | media/libstagefright/XINGSeeker.cpp | 223 | ||||
-rw-r--r-- | media/libstagefright/include/MP3Extractor.h | 9 | ||||
-rw-r--r-- | media/libstagefright/include/MP3Seeker.h | 46 | ||||
-rw-r--r-- | media/libstagefright/include/VBRISeeker.h | 50 | ||||
-rw-r--r-- | media/libstagefright/include/XINGSeeker.h | 50 |
8 files changed, 609 insertions, 196 deletions
diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk index 562eb60..1df0bed 100644 --- a/media/libstagefright/Android.mk +++ b/media/libstagefright/Android.mk @@ -44,8 +44,10 @@ LOCAL_SRC_FILES:= \ TimeSource.cpp \ TimedEventQueue.cpp \ Utils.cpp \ + VBRISeeker.cpp \ WAVExtractor.cpp \ WVMExtractor.cpp \ + XINGSeeker.cpp \ avc_utils.cpp \ string.cpp diff --git a/media/libstagefright/MP3Extractor.cpp b/media/libstagefright/MP3Extractor.cpp index 82c0426..84ced8f 100644 --- a/media/libstagefright/MP3Extractor.cpp +++ b/media/libstagefright/MP3Extractor.cpp @@ -21,6 +21,8 @@ #include "include/MP3Extractor.h" #include "include/ID3.h" +#include "include/VBRISeeker.h" +#include "include/XINGSeeker.h" #include <media/stagefright/foundation/AMessage.h> #include <media/stagefright/DataSource.h> @@ -42,10 +44,11 @@ namespace android { // Yes ... there are things that must indeed match... static const uint32_t kMask = 0xfffe0cc0; -static bool get_mp3_frame_size( +// static +bool MP3Extractor::get_mp3_frame_size( uint32_t header, size_t *frame_size, - int *out_sampling_rate = NULL, int *out_channels = NULL, - int *out_bitrate = NULL) { + int *out_sampling_rate, int *out_channels, + int *out_bitrate) { *frame_size = 0; if (out_sampling_rate) { @@ -178,136 +181,13 @@ static bool get_mp3_frame_size( return true; } -static bool parse_xing_header( - const sp<DataSource> &source, off_t first_frame_pos, - int32_t *frame_number = NULL, int32_t *byte_number = NULL, - char *table_of_contents = NULL, int32_t *quality_indicator = NULL, - int64_t *duration = NULL) { - - if (frame_number) { - *frame_number = 0; - } - if (byte_number) { - *byte_number = 0; - } - if (table_of_contents) { - table_of_contents[0] = 0; - } - if (quality_indicator) { - *quality_indicator = 0; - } - if (duration) { - *duration = 0; - } - - uint8_t buffer[4]; - int offset = first_frame_pos; - if (source->readAt(offset, &buffer, 4) < 4) { // get header - return false; - } - offset += 4; - - uint8_t id, layer, sr_index, mode; - layer = (buffer[1] >> 1) & 3; - id = (buffer[1] >> 3) & 3; - sr_index = (buffer[2] >> 2) & 3; - mode = (buffer[3] >> 6) & 3; - if (layer == 0) { - return false; - } - if (id == 1) { - return false; - } - if (sr_index == 3) { - return false; - } - // determine offset of XING header - if(id&1) { // mpeg1 - if (mode != 3) offset += 32; - else offset += 17; - } else { // mpeg2 - if (mode != 3) offset += 17; - else offset += 9; - } - - if (source->readAt(offset, &buffer, 4) < 4) { // XING header ID - return false; - } - offset += 4; - // Check XING ID - if ((buffer[0] != 'X') || (buffer[1] != 'i') - || (buffer[2] != 'n') || (buffer[3] != 'g')) { - if ((buffer[0] != 'I') || (buffer[1] != 'n') - || (buffer[2] != 'f') || (buffer[3] != 'o')) { - return false; - } - } - - if (source->readAt(offset, &buffer, 4) < 4) { // flags - return false; - } - offset += 4; - uint32_t flags = U32_AT(buffer); - - if (flags & 0x0001) { // Frames field is present - if (source->readAt(offset, buffer, 4) < 4) { - return false; - } - if (frame_number) { - *frame_number = U32_AT(buffer); - } - int32_t frame = U32_AT(buffer); - // Samples per Frame: 1. index = MPEG Version ID, 2. index = Layer - const int samplesPerFrames[2][3] = - { - { 384, 1152, 576 }, // MPEG 2, 2.5: layer1, layer2, layer3 - { 384, 1152, 1152 }, // MPEG 1: layer1, layer2, layer3 - }; - // sampling rates in hertz: 1. index = MPEG Version ID, 2. index = sampling rate index - const int samplingRates[4][3] = - { - { 11025, 12000, 8000, }, // MPEG 2.5 - { 0, 0, 0, }, // reserved - { 22050, 24000, 16000, }, // MPEG 2 - { 44100, 48000, 32000, } // MPEG 1 - }; - if (duration) { - *duration = (int64_t)frame * samplesPerFrames[id&1][3-layer] * 1000000LL - / samplingRates[id][sr_index]; - } - offset += 4; - } - if (flags & 0x0002) { // Bytes field is present - if (byte_number) { - if (source->readAt(offset, buffer, 4) < 4) { - return false; - } - *byte_number = U32_AT(buffer); - } - offset += 4; - } - if (flags & 0x0004) { // TOC field is present - if (table_of_contents) { - if (source->readAt(offset + 1, table_of_contents, 99) < 99) { - return false; - } - } - offset += 100; - } - if (flags & 0x0008) { // Quality indicator field is present - if (quality_indicator) { - if (source->readAt(offset, buffer, 4) < 4) { - return false; - } - *quality_indicator = U32_AT(buffer); - } - } - return true; -} - static bool Resync( const sp<DataSource> &source, uint32_t match_header, - off_t *inout_pos, uint32_t *out_header) { + off_t *inout_pos, off_t *post_id3_pos, uint32_t *out_header) { + if (post_id3_pos != NULL) { + *post_id3_pos = 0; + } + if (*inout_pos == 0) { // Skip an optional ID3 header if syncing at the very beginning // of the datasource. @@ -340,6 +220,10 @@ static bool Resync( LOGV("skipped ID3 tag, new starting offset is %ld (0x%08lx)", *inout_pos, *inout_pos); } + + if (post_id3_pos != NULL) { + *post_id3_pos = *inout_pos; + } } off_t pos = *inout_pos; @@ -365,8 +249,9 @@ static bool Resync( size_t frame_size; int sample_rate, num_channels, bitrate; - if (!get_mp3_frame_size(header, &frame_size, - &sample_rate, &num_channels, &bitrate)) { + if (!MP3Extractor::get_mp3_frame_size( + header, &frame_size, + &sample_rate, &num_channels, &bitrate)) { ++pos; continue; } @@ -396,7 +281,8 @@ static bool Resync( } size_t test_frame_size; - if (!get_mp3_frame_size(test_header, &test_frame_size)) { + if (!MP3Extractor::get_mp3_frame_size( + test_header, &test_frame_size)) { valid = false; break; } @@ -427,7 +313,7 @@ public: MP3Source( const sp<MetaData> &meta, const sp<DataSource> &source, off_t first_frame_pos, uint32_t fixed_header, - int32_t byte_number, const char *table_of_contents); + const sp<MP3Seeker> &seeker); virtual status_t start(MetaData *params = NULL); virtual status_t stop(); @@ -448,9 +334,7 @@ private: off_t mCurrentPos; int64_t mCurrentTimeUs; bool mStarted; - int32_t mByteNumber; // total number of bytes in this MP3 - // TOC entries in XING header. Skip the first one since it's always 0. - char mTableOfContents[99]; + sp<MP3Seeker> mSeeker; MediaBufferGroup *mGroup; MP3Source(const MP3Source &); @@ -462,25 +346,28 @@ MP3Extractor::MP3Extractor( : mInitCheck(NO_INIT), mDataSource(source), mFirstFramePos(-1), - mFixedHeader(0), - mByteNumber(0) { + mFixedHeader(0) { off_t pos = 0; + off_t post_id3_pos; uint32_t header; bool success; int64_t meta_offset; uint32_t meta_header; + int64_t meta_post_id3_offset; if (meta != NULL && meta->findInt64("offset", &meta_offset) - && meta->findInt32("header", (int32_t *)&meta_header)) { + && meta->findInt32("header", (int32_t *)&meta_header) + && meta->findInt64("post-id3-offset", &meta_post_id3_offset)) { // The sniffer has already done all the hard work for us, simply // accept its judgement. pos = (off_t)meta_offset; header = meta_header; + post_id3_pos = (off_t)meta_post_id3_offset; success = true; } else { - success = Resync(mDataSource, 0, &pos, &header); + success = Resync(mDataSource, 0, &pos, &post_id3_pos, &header); } if (!success) { @@ -505,21 +392,27 @@ MP3Extractor::MP3Extractor( mMeta->setInt32(kKeyBitRate, bitrate * 1000); mMeta->setInt32(kKeyChannelCount, num_channels); - int64_t duration; - parse_xing_header( - mDataSource, mFirstFramePos, NULL, &mByteNumber, - mTableOfContents, NULL, &duration); - if (duration > 0) { - mMeta->setInt64(kKeyDuration, duration); - } else { + mSeeker = XINGSeeker::CreateFromSource(mDataSource, mFirstFramePos); + + if (mSeeker == NULL) { + mSeeker = VBRISeeker::CreateFromSource(mDataSource, post_id3_pos); + } + + int64_t durationUs; + + if (mSeeker == NULL || !mSeeker->getDuration(&durationUs)) { off_t fileSize; if (mDataSource->getSize(&fileSize) == OK) { - mMeta->setInt64( - kKeyDuration, - 8000LL * (fileSize - mFirstFramePos) / bitrate); + durationUs = 8000LL * (fileSize - mFirstFramePos) / bitrate; + } else { + durationUs = -1; } } + if (durationUs >= 0) { + mMeta->setInt64(kKeyDuration, durationUs); + } + mInitCheck = OK; } @@ -534,7 +427,7 @@ sp<MediaSource> MP3Extractor::getTrack(size_t index) { return new MP3Source( mMeta, mDataSource, mFirstFramePos, mFixedHeader, - mByteNumber, mTableOfContents); + mSeeker); } sp<MetaData> MP3Extractor::getTrackMetaData(size_t index, uint32_t flags) { @@ -550,7 +443,7 @@ sp<MetaData> MP3Extractor::getTrackMetaData(size_t index, uint32_t flags) { MP3Source::MP3Source( const sp<MetaData> &meta, const sp<DataSource> &source, off_t first_frame_pos, uint32_t fixed_header, - int32_t byte_number, const char *table_of_contents) + const sp<MP3Seeker> &seeker) : mMeta(meta), mDataSource(source), mFirstFramePos(first_frame_pos), @@ -558,9 +451,8 @@ MP3Source::MP3Source( mCurrentPos(0), mCurrentTimeUs(0), mStarted(false), - mByteNumber(byte_number), + mSeeker(seeker), mGroup(NULL) { - memcpy (mTableOfContents, table_of_contents, sizeof(mTableOfContents)); } MP3Source::~MP3Source() { @@ -607,43 +499,21 @@ status_t MP3Source::read( int64_t seekTimeUs; ReadOptions::SeekMode mode; if (options != NULL && options->getSeekTo(&seekTimeUs, &mode)) { - int32_t bitrate; - if (!mMeta->findInt32(kKeyBitRate, &bitrate)) { - // bitrate is in bits/sec. - LOGI("no bitrate"); - - return ERROR_UNSUPPORTED; - } - - mCurrentTimeUs = seekTimeUs; - // interpolate in TOC to get file seek point in bytes - int64_t duration; - if ((mByteNumber > 0) && (mTableOfContents[0] > 0) - && mMeta->findInt64(kKeyDuration, &duration)) { - float percent = (float)seekTimeUs * 100 / duration; - float fx; - if( percent <= 0.0f ) { - fx = 0.0f; - } else if( percent >= 100.0f ) { - fx = 256.0f; - } else { - int a = (int)percent; - float fa, fb; - if ( a == 0 ) { - fa = 0.0f; - } else { - fa = (float)mTableOfContents[a-1]; - } - if ( a < 99 ) { - fb = (float)mTableOfContents[a]; - } else { - fb = 256.0f; - } - fx = fa + (fb-fa)*(percent-a); + int64_t actualSeekTimeUs = seekTimeUs; + if (mSeeker == NULL + || !mSeeker->getOffsetForTime(&actualSeekTimeUs, &mCurrentPos)) { + int32_t bitrate; + if (!mMeta->findInt32(kKeyBitRate, &bitrate)) { + // bitrate is in bits/sec. + LOGI("no bitrate"); + + return ERROR_UNSUPPORTED; } - mCurrentPos = mFirstFramePos + (int)((1.0f/256.0f)*fx*mByteNumber); - } else { + + mCurrentTimeUs = seekTimeUs; mCurrentPos = mFirstFramePos + seekTimeUs * bitrate / 8000000; + } else { + mCurrentTimeUs = actualSeekTimeUs; } } @@ -667,7 +537,8 @@ status_t MP3Source::read( uint32_t header = U32_AT((const uint8_t *)buffer->data()); if ((header & kMask) == (mFixedHeader & kMask) - && get_mp3_frame_size(header, &frame_size, NULL, NULL, &bitrate)) { + && MP3Extractor::get_mp3_frame_size( + header, &frame_size, NULL, NULL, &bitrate)) { break; } @@ -675,7 +546,7 @@ status_t MP3Source::read( LOGV("lost sync! header = 0x%08x, old header = 0x%08x\n", header, mFixedHeader); off_t pos = mCurrentPos; - if (!Resync(mDataSource, mFixedHeader, &pos, NULL)) { + if (!Resync(mDataSource, mFixedHeader, &pos, NULL, NULL)) { LOGE("Unable to resync. Signalling end of stream."); buffer->release(); @@ -781,14 +652,16 @@ bool SniffMP3( const sp<DataSource> &source, String8 *mimeType, float *confidence, sp<AMessage> *meta) { off_t pos = 0; + off_t post_id3_pos; uint32_t header; - if (!Resync(source, 0, &pos, &header)) { + if (!Resync(source, 0, &pos, &post_id3_pos, &header)) { return false; } *meta = new AMessage; (*meta)->setInt64("offset", pos); (*meta)->setInt32("header", header); + (*meta)->setInt64("post-id3-offset", post_id3_pos); *mimeType = MEDIA_MIMETYPE_AUDIO_MPEG; *confidence = 0.2f; diff --git a/media/libstagefright/VBRISeeker.cpp b/media/libstagefright/VBRISeeker.cpp new file mode 100644 index 0000000..6608644 --- /dev/null +++ b/media/libstagefright/VBRISeeker.cpp @@ -0,0 +1,164 @@ +/* + * 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 "VBRISeeker" +#include <utils/Log.h> + +#include "include/VBRISeeker.h" + +#include "include/MP3Extractor.h" + +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/DataSource.h> +#include <media/stagefright/Utils.h> + +namespace android { + +static uint32_t U24_AT(const uint8_t *ptr) { + return ptr[0] << 16 | ptr[1] << 8 | ptr[2]; +} + +// static +sp<VBRISeeker> VBRISeeker::CreateFromSource( + const sp<DataSource> &source, off_t post_id3_pos) { + off_t pos = post_id3_pos; + + uint8_t header[4]; + ssize_t n = source->readAt(pos, header, sizeof(header)); + if (n < (ssize_t)sizeof(header)) { + return NULL; + } + + uint32_t tmp = U32_AT(&header[0]); + size_t frameSize; + int sampleRate; + if (!MP3Extractor::get_mp3_frame_size(tmp, &frameSize, &sampleRate)) { + return NULL; + } + + // VBRI header follows 32 bytes after the header _ends_. + pos += sizeof(header) + 32; + + uint8_t vbriHeader[26]; + n = source->readAt(pos, vbriHeader, sizeof(vbriHeader)); + if (n < (ssize_t)sizeof(vbriHeader)) { + return NULL; + } + + if (memcmp(vbriHeader, "VBRI", 4)) { + return NULL; + } + + size_t numFrames = U32_AT(&vbriHeader[14]); + + int64_t durationUs = + numFrames * 1000000ll * (sampleRate >= 32000 ? 1152 : 576) / sampleRate; + + LOGV("duration = %.2f secs", durationUs / 1E6); + + size_t numEntries = U16_AT(&vbriHeader[18]); + size_t entrySize = U16_AT(&vbriHeader[22]); + size_t scale = U16_AT(&vbriHeader[20]); + + LOGV("%d entries, scale=%d, size_per_entry=%d", + numEntries, + scale, + entrySize); + + size_t totalEntrySize = numEntries * entrySize; + uint8_t *buffer = new uint8_t[totalEntrySize]; + + n = source->readAt(pos + sizeof(vbriHeader), buffer, totalEntrySize); + if (n < (ssize_t)totalEntrySize) { + delete[] buffer; + buffer = NULL; + + return NULL; + } + + sp<VBRISeeker> seeker = new VBRISeeker; + seeker->mBasePos = post_id3_pos; + seeker->mDurationUs = durationUs; + + off_t offset = post_id3_pos; + for (size_t i = 0; i < numEntries; ++i) { + uint32_t numBytes; + switch (entrySize) { + case 1: numBytes = buffer[i]; break; + case 2: numBytes = U16_AT(buffer + 2 * i); break; + case 3: numBytes = U24_AT(buffer + 3 * i); break; + default: + { + CHECK_EQ(entrySize, 4u); + numBytes = U32_AT(buffer + 4 * i); break; + } + } + + numBytes *= scale; + + seeker->mSegments.push(numBytes); + + LOGV("entry #%d: %d offset 0x%08lx", i, numBytes, offset); + offset += numBytes; + } + + delete[] buffer; + buffer = NULL; + + LOGI("Found VBRI header."); + + return seeker; +} + +VBRISeeker::VBRISeeker() + : mDurationUs(-1) { +} + +bool VBRISeeker::getDuration(int64_t *durationUs) { + if (mDurationUs < 0) { + return false; + } + + *durationUs = mDurationUs; + + return true; +} + +bool VBRISeeker::getOffsetForTime(int64_t *timeUs, off_t *pos) { + if (mDurationUs < 0) { + return false; + } + + int64_t segmentDurationUs = mDurationUs / mSegments.size(); + + int64_t nowUs = 0; + *pos = mBasePos; + size_t segmentIndex = 0; + while (segmentIndex < mSegments.size() && nowUs < *timeUs) { + nowUs += segmentDurationUs; + *pos += mSegments.itemAt(segmentIndex++); + } + + LOGV("getOffsetForTime %lld us => 0x%08lx", *timeUs, *pos); + + *timeUs = nowUs; + + return true; +} + +} // namespace android + diff --git a/media/libstagefright/XINGSeeker.cpp b/media/libstagefright/XINGSeeker.cpp new file mode 100644 index 0000000..72f260e --- /dev/null +++ b/media/libstagefright/XINGSeeker.cpp @@ -0,0 +1,223 @@ +/* + * 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/XINGSeeker.h" + +#include <media/stagefright/DataSource.h> +#include <media/stagefright/Utils.h> + +namespace android { + +static bool parse_xing_header( + const sp<DataSource> &source, off_t first_frame_pos, + int32_t *frame_number = NULL, int32_t *byte_number = NULL, + char *table_of_contents = NULL, int32_t *quality_indicator = NULL, + int64_t *duration = NULL); + +// static +sp<XINGSeeker> XINGSeeker::CreateFromSource( + const sp<DataSource> &source, off_t first_frame_pos) { + sp<XINGSeeker> seeker = new XINGSeeker; + + seeker->mFirstFramePos = first_frame_pos; + + if (!parse_xing_header( + source, first_frame_pos, + NULL, &seeker->mSizeBytes, seeker->mTableOfContents, + NULL, &seeker->mDurationUs)) { + return NULL; + } + + LOGI("Found XING header."); + + return seeker; +} + +XINGSeeker::XINGSeeker() + : mDurationUs(-1), + mSizeBytes(0) { +} + +bool XINGSeeker::getDuration(int64_t *durationUs) { + if (mDurationUs < 0) { + return false; + } + + *durationUs = mDurationUs; + + return true; +} + +bool XINGSeeker::getOffsetForTime(int64_t *timeUs, off_t *pos) { + if (mSizeBytes == 0 || mTableOfContents[0] <= 0 || mDurationUs < 0) { + return false; + } + + float percent = (float)(*timeUs) * 100 / mDurationUs; + float fx; + if( percent <= 0.0f ) { + fx = 0.0f; + } else if( percent >= 100.0f ) { + fx = 256.0f; + } else { + int a = (int)percent; + float fa, fb; + if ( a == 0 ) { + fa = 0.0f; + } else { + fa = (float)mTableOfContents[a-1]; + } + if ( a < 99 ) { + fb = (float)mTableOfContents[a]; + } else { + fb = 256.0f; + } + fx = fa + (fb-fa)*(percent-a); + } + + *pos = (int)((1.0f/256.0f)*fx*mSizeBytes) + mFirstFramePos; + + return true; +} + +static bool parse_xing_header( + const sp<DataSource> &source, off_t first_frame_pos, + int32_t *frame_number, int32_t *byte_number, + char *table_of_contents, int32_t *quality_indicator, + int64_t *duration) { + if (frame_number) { + *frame_number = 0; + } + if (byte_number) { + *byte_number = 0; + } + if (table_of_contents) { + table_of_contents[0] = 0; + } + if (quality_indicator) { + *quality_indicator = 0; + } + if (duration) { + *duration = 0; + } + + uint8_t buffer[4]; + int offset = first_frame_pos; + if (source->readAt(offset, &buffer, 4) < 4) { // get header + return false; + } + offset += 4; + + uint8_t id, layer, sr_index, mode; + layer = (buffer[1] >> 1) & 3; + id = (buffer[1] >> 3) & 3; + sr_index = (buffer[2] >> 2) & 3; + mode = (buffer[3] >> 6) & 3; + if (layer == 0) { + return false; + } + if (id == 1) { + return false; + } + if (sr_index == 3) { + return false; + } + // determine offset of XING header + if(id&1) { // mpeg1 + if (mode != 3) offset += 32; + else offset += 17; + } else { // mpeg2 + if (mode != 3) offset += 17; + else offset += 9; + } + + if (source->readAt(offset, &buffer, 4) < 4) { // XING header ID + return false; + } + offset += 4; + // Check XING ID + if ((buffer[0] != 'X') || (buffer[1] != 'i') + || (buffer[2] != 'n') || (buffer[3] != 'g')) { + if ((buffer[0] != 'I') || (buffer[1] != 'n') + || (buffer[2] != 'f') || (buffer[3] != 'o')) { + return false; + } + } + + if (source->readAt(offset, &buffer, 4) < 4) { // flags + return false; + } + offset += 4; + uint32_t flags = U32_AT(buffer); + + if (flags & 0x0001) { // Frames field is present + if (source->readAt(offset, buffer, 4) < 4) { + return false; + } + if (frame_number) { + *frame_number = U32_AT(buffer); + } + int32_t frame = U32_AT(buffer); + // Samples per Frame: 1. index = MPEG Version ID, 2. index = Layer + const int samplesPerFrames[2][3] = + { + { 384, 1152, 576 }, // MPEG 2, 2.5: layer1, layer2, layer3 + { 384, 1152, 1152 }, // MPEG 1: layer1, layer2, layer3 + }; + // sampling rates in hertz: 1. index = MPEG Version ID, 2. index = sampling rate index + const int samplingRates[4][3] = + { + { 11025, 12000, 8000, }, // MPEG 2.5 + { 0, 0, 0, }, // reserved + { 22050, 24000, 16000, }, // MPEG 2 + { 44100, 48000, 32000, } // MPEG 1 + }; + if (duration) { + *duration = (int64_t)frame * samplesPerFrames[id&1][3-layer] * 1000000LL + / samplingRates[id][sr_index]; + } + offset += 4; + } + if (flags & 0x0002) { // Bytes field is present + if (byte_number) { + if (source->readAt(offset, buffer, 4) < 4) { + return false; + } + *byte_number = U32_AT(buffer); + } + offset += 4; + } + if (flags & 0x0004) { // TOC field is present + if (table_of_contents) { + if (source->readAt(offset + 1, table_of_contents, 99) < 99) { + return false; + } + } + offset += 100; + } + if (flags & 0x0008) { // Quality indicator field is present + if (quality_indicator) { + if (source->readAt(offset, buffer, 4) < 4) { + return false; + } + *quality_indicator = U32_AT(buffer); + } + } + return true; +} + +} // namespace android + diff --git a/media/libstagefright/include/MP3Extractor.h b/media/libstagefright/include/MP3Extractor.h index 30136e7d..11ca243 100644 --- a/media/libstagefright/include/MP3Extractor.h +++ b/media/libstagefright/include/MP3Extractor.h @@ -24,6 +24,7 @@ namespace android { struct AMessage; class DataSource; +struct MP3Seeker; class String8; class MP3Extractor : public MediaExtractor { @@ -37,6 +38,11 @@ public: virtual sp<MetaData> getMetaData(); + static bool get_mp3_frame_size( + uint32_t header, size_t *frame_size, + int *out_sampling_rate = NULL, int *out_channels = NULL, + int *out_bitrate = NULL); + private: status_t mInitCheck; @@ -44,8 +50,7 @@ private: off_t mFirstFramePos; sp<MetaData> mMeta; uint32_t mFixedHeader; - int32_t mByteNumber; // total number of bytes in this MP3 - char mTableOfContents[99]; // TOC entries in XING header + sp<MP3Seeker> mSeeker; MP3Extractor(const MP3Extractor &); MP3Extractor &operator=(const MP3Extractor &); diff --git a/media/libstagefright/include/MP3Seeker.h b/media/libstagefright/include/MP3Seeker.h new file mode 100644 index 0000000..190eaed --- /dev/null +++ b/media/libstagefright/include/MP3Seeker.h @@ -0,0 +1,46 @@ +/* + * 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 MP3_SEEKER_H_ + +#define MP3_SEEKER_H_ + +#include <media/stagefright/foundation/ABase.h> +#include <utils/RefBase.h> + +namespace android { + +struct MP3Seeker : public RefBase { + MP3Seeker() {} + + virtual bool getDuration(int64_t *durationUs) = 0; + + // Given a request seek time in "*timeUs", find the byte offset closest + // to that position and return it in "*pos". Update "*timeUs" to reflect + // the actual time that seekpoint represents. + virtual bool getOffsetForTime(int64_t *timeUs, off_t *pos) = 0; + +protected: + virtual ~MP3Seeker() {} + +private: + DISALLOW_EVIL_CONSTRUCTORS(MP3Seeker); +}; + +} // namespace android + +#endif // MP3_SEEKER_H_ + diff --git a/media/libstagefright/include/VBRISeeker.h b/media/libstagefright/include/VBRISeeker.h new file mode 100644 index 0000000..d6bd19d --- /dev/null +++ b/media/libstagefright/include/VBRISeeker.h @@ -0,0 +1,50 @@ +/* + * 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 VBRI_SEEKER_H_ + +#define VBRI_SEEKER_H_ + +#include "include/MP3Seeker.h" + +#include <utils/Vector.h> + +namespace android { + +struct DataSource; + +struct VBRISeeker : public MP3Seeker { + static sp<VBRISeeker> CreateFromSource( + const sp<DataSource> &source, off_t post_id3_pos); + + virtual bool getDuration(int64_t *durationUs); + virtual bool getOffsetForTime(int64_t *timeUs, off_t *pos); + +private: + off_t mBasePos; + int64_t mDurationUs; + Vector<uint32_t> mSegments; + + VBRISeeker(); + + DISALLOW_EVIL_CONSTRUCTORS(VBRISeeker); +}; + +} // namespace android + +#endif // VBRI_SEEKER_H_ + + diff --git a/media/libstagefright/include/XINGSeeker.h b/media/libstagefright/include/XINGSeeker.h new file mode 100644 index 0000000..d4ff4e1 --- /dev/null +++ b/media/libstagefright/include/XINGSeeker.h @@ -0,0 +1,50 @@ +/* + * 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 XING_SEEKER_H_ + +#define XING_SEEKER_H_ + +#include "include/MP3Seeker.h" + +namespace android { + +struct DataSource; + +struct XINGSeeker : public MP3Seeker { + static sp<XINGSeeker> CreateFromSource( + const sp<DataSource> &source, off_t first_frame_pos); + + virtual bool getDuration(int64_t *durationUs); + virtual bool getOffsetForTime(int64_t *timeUs, off_t *pos); + +private: + int64_t mFirstFramePos; + int64_t mDurationUs; + int32_t mSizeBytes; + + // TOC entries in XING header. Skip the first one since it's always 0. + char mTableOfContents[99]; + + XINGSeeker(); + + DISALLOW_EVIL_CONSTRUCTORS(XINGSeeker); +}; + +} // namespace android + +#endif // XING_SEEKER_H_ + |