diff options
| -rw-r--r-- | core/java/android/app/LoadedApk.java | 7 | ||||
| -rw-r--r-- | core/java/android/provider/Calendar.java | 2 | ||||
| -rw-r--r-- | core/res/res/layout/preference.xml | 17 | ||||
| -rw-r--r-- | core/res/res/layout/preference_child.xml | 17 | ||||
| -rw-r--r-- | core/res/res/layout/preference_dialog.xml | 53 | ||||
| -rw-r--r-- | core/res/res/layout/preference_widget_checkbox.xml | 56 | ||||
| -rw-r--r-- | core/res/res/values/styles.xml | 10 | ||||
| -rw-r--r-- | docs/html/sdk/eclipse-adt.jd | 44 | ||||
| -rw-r--r-- | docs/html/sdk/sdk_toc.cs | 5 | ||||
| -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/codecs/m4v_h263/enc/M4vH263Encoder.cpp | 7 | ||||
| -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 |
18 files changed, 700 insertions, 323 deletions
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index 7f24d27..03a98d1 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -35,6 +35,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.Process; import android.os.RemoteException; +import android.os.StrictMode; import android.util.AndroidRuntimeException; import android.util.Slog; @@ -285,10 +286,16 @@ final class LoadedApk { if (ActivityThread.localLOGV) Slog.v(ActivityThread.TAG, "Class path: " + zip + ", JNI path: " + mLibDir); + // Temporarily disable logging of disk reads on the Looper thread + // as this is early and necessary. + StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); + mClassLoader = ApplicationLoaders.getDefault().getClassLoader( zip, mLibDir, mBaseClassLoader); initializeJavaContextClassLoader(); + + StrictMode.setThreadPolicy(oldPolicy); } else { if (mBaseClassLoader == null) { mClassLoader = ClassLoader.getSystemClassLoader(); diff --git a/core/java/android/provider/Calendar.java b/core/java/android/provider/Calendar.java index 88ce0f0..c007605 100644 --- a/core/java/android/provider/Calendar.java +++ b/core/java/android/provider/Calendar.java @@ -861,6 +861,8 @@ public final class Calendar { DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, _SYNC_VERSION); DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, EventsColumns.DELETED); DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, Calendars.SYNC1); + DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, + Events.SYNC_ADAPTER_DATA); Entity entity = new Entity(cv); Cursor subCursor; diff --git a/core/res/res/layout/preference.xml b/core/res/res/layout/preference.xml index ba0e225..46c3e9c 100644 --- a/core/res/res/layout/preference.xml +++ b/core/res/res/layout/preference.xml @@ -24,9 +24,13 @@ android:gravity="center_vertical" android:paddingRight="?android:attr/scrollbarSize"> - <View - android:layout_width="@dimen/preference_widget_width" - android:layout_height="match_parent" /> + <!-- Preference should place its actual preference widget here. --> + <LinearLayout android:id="@+android:id/widget_frame" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:minWidth="@dimen/preference_widget_width" + android:gravity="center" + android:orientation="vertical" /> <RelativeLayout android:layout_width="wrap_content" @@ -54,12 +58,5 @@ android:maxLines="4" /> </RelativeLayout> - - <!-- Preference should place its actual preference widget here. --> - <LinearLayout android:id="@+android:id/widget_frame" - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:gravity="center_vertical" - android:orientation="vertical" /> </LinearLayout> diff --git a/core/res/res/layout/preference_child.xml b/core/res/res/layout/preference_child.xml index d6f1182..713aa17 100644 --- a/core/res/res/layout/preference_child.xml +++ b/core/res/res/layout/preference_child.xml @@ -22,9 +22,13 @@ android:gravity="center_vertical" android:paddingRight="?android:attr/scrollbarSize"> - <View - android:layout_width="@dimen/preference_widget_width" - android:layout_height="match_parent" /> + <!-- Preference should place its actual preference widget here. --> + <LinearLayout android:id="@+android:id/widget_frame" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:minWidth="@dimen/preference_widget_width" + android:gravity="center" + android:orientation="vertical" /> <RelativeLayout android:layout_width="wrap_content" @@ -54,11 +58,4 @@ </RelativeLayout> - <!-- Preference should place its actual preference widget here. --> - <LinearLayout android:id="@+android:id/widget_frame" - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:gravity="center_vertical" - android:orientation="vertical" /> - </LinearLayout> diff --git a/core/res/res/layout/preference_dialog.xml b/core/res/res/layout/preference_dialog.xml index 035b411..9e988ad 100644 --- a/core/res/res/layout/preference_dialog.xml +++ b/core/res/res/layout/preference_dialog.xml @@ -15,52 +15,9 @@ --> <!-- Layout used by DialogPreference in a PreferenceActivity. --> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" +<ImageView xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" android:layout_height="wrap_content" - android:minHeight="?android:attr/listPreferredItemHeight" - android:gravity="center_vertical" - android:paddingRight="?android:attr/scrollbarSize"> - - <!-- Preference should place its actual preference widget here. --> - <LinearLayout android:id="@+android:id/widget_frame" - android:layout_width="@dimen/preference_widget_width" - android:layout_height="match_parent" - android:gravity="center" - android:orientation="horizontal" > - <ImageView xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:background="@drawable/btn_circle" - android:src="@drawable/ic_btn_round_more" /> - </LinearLayout> - - <RelativeLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginRight="6dip" - android:layout_marginTop="6dip" - android:layout_marginBottom="6dip" - android:layout_weight="1"> - - <TextView android:id="@+android:id/title" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:singleLine="true" - android:textAppearance="?android:attr/textAppearanceMedium" - android:ellipsize="marquee" - android:fadingEdge="horizontal" /> - - <TextView android:id="@+android:id/summary" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_below="@android:id/title" - android:layout_alignLeft="@android:id/title" - android:textAppearance="?android:attr/textAppearanceSmall" - android:textColor="?android:attr/textColorSecondary" - android:maxLines="4" /> - - </RelativeLayout> -</LinearLayout> - + android:layout_gravity="center" + android:background="@drawable/btn_circle" + android:src="@drawable/ic_btn_round_more" /> diff --git a/core/res/res/layout/preference_widget_checkbox.xml b/core/res/res/layout/preference_widget_checkbox.xml index 1ca4393..33a43f4 100644 --- a/core/res/res/layout/preference_widget_checkbox.xml +++ b/core/res/res/layout/preference_widget_checkbox.xml @@ -16,54 +16,10 @@ <!-- Layout used by CheckBoxPreference for the checkbox style. This is inflated inside android.R.layout.preference. --> -<!-- Layout used by DialogPreference in a PreferenceActivity. --> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" +<CheckBox xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+android:id/checkbox" + android:layout_width="wrap_content" android:layout_height="wrap_content" - android:minHeight="?android:attr/listPreferredItemHeight" - android:gravity="center_vertical" - android:paddingRight="?android:attr/scrollbarSize"> - - <!-- Preference should place its actual preference widget here. --> - <LinearLayout android:id="@+android:id/widget_frame" - android:layout_width="@dimen/preference_widget_width" - android:layout_height="match_parent" - android:gravity="center" - android:orientation="horizontal" > - <CheckBox xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+android:id/checkbox" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:focusable="false" - android:clickable="false" /> - </LinearLayout> - - <RelativeLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginRight="6dip" - android:layout_marginTop="6dip" - android:layout_marginBottom="6dip" - android:layout_weight="1"> - - <TextView android:id="@+android:id/title" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:singleLine="true" - android:textAppearance="?android:attr/textAppearanceMedium" - android:ellipsize="marquee" - android:fadingEdge="horizontal" /> - - <TextView android:id="@+android:id/summary" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_below="@android:id/title" - android:layout_alignLeft="@android:id/title" - android:textAppearance="?android:attr/textAppearanceSmall" - android:textColor="?android:attr/textColorSecondary" - android:maxLines="4" /> - - </RelativeLayout> -</LinearLayout> - + android:layout_gravity="center" + android:focusable="false" + android:clickable="false" /> diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml index 1fd2565..f63805b 100644 --- a/core/res/res/values/styles.xml +++ b/core/res/res/values/styles.xml @@ -818,18 +818,18 @@ <item name="android:shouldDisableView">false</item> <item name="android:selectable">false</item> </style> - + <style name="Preference.CheckBoxPreference"> - <item name="android:layout">@android:layout/preference_widget_checkbox</item> + <item name="android:widgetLayout">@android:layout/preference_widget_checkbox</item> </style> - + <style name="Preference.PreferenceScreen"> </style> <style name="Preference.DialogPreference"> <item name="android:positiveButtonText">@android:string/ok</item> <item name="android:negativeButtonText">@android:string/cancel</item> - <item name="android:layout">@android:layout/preference_dialog</item> + <item name="android:widgetLayout">@android:layout/preference_dialog</item> </style> <style name="Preference.DialogPreference.YesNoPreference"> @@ -845,7 +845,7 @@ <item name="android:ringtoneType">ringtone</item> <item name="android:showSilent">true</item> <item name="android:showDefault">true</item> - <item name="android:layout">@android:layout/preference_dialog</item> + <item name="android:widgetLayout">@android:layout/preference_dialog</item> </style> <!-- Other Misc Styles --> diff --git a/docs/html/sdk/eclipse-adt.jd b/docs/html/sdk/eclipse-adt.jd index 632fa4b..350f2b0 100644 --- a/docs/html/sdk/eclipse-adt.jd +++ b/docs/html/sdk/eclipse-adt.jd @@ -103,12 +103,54 @@ padding: .25em 1em; <div class="toggleable opened"> <a href="#" onclick="return toggleDiv(this)"> <img src="{@docRoot}assets/images/triangle-opened.png" class="toggle-img" height="9px" width="9px" /> -ADT 0.9.9</a> <em>(September 2010)</em> +ADT 8.0</a> <em>(November 2010)</em> <div class="toggleme"> +<dl> + +<dt>Dependencies:</dt> + +<p><p>ADT 8.0 is designed for use with SDK Tools r8. If you haven't +already installed SDK Tools r8 into your SDK, use the Android SDK and AVD Manager to do +so.</p></dd> +<dt>General notes:</dt> +<dd> +<ul> + <li>New version number scheme that follows the SDK Tools revision number. The major version +number for your ADT plugin should now always match the revision number of your SDK Tools. For +example, ADT 8.x is for SDK Tools r8.</li> + <li>Support for true debug build. You no longer need to change the value of the + <code>debuggable</code> attribute in the Android Manifest. + <p>Incremental builds automatically insert <code>debuggable="true"</code>, but if you perform + "export signed/unsigned application package", ADT does <em>not</em> insert it. + If you manually set <code>debuggable="true"</code> in the manifest file, then release builds will + actually create a debug build (it does not remove it if you placed it there).</p></li> + <li>Automatic <a href="{@docRoot}guide/developing/tools/proguard.html">Proguard</a> support in +release builds. For it to work, you need to have a <code>proguard.config</code> + property in the <code>default.properties</code> file that points to a proguard config file.</li> + <li>Completely rewritten Visual Layout Editor. (This is still a work in progress.) Now includes: + <ul> + <li>Full drag and drop from palette to layout for all Layout classes.</li> + <li>Move widgets inside a Layout view, from one Layout view to another and from one layout file to another.</li> + <li>Contextual menu with enum/flag type properties.</li> + <li>New zoom controls.</li> + </ul></li> + <li>New HierarchyViewer plug-in integrated in Eclipse.</li> + <li>Android launch configurations don't recompile the whole workspace on launch anymore.</li> + <li><code>android.jar</code> source and javadoc location can now be configured.</li> </ul> </dd> +</dl> + </div> +</div> + + +<div class="toggleable closed"> + <a href="#" onclick="return toggleDiv(this)"> + <img src="{@docRoot}assets/images/triangle-closed.png" class="toggle-img" height="9px" width="9px" /> +ADT 0.9.9</a> <em>(September 2010)</em> + <div class="toggleme"> <dl> diff --git a/docs/html/sdk/sdk_toc.cs b/docs/html/sdk/sdk_toc.cs index ecc69c2..7ccb7a0 100644 --- a/docs/html/sdk/sdk_toc.cs +++ b/docs/html/sdk/sdk_toc.cs @@ -93,7 +93,7 @@ <span style="display:none" class="zh-TW"></span> </h2> <ul> - <li><a href="<?cs var:toroot ?>sdk/eclipse-adt.html">ADT 0.9.9 + <li><a href="<?cs var:toroot ?>sdk/eclipse-adt.html">ADT 8.0 <span style="display:none" class="de"></span> <span style="display:none" class="es"></span> <span style="display:none" class="fr"></span> @@ -115,7 +115,8 @@ <span style="display:none" class="zh-TW"></span> </h2> <ul> - <li><a href="<?cs var:toroot ?>sdk/ndk/index.html">Download the Android NDK, r5</a></li> + <li><a href="<?cs var:toroot ?>sdk/ndk/index.html">Download the Android NDK, r5</a> + <span class="new">new!</span></li> <li><a href="<?cs var:toroot ?>sdk/ndk/overview.html">What is the NDK?</a></li> </ul> </li> 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/codecs/m4v_h263/enc/M4vH263Encoder.cpp b/media/libstagefright/codecs/m4v_h263/enc/M4vH263Encoder.cpp index d5a5313..72611cf 100644 --- a/media/libstagefright/codecs/m4v_h263/enc/M4vH263Encoder.cpp +++ b/media/libstagefright/codecs/m4v_h263/enc/M4vH263Encoder.cpp @@ -50,6 +50,7 @@ static status_t ConvertOmxProfileLevel( profileLevel = CORE_PROFILE_LEVEL2; break; } + break; default: LOGE("Unsupported profile (%d) for H263", omxProfile); return BAD_VALUE; @@ -74,7 +75,8 @@ static status_t ConvertOmxProfileLevel( LOGE("Unsupported level (%d) for MPEG4 simple profile", omxLevel); return BAD_VALUE; - } + } + break; case OMX_VIDEO_MPEG4ProfileSimpleScalable: switch (omxLevel) { case OMX_VIDEO_MPEG4Level0b: @@ -91,6 +93,7 @@ static status_t ConvertOmxProfileLevel( "scalable profile", omxLevel); return BAD_VALUE; } + break; case OMX_VIDEO_MPEG4ProfileCore: switch (omxLevel) { case OMX_VIDEO_MPEG4Level1: @@ -104,6 +107,7 @@ static status_t ConvertOmxProfileLevel( "profile", omxLevel); return BAD_VALUE; } + break; case OMX_VIDEO_MPEG4ProfileCoreScalable: switch (omxLevel) { case OMX_VIDEO_MPEG4Level1: @@ -120,6 +124,7 @@ static status_t ConvertOmxProfileLevel( "scalable profile", omxLevel); return BAD_VALUE; } + break; default: LOGE("Unsupported MPEG4 profile (%d)", omxProfile); return BAD_VALUE; 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_ + |
