diff options
Diffstat (limited to 'media')
32 files changed, 1569 insertions, 327 deletions
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java index 2557730..33312d1 100644 --- a/media/java/android/media/MediaPlayer.java +++ b/media/java/android/media/MediaPlayer.java @@ -1568,7 +1568,14 @@ public class MediaPlayer return; case MEDIA_TIMED_TEXT: if (mOnTimedTextListener != null) { - mOnTimedTextListener.onTimedText(mMediaPlayer, (String)msg.obj); + if (msg.obj == null) { + mOnTimedTextListener.onTimedText(mMediaPlayer, null); + } else { + if (msg.obj instanceof byte[]) { + TimedText text = new TimedText((byte[])(msg.obj)); + mOnTimedTextListener.onTimedText(mMediaPlayer, text); + } + } } return; @@ -1755,14 +1762,14 @@ public class MediaPlayer public interface OnTimedTextListener { /** - * Called to indicate the video size + * Called to indicate an avaliable timed text * * @param mp the MediaPlayer associated with this callback - * @param text the timed text sample which contains the - * text needed to be displayed. + * @param text the timed text sample which contains the text + * needed to be displayed and the display format. * {@hide} */ - public void onTimedText(MediaPlayer mp, String text); + public void onTimedText(MediaPlayer mp, TimedText text); } /** diff --git a/media/java/android/media/TimedText.java b/media/java/android/media/TimedText.java new file mode 100644 index 0000000..a055c8b --- /dev/null +++ b/media/java/android/media/TimedText.java @@ -0,0 +1,655 @@ +/* + * Copyright (C) 2011 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. + */ + +package android.media; + +import android.os.Parcel; +import android.util.Log; +import java.util.HashMap; +import java.util.Set; +import java.util.List; +import java.util.ArrayList; + +/** + * Class to hold the timed text's metadata. + * + * {@hide} + */ +public class TimedText +{ + private static final int FIRST_PUBLIC_KEY = 1; + + // These keys must be in sync with the keys in TextDescription.h + public static final int KEY_DISPLAY_FLAGS = 1; // int + public static final int KEY_STYLE_FLAGS = 2; // int + public static final int KEY_BACKGROUND_COLOR_RGBA = 3; // int + public static final int KEY_HIGHLIGHT_COLOR_RGBA = 4; // int + public static final int KEY_SCROLL_DELAY = 5; // int + public static final int KEY_WRAP_TEXT = 6; // int + public static final int KEY_START_TIME = 7; // int + public static final int KEY_STRUCT_BLINKING_TEXT_LIST = 8; // List<CharPos> + public static final int KEY_STRUCT_FONT_LIST = 9; // List<Font> + public static final int KEY_STRUCT_HIGHLIGHT_LIST = 10; // List<CharPos> + public static final int KEY_STRUCT_HYPER_TEXT_LIST = 11; // List<HyperText> + public static final int KEY_STRUCT_KARAOKE_LIST = 12; // List<Karaoke> + public static final int KEY_STRUCT_STYLE_LIST = 13; // List<Style> + public static final int KEY_STRUCT_TEXT_POS = 14; // TextPos + public static final int KEY_STRUCT_JUSTIFICATION = 15; // Justification + public static final int KEY_STRUCT_TEXT = 16; // Text + + private static final int LAST_PUBLIC_KEY = 16; + + private static final int FIRST_PRIVATE_KEY = 101; + + // The following keys are used between TimedText.java and + // TextDescription.cpp in order to parce the Parcel. + private static final int KEY_GLOBAL_SETTING = 101; + private static final int KEY_LOCAL_SETTING = 102; + private static final int KEY_START_CHAR = 103; + private static final int KEY_END_CHAR = 104; + private static final int KEY_FONT_ID = 105; + private static final int KEY_FONT_SIZE = 106; + private static final int KEY_TEXT_COLOR_RGBA = 107; + + private static final int LAST_PRIVATE_KEY = 107; + + private static final String TAG = "TimedText"; + + private Parcel mParcel = Parcel.obtain(); + private final HashMap<Integer, Object> mKeyObjectMap = + new HashMap<Integer, Object>(); + + private int mDisplayFlags = -1; + private int mBackgroundColorRGBA = -1; + private int mHighlightColorRGBA = -1; + private int mScrollDelay = -1; + private int mWrapText = -1; + + private List<CharPos> mBlinkingPosList = null; + private List<CharPos> mHighlightPosList = null; + private List<Karaoke> mKaraokeList = null; + private List<Font> mFontList = null; + private List<Style> mStyleList = null; + private List<HyperText> mHyperTextList = null; + + private TextPos mTextPos; + private Justification mJustification; + private Text mTextStruct; + + /** + * Helper class to hold the text length and text content of + * one text sample. The member variables in this class are + * read-only. + */ + public class Text { + /** + * The byte-count of this text sample + */ + public int textLen; + + /** + * The text sample + */ + public byte[] text; + + public Text() { } + } + + /** + * Helper class to hold the start char offset and end char offset + * for Blinking Text or Highlight Text. endChar is the end offset + * of the text (startChar + number of characters to be highlighted + * or blinked). The member variables in this class are read-only. + */ + public class CharPos { + /** + * The offset of the start character + */ + public int startChar = -1; + + /** + * The offset of the end character + */ + public int endChar = -1; + + public CharPos() { } + } + + /** + * Helper class to hold the box position to display the text sample. + * The member variables in this class are read-only. + */ + public class TextPos { + /** + * The top position of the text + */ + public int top = -1; + + /** + * The left position of the text + */ + public int left = -1; + + /** + * The bottom position of the text + */ + public int bottom = -1; + + /** + * The right position of the text + */ + public int right = -1; + + public TextPos() { } + } + + /** + * Helper class to hold the justification for text display in the text box. + * The member variables in this class are read-only. + */ + public class Justification { + /** + * horizontalJustification 0: left, 1: centered, -1: right + */ + public int horizontalJustification = -1; + + /** + * verticalJustification 0: top, 1: centered, -1: bottom + */ + public int verticalJustification = -1; + + public Justification() { } + } + + /** + * Helper class to hold the style information to display the text. + * The member variables in this class are read-only. + */ + public class Style { + /** + * The offset of the start character which applys this style + */ + public int startChar = -1; + + /** + * The offset of the end character which applys this style + */ + public int endChar = -1; + + /** + * ID of the font. This ID will be used to choose the font + * to be used from the font list. + */ + public int fontID = -1; + + /** + * True if the characters should be bold + */ + public boolean isBold = false; + + /** + * True if the characters should be italic + */ + public boolean isItalic = false; + + /** + * True if the characters should be underlined + */ + public boolean isUnderlined = false; + + /** + * The size of the font + */ + public int fontSize = -1; + + /** + * To specify the RGBA color: 8 bits each of red, green, blue, + * and an alpha(transparency) value + */ + public int colorRGBA = -1; + + public Style() { } + } + + /** + * Helper class to hold the font ID and name. + * The member variables in this class are read-only. + */ + public class Font { + /** + * The font ID + */ + public int ID = -1; + + /** + * The font name + */ + public String name; + + public Font() { } + } + + /** + * Helper class to hold the karaoke information. + * The member variables in this class are read-only. + */ + public class Karaoke { + /** + * The start time (in milliseconds) to highlight the characters + * specified by startChar and endChar. + */ + public int startTimeMs = -1; + + /** + * The end time (in milliseconds) to highlight the characters + * specified by startChar and endChar. + */ + public int endTimeMs = -1; + + /** + * The offset of the start character to be highlighted + */ + public int startChar = -1; + + /** + * The offset of the end character to be highlighted + */ + public int endChar = -1; + + public Karaoke() { } + } + + /** + * Helper class to hold the hyper text information. + * The member variables in this class are read-only. + */ + public class HyperText { + /** + * The offset of the start character + */ + public int startChar = -1; + + /** + * The offset of the end character + */ + public int endChar = -1; + + /** + * The linked-to URL + */ + public String URL; + + /** + * The "alt" string for user display + */ + public String altString; + + public HyperText() { } + } + + /** + * @param obj the byte array which contains the timed text. + * @throws IllegalArgumentExcept if parseParcel() fails. + * {@hide} + */ + public TimedText(byte[] obj) { + mParcel.unmarshall(obj, 0, obj.length); + + if (!parseParcel()) { + mKeyObjectMap.clear(); + throw new IllegalArgumentException("parseParcel() fails"); + } + } + + /** + * Go over all the records, collecting metadata keys and fields in the + * Parcel. These are stored in mKeyObjectMap for application to retrieve. + * @return false if an error occurred during parsing. Otherwise, true. + */ + private boolean parseParcel() { + mParcel.setDataPosition(0); + if (mParcel.dataAvail() == 0) { + return false; + } + + int type = mParcel.readInt(); + if (type == KEY_LOCAL_SETTING) { + type = mParcel.readInt(); + if (type != KEY_START_TIME) { + return false; + } + int mStartTimeMs = mParcel.readInt(); + mKeyObjectMap.put(type, mStartTimeMs); + + type = mParcel.readInt(); + if (type != KEY_STRUCT_TEXT) { + return false; + } + + mTextStruct = new Text(); + mTextStruct.textLen = mParcel.readInt(); + + mTextStruct.text = mParcel.createByteArray(); + mKeyObjectMap.put(type, mTextStruct); + + } else if (type != KEY_GLOBAL_SETTING) { + Log.w(TAG, "Invalid timed text key found: " + type); + return false; + } + + while (mParcel.dataAvail() > 0) { + int key = mParcel.readInt(); + if (!isValidKey(key)) { + Log.w(TAG, "Invalid timed text key found: " + key); + return false; + } + + Object object = null; + + switch (key) { + case KEY_STRUCT_STYLE_LIST: { + readStyle(); + object = mStyleList; + break; + } + case KEY_STRUCT_FONT_LIST: { + readFont(); + object = mFontList; + break; + } + case KEY_STRUCT_HIGHLIGHT_LIST: { + readHighlight(); + object = mHighlightPosList; + break; + } + case KEY_STRUCT_KARAOKE_LIST: { + readKaraoke(); + object = mKaraokeList; + break; + } + case KEY_STRUCT_HYPER_TEXT_LIST: { + readHyperText(); + object = mHyperTextList; + + break; + } + case KEY_STRUCT_BLINKING_TEXT_LIST: { + readBlinkingText(); + object = mBlinkingPosList; + + break; + } + case KEY_WRAP_TEXT: { + mWrapText = mParcel.readInt(); + object = mWrapText; + break; + } + case KEY_HIGHLIGHT_COLOR_RGBA: { + mHighlightColorRGBA = mParcel.readInt(); + object = mHighlightColorRGBA; + break; + } + case KEY_DISPLAY_FLAGS: { + mDisplayFlags = mParcel.readInt(); + object = mDisplayFlags; + break; + } + case KEY_STRUCT_JUSTIFICATION: { + mJustification = new Justification(); + + mJustification.horizontalJustification = mParcel.readInt(); + mJustification.verticalJustification = mParcel.readInt(); + + object = mJustification; + break; + } + case KEY_BACKGROUND_COLOR_RGBA: { + mBackgroundColorRGBA = mParcel.readInt(); + object = mBackgroundColorRGBA; + break; + } + case KEY_STRUCT_TEXT_POS: { + mTextPos = new TextPos(); + + mTextPos.top = mParcel.readInt(); + mTextPos.left = mParcel.readInt(); + mTextPos.bottom = mParcel.readInt(); + mTextPos.right = mParcel.readInt(); + + object = mTextPos; + break; + } + case KEY_SCROLL_DELAY: { + mScrollDelay = mParcel.readInt(); + object = mScrollDelay; + break; + } + default: { + break; + } + } + + if (object != null) { + if (mKeyObjectMap.containsKey(key)) { + mKeyObjectMap.remove(key); + } + mKeyObjectMap.put(key, object); + } + } + + mParcel.recycle(); + return true; + } + + /** + * To parse and store the Style list. + */ + private void readStyle() { + Style style = new Style(); + boolean endOfStyle = false; + + while (!endOfStyle && (mParcel.dataAvail() > 0)) { + int key = mParcel.readInt(); + switch (key) { + case KEY_START_CHAR: { + style.startChar = mParcel.readInt(); + break; + } + case KEY_END_CHAR: { + style.endChar = mParcel.readInt(); + break; + } + case KEY_FONT_ID: { + style.fontID = mParcel.readInt(); + break; + } + case KEY_STYLE_FLAGS: { + int flags = mParcel.readInt(); + // In the absence of any bits set in flags, the text + // is plain. Otherwise, 1: bold, 2: italic, 4: underline + style.isBold = ((flags % 2) == 1); + style.isItalic = ((flags % 4) >= 2); + style.isUnderlined = ((flags / 4) == 1); + break; + } + case KEY_FONT_SIZE: { + style.fontSize = mParcel.readInt(); + break; + } + case KEY_TEXT_COLOR_RGBA: { + style.colorRGBA = mParcel.readInt(); + break; + } + default: { + // End of the Style parsing. Reset the data position back + // to the position before the last mParcel.readInt() call. + mParcel.setDataPosition(mParcel.dataPosition() - 4); + endOfStyle = true; + break; + } + } + } + + if (mStyleList == null) { + mStyleList = new ArrayList<Style>(); + } + mStyleList.add(style); + } + + /** + * To parse and store the Font list + */ + private void readFont() { + int entryCount = mParcel.readInt(); + + for (int i = 0; i < entryCount; i++) { + Font font = new Font(); + + font.ID = mParcel.readInt(); + int nameLen = mParcel.readInt(); + + byte[] text = mParcel.createByteArray(); + font.name = new String(text, 0, nameLen); + + if (mFontList == null) { + mFontList = new ArrayList<Font>(); + } + mFontList.add(font); + } + } + + /** + * To parse and store the Highlight list + */ + private void readHighlight() { + CharPos pos = new CharPos(); + + pos.startChar = mParcel.readInt(); + pos.endChar = mParcel.readInt(); + + if (mHighlightPosList == null) { + mHighlightPosList = new ArrayList<CharPos>(); + } + mHighlightPosList.add(pos); + } + + /** + * To parse and store the Karaoke list + */ + private void readKaraoke() { + int entryCount = mParcel.readInt(); + + for (int i = 0; i < entryCount; i++) { + Karaoke kara = new Karaoke(); + + kara.startTimeMs = mParcel.readInt(); + kara.endTimeMs = mParcel.readInt(); + kara.startChar = mParcel.readInt(); + kara.endChar = mParcel.readInt(); + + if (mKaraokeList == null) { + mKaraokeList = new ArrayList<Karaoke>(); + } + mKaraokeList.add(kara); + } + } + + /** + * To parse and store HyperText list + */ + private void readHyperText() { + HyperText hyperText = new HyperText(); + + hyperText.startChar = mParcel.readInt(); + hyperText.endChar = mParcel.readInt(); + + int len = mParcel.readInt(); + byte[] url = mParcel.createByteArray(); + hyperText.URL = new String(url, 0, len); + + len = mParcel.readInt(); + byte[] alt = mParcel.createByteArray(); + hyperText.altString = new String(alt, 0, len); + + if (mHyperTextList == null) { + mHyperTextList = new ArrayList<HyperText>(); + } + mHyperTextList.add(hyperText); + } + + /** + * To parse and store blinking text list + */ + private void readBlinkingText() { + CharPos blinkingPos = new CharPos(); + + blinkingPos.startChar = mParcel.readInt(); + blinkingPos.endChar = mParcel.readInt(); + + if (mBlinkingPosList == null) { + mBlinkingPosList = new ArrayList<CharPos>(); + } + mBlinkingPosList.add(blinkingPos); + } + + /** + * To check whether the given key is valid. + * @param key the key to be checked. + * @return true if the key is a valid one. Otherwise, false. + */ + public boolean isValidKey(final int key) { + if (!((key >= FIRST_PUBLIC_KEY) && (key <= LAST_PUBLIC_KEY)) + && !((key >= FIRST_PRIVATE_KEY) && (key <= LAST_PRIVATE_KEY))) { + return false; + } + return true; + } + + /** + * To check whether the given key is contained in this TimedText object. + * @param key the key to be checked. + * @return true if the key is contained in this TimedText object. + * Otherwise, false. + */ + public boolean containsKey(final int key) { + if (isValidKey(key) && mKeyObjectMap.containsKey(key)) { + return true; + } + return false; + } + /** + * @return a set of the keys contained in this TimedText object. + */ + public Set keySet() { + return mKeyObjectMap.keySet(); + } + + /** + * To retrieve the object associated with the key. Caller must make sure + * the key is present using the containsKey method otherwise a + * RuntimeException will occur. + * @param key the key used to retrieve the object. + * @return an object. The object could be an instanceof Integer, List, or + * any of the helper classes such as TextPos, Justification, and Text. + */ + public Object getObject(final int key) { + if (containsKey(key)) { + return mKeyObjectMap.get(key); + } else { + throw new IllegalArgumentException("Invalid key: " + key); + } + } +} diff --git a/media/java/android/media/videoeditor/VideoEditorImpl.java b/media/java/android/media/videoeditor/VideoEditorImpl.java index 2105deb..649b98a 100755 --- a/media/java/android/media/videoeditor/VideoEditorImpl.java +++ b/media/java/android/media/videoeditor/VideoEditorImpl.java @@ -38,6 +38,7 @@ import android.graphics.Bitmap; import android.graphics.Rect; import android.media.videoeditor.MediaImageItem; import android.media.videoeditor.MediaItem; +import android.media.MediaMetadataRetriever; import android.util.Log; import android.util.Xml; import android.view.Surface; @@ -1833,12 +1834,32 @@ public class VideoEditorImpl implements VideoEditor { } Bitmap projectBitmap = null; - try { - projectBitmap = mI.getThumbnail(width, height, 500); - } catch (IllegalArgumentException e) { - throw new IllegalArgumentException ("Illegal argument error creating project thumbnail"); - } catch (IOException e) { - throw new IllegalArgumentException ("IO Error creating project thumbnail"); + String filename = mI.getFilename(); + if (mI instanceof MediaVideoItem) { + MediaMetadataRetriever retriever = new MediaMetadataRetriever(); + retriever.setDataSource(filename); + Bitmap bitmap = retriever.getFrameAtTime(); + retriever.release(); + retriever = null; + if (bitmap == null) { + String msg = "Thumbnail extraction from " + + filename + " failed"; + throw new IllegalArgumentException(msg); + } + // Resize the thumbnail to the target size + projectBitmap = + Bitmap.createScaledBitmap(bitmap, width, height, true); + } else { + try { + projectBitmap = mI.getThumbnail(width, height, 500); + } catch (IllegalArgumentException e) { + String msg = "Project thumbnail extraction from " + + filename + " failed"; + throw new IllegalArgumentException(msg); + } catch (IOException e) { + String msg = "IO Error creating project thumbnail"; + throw new IllegalArgumentException(msg); + } } try { diff --git a/media/java/android/mtp/MtpDatabase.java b/media/java/android/mtp/MtpDatabase.java index c9e0f6f..4e271c7 100644 --- a/media/java/android/mtp/MtpDatabase.java +++ b/media/java/android/mtp/MtpDatabase.java @@ -92,13 +92,18 @@ public class MtpDatabase { }; private static final String ID_WHERE = Files.FileColumns._ID + "=?"; private static final String PATH_WHERE = Files.FileColumns.DATA + "=?"; - private static final String PARENT_WHERE = Files.FileColumns.PARENT + "=?"; - private static final String PARENT_FORMAT_WHERE = PARENT_WHERE + " AND " - + Files.FileColumns.FORMAT + "=?"; - private static final String PARENT_STORAGE_WHERE = PARENT_WHERE + " AND " - + Files.FileColumns.STORAGE_ID + "=?"; - private static final String PARENT_STORAGE_FORMAT_WHERE = PARENT_STORAGE_WHERE + " AND " + + private static final String STORAGE_WHERE = Files.FileColumns.STORAGE_ID + "=?"; + private static final String FORMAT_WHERE = Files.FileColumns.PARENT + "=?"; + private static final String PARENT_WHERE = Files.FileColumns.FORMAT + "=?"; + private static final String STORAGE_FORMAT_WHERE = STORAGE_WHERE + " AND " + Files.FileColumns.FORMAT + "=?"; + private static final String STORAGE_PARENT_WHERE = STORAGE_WHERE + " AND " + + Files.FileColumns.PARENT + "=?"; + private static final String FORMAT_PARENT_WHERE = FORMAT_WHERE + " AND " + + Files.FileColumns.PARENT + "=?"; + private static final String STORAGE_FORMAT_PARENT_WHERE = STORAGE_FORMAT_WHERE + " AND " + + Files.FileColumns.PARENT + "=?"; private final MediaScanner mMediaScanner; @@ -249,26 +254,67 @@ public class MtpDatabase { } private Cursor createObjectQuery(int storageID, int format, int parent) throws RemoteException { - if (storageID != 0) { - if (format != 0) { - return mMediaProvider.query(mObjectsUri, ID_PROJECTION, - PARENT_STORAGE_FORMAT_WHERE, - new String[] { Integer.toString(parent), Integer.toString(storageID), - Integer.toString(format) }, null); + if (storageID == 0xFFFFFFFF) { + // query all stores + if (format == 0) { + // query all formats + if (parent == 0) { + // query all objects + return mMediaProvider.query(mObjectsUri, ID_PROJECTION, null, null, null); + } + if (parent == 0xFFFFFFFF) { + // all objects in root of store + parent = 0; + } + return mMediaProvider.query(mObjectsUri, ID_PROJECTION, PARENT_WHERE, + new String[] { Integer.toString(parent) }, null); } else { - return mMediaProvider.query(mObjectsUri, ID_PROJECTION, - PARENT_STORAGE_WHERE, new String[] - { Integer.toString(parent), Integer.toString(storageID) }, null); + // query specific format + if (parent == 0) { + // query all objects + return mMediaProvider.query(mObjectsUri, ID_PROJECTION, FORMAT_WHERE, + new String[] { Integer.toString(format) }, null); + } + if (parent == 0xFFFFFFFF) { + // all objects in root of store + parent = 0; + } + return mMediaProvider.query(mObjectsUri, ID_PROJECTION, FORMAT_PARENT_WHERE, + new String[] { Integer.toString(format), Integer.toString(parent) }, null); } } else { - if (format != 0) { - return mMediaProvider.query(mObjectsUri, ID_PROJECTION, - PARENT_FORMAT_WHERE, - new String[] { Integer.toString(parent), Integer.toString(format) }, - null); + // query specific store + if (format == 0) { + // query all formats + if (parent == 0) { + // query all objects + return mMediaProvider.query(mObjectsUri, ID_PROJECTION, STORAGE_WHERE, + new String[] { Integer.toString(storageID) }, null); + } + if (parent == 0xFFFFFFFF) { + // all objects in root of store + parent = 0; + } + return mMediaProvider.query(mObjectsUri, ID_PROJECTION, STORAGE_PARENT_WHERE, + new String[] { Integer.toString(storageID), Integer.toString(parent) }, + null); } else { - return mMediaProvider.query(mObjectsUri, ID_PROJECTION, - PARENT_WHERE, new String[] { Integer.toString(parent) }, null); + // query specific format + if (parent == 0) { + // query all objects + return mMediaProvider.query(mObjectsUri, ID_PROJECTION, STORAGE_FORMAT_WHERE, + new String[] { Integer.toString(storageID), Integer.toString(format) }, + null); + } + if (parent == 0xFFFFFFFF) { + // all objects in root of store + parent = 0; + } + return mMediaProvider.query(mObjectsUri, ID_PROJECTION, STORAGE_FORMAT_PARENT_WHERE, + new String[] { Integer.toString(storageID), + Integer.toString(format), + Integer.toString(parent) }, + null); } } } diff --git a/media/java/android/mtp/MtpServer.java b/media/java/android/mtp/MtpServer.java index 687cc44..0133cf6 100644 --- a/media/java/android/mtp/MtpServer.java +++ b/media/java/android/mtp/MtpServer.java @@ -33,8 +33,8 @@ public class MtpServer { System.loadLibrary("media_jni"); } - public MtpServer(MtpDatabase database) { - native_setup(database); + public MtpServer(MtpDatabase database, boolean usePtp) { + native_setup(database, usePtp); } public void start() { @@ -69,7 +69,7 @@ public class MtpServer { native_remove_storage(storage.getStorageId()); } - private native final void native_setup(MtpDatabase database); + private native final void native_setup(MtpDatabase database, boolean usePtp); private native final void native_start(); private native final void native_stop(); private native final void native_send_object_added(int handle); diff --git a/media/jni/android_mtp_MtpServer.cpp b/media/jni/android_mtp_MtpServer.cpp index e36e6db..aaf85c3 100644 --- a/media/jni/android_mtp_MtpServer.cpp +++ b/media/jni/android_mtp_MtpServer.cpp @@ -68,13 +68,15 @@ static bool ExceptionCheck(void* env) class MtpThread : public Thread { private: MtpDatabase* mDatabase; + bool mPtp; MtpServer* mServer; MtpStorageList mStorageList; int mFd; public: - MtpThread(MtpDatabase* database) + MtpThread(MtpDatabase* database, bool usePtp) : mDatabase(database), + mPtp(usePtp), mServer(NULL), mFd(-1) { @@ -113,7 +115,7 @@ public: mFd = open("/dev/mtp_usb", O_RDWR); if (mFd >= 0) { - mServer = new MtpServer(mFd, mDatabase, AID_MEDIA_RW, 0664, 0775); + mServer = new MtpServer(mFd, mDatabase, mPtp, AID_MEDIA_RW, 0664, 0775); for (size_t i = 0; i < mStorageList.size(); i++) { mServer->addStorage(mStorageList[i]); } @@ -156,11 +158,11 @@ static sp<MtpThread> sThread; #endif // HAVE_ANDROID_OS static void -android_mtp_MtpServer_setup(JNIEnv *env, jobject thiz, jobject javaDatabase) +android_mtp_MtpServer_setup(JNIEnv *env, jobject thiz, jobject javaDatabase, jboolean usePtp) { #ifdef HAVE_ANDROID_OS // create the thread and assign it to the smart pointer - sThread = new MtpThread(getMtpDatabase(env, javaDatabase)); + sThread = new MtpThread(getMtpDatabase(env, javaDatabase), usePtp); #endif } @@ -263,7 +265,7 @@ android_mtp_MtpServer_remove_storage(JNIEnv *env, jobject thiz, jint storageId) // ---------------------------------------------------------------------------- static JNINativeMethod gMethods[] = { - {"native_setup", "(Landroid/mtp/MtpDatabase;)V", + {"native_setup", "(Landroid/mtp/MtpDatabase;Z)V", (void *)android_mtp_MtpServer_setup}, {"native_start", "()V", (void *)android_mtp_MtpServer_start}, {"native_stop", "()V", (void *)android_mtp_MtpServer_stop}, diff --git a/media/jni/mediaeditor/VideoEditorClasses.cpp b/media/jni/mediaeditor/VideoEditorClasses.cpp index 5696433..d43a562 100755 --- a/media/jni/mediaeditor/VideoEditorClasses.cpp +++ b/media/jni/mediaeditor/VideoEditorClasses.cpp @@ -144,6 +144,7 @@ VIDEOEDIT_JAVA_DEFINE_CONSTANTS(ClipType) VIDEOEDIT_JAVA_CONSTANT_INIT("MP3", M4VIDEOEDITING_kFileType_MP3), VIDEOEDIT_JAVA_CONSTANT_INIT("PCM", M4VIDEOEDITING_kFileType_PCM), VIDEOEDIT_JAVA_CONSTANT_INIT("JPG", M4VIDEOEDITING_kFileType_JPG), + VIDEOEDIT_JAVA_CONSTANT_INIT("PNG", M4VIDEOEDITING_kFileType_PNG), VIDEOEDIT_JAVA_CONSTANT_INIT("M4V", M4VIDEOEDITING_kFileType_M4V), VIDEOEDIT_JAVA_CONSTANT_INIT("UNSUPPORTED", M4VIDEOEDITING_kFileType_Unsupported) }; @@ -1394,8 +1395,8 @@ videoEditClasses_getClipSettings( pSettings->FileType = (M4VIDEOEDITING_FileType)videoEditJava_getClipTypeJavaToC( &converted, pEnv->GetIntField(object, fieldIds.fileType)); - if ( pSettings->FileType == M4VIDEOEDITING_kFileType_JPG) - { + if (( pSettings->FileType == M4VIDEOEDITING_kFileType_JPG) || + ( pSettings->FileType == M4VIDEOEDITING_kFileType_PNG)) { pSettings->FileType = M4VIDEOEDITING_kFileType_ARGB8888; } diff --git a/media/jni/soundpool/SoundPool.cpp b/media/jni/soundpool/SoundPool.cpp index 3ea13a6..4ffb2c0 100644 --- a/media/jni/soundpool/SoundPool.cpp +++ b/media/jni/soundpool/SoundPool.cpp @@ -23,7 +23,6 @@ // XXX needed for timing latency #include <utils/Timers.h> -#include <sys/resource.h> #include <media/AudioTrack.h> #include <media/mediaplayer.h> diff --git a/media/libmedia/ToneGenerator.cpp b/media/libmedia/ToneGenerator.cpp index 9f1b3d6..7c2200e 100644 --- a/media/libmedia/ToneGenerator.cpp +++ b/media/libmedia/ToneGenerator.cpp @@ -21,7 +21,6 @@ #include <stdio.h> #include <math.h> #include <utils/Log.h> -#include <sys/resource.h> #include <utils/RefBase.h> #include <utils/Timers.h> #include <cutils/properties.h> diff --git a/media/libmediaplayerservice/MetadataRetrieverClient.cpp b/media/libmediaplayerservice/MetadataRetrieverClient.cpp index 06fb103..d574ea3 100644 --- a/media/libmediaplayerservice/MetadataRetrieverClient.cpp +++ b/media/libmediaplayerservice/MetadataRetrieverClient.cpp @@ -21,7 +21,6 @@ #include <sys/types.h> #include <sys/stat.h> -#include <sys/resource.h> #include <dirent.h> #include <unistd.h> diff --git a/media/libstagefright/AACWriter.cpp b/media/libstagefright/AACWriter.cpp index 8413208..d133e91 100644 --- a/media/libstagefright/AACWriter.cpp +++ b/media/libstagefright/AACWriter.cpp @@ -27,7 +27,6 @@ #include <media/stagefright/MetaData.h> #include <media/mediarecorder.h> #include <sys/prctl.h> -#include <sys/resource.h> #include <fcntl.h> namespace android { diff --git a/media/libstagefright/AMRWriter.cpp b/media/libstagefright/AMRWriter.cpp index b10d52c..6436071 100644 --- a/media/libstagefright/AMRWriter.cpp +++ b/media/libstagefright/AMRWriter.cpp @@ -23,7 +23,6 @@ #include <media/stagefright/MetaData.h> #include <media/mediarecorder.h> #include <sys/prctl.h> -#include <sys/resource.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> diff --git a/media/libstagefright/MPEG4Extractor.cpp b/media/libstagefright/MPEG4Extractor.cpp index 5fe511f..5582f92 100644 --- a/media/libstagefright/MPEG4Extractor.cpp +++ b/media/libstagefright/MPEG4Extractor.cpp @@ -889,11 +889,17 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) { uint32_t entry_count = U32_AT(&buffer[4]); if (entry_count > 1) { - // For now we only support a single type of media per track. - - mLastTrack->skipTrack = true; - *offset += chunk_size; - break; + // For 3GPP timed text, there could be multiple tx3g boxes contain + // multiple text display formats. These formats will be used to + // display the timed text. + const char *mime; + CHECK(mLastTrack->meta->findCString(kKeyMIMEType, &mime)); + if (strcasecmp(mime, MEDIA_MIMETYPE_TEXT_3GPP)) { + // For now we only support a single type of media per track. + mLastTrack->skipTrack = true; + *offset += chunk_size; + break; + } } off64_t stop_offset = *offset + chunk_size; @@ -1324,9 +1330,53 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) { return parseDrmSINF(offset, data_offset); } + case FOURCC('h', 'd', 'l', 'r'): + { + uint32_t buffer; + if (mDataSource->readAt( + data_offset + 8, &buffer, 4) < 4) { + return ERROR_IO; + } + + uint32_t type = ntohl(buffer); + // For the 3GPP file format, the handler-type within the 'hdlr' box + // shall be 'text' + if (type == FOURCC('t', 'e', 'x', 't')) { + mLastTrack->meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_TEXT_3GPP); + } + + *offset += chunk_size; + break; + } + case FOURCC('t', 'x', '3', 'g'): { - mLastTrack->meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_TEXT_3GPP); + uint32_t type; + const void *data; + size_t size = 0; + if (!mLastTrack->meta->findData( + kKeyTextFormatData, &type, &data, &size)) { + size = 0; + } + + uint8_t *buffer = new uint8_t[size + chunk_size]; + + if (size > 0) { + memcpy(buffer, data, size); + } + + if ((size_t)(mDataSource->readAt(*offset, buffer + size, chunk_size)) + < chunk_size) { + delete[] buffer; + buffer = NULL; + + return ERROR_IO; + } + + mLastTrack->meta->setData( + kKeyTextFormatData, 0, buffer, size + chunk_size); + + delete[] buffer; *offset += chunk_size; break; diff --git a/media/libstagefright/MPEG4Writer.cpp b/media/libstagefright/MPEG4Writer.cpp index 28add18..f075699 100644 --- a/media/libstagefright/MPEG4Writer.cpp +++ b/media/libstagefright/MPEG4Writer.cpp @@ -22,7 +22,6 @@ #include <pthread.h> #include <sys/prctl.h> -#include <sys/resource.h> #include <media/stagefright/MPEG4Writer.h> #include <media/stagefright/MediaBuffer.h> @@ -48,10 +47,6 @@ static const uint8_t kNalUnitTypeSeqParamSet = 0x07; static const uint8_t kNalUnitTypePicParamSet = 0x08; static const int64_t kInitialDelayTimeUs = 700000LL; -// Using longer adjustment period to suppress fluctuations in -// the audio encoding paths -static const int64_t kVideoMediaTimeAdjustPeriodTimeUs = 600000000LL; // 10 minutes - class MPEG4Writer::Track { public: Track(MPEG4Writer *owner, const sp<MediaSource> &source, size_t trackId); @@ -89,8 +84,6 @@ private: int64_t mTrackDurationUs; int64_t mMaxChunkDurationUs; - // For realtime applications, we need to adjust the media clock - // for video track based on the audio media clock bool mIsRealTimeRecording; int64_t mMaxTimeStampUs; int64_t mEstimatedTrackSizeBytes; @@ -176,28 +169,9 @@ private: int64_t mPreviousTrackTimeUs; int64_t mTrackEveryTimeDurationUs; - // Has the media time adjustment for video started? - bool mIsMediaTimeAdjustmentOn; - // The time stamp when previous media time adjustment period starts - int64_t mPrevMediaTimeAdjustTimestampUs; - // Number of vidoe frames whose time stamp may be adjusted - int64_t mMediaTimeAdjustNumFrames; - // The sample number when previous meida time adjustmnet period starts - int64_t mPrevMediaTimeAdjustSample; - // The total accumulated drift time within a period of - // kVideoMediaTimeAdjustPeriodTimeUs. - int64_t mTotalDriftTimeToAdjustUs; - // The total accumalated drift time since the start of the recording - // excluding the current time adjustment period - int64_t mPrevTotalAccumDriftTimeUs; - // Update the audio track's drift information. void updateDriftTime(const sp<MetaData>& meta); - // Adjust the time stamp of the video track according to - // the drift time information from the audio track. - void adjustMediaTime(int64_t *timestampUs); - static void *ThreadWrapper(void *me); status_t threadEntry(); @@ -231,6 +205,8 @@ private: // Duration is time scale based void addOneSttsTableEntry(size_t sampleCount, int32_t timescaledDur); void addOneCttsTableEntry(size_t sampleCount, int32_t timescaledDur); + + bool isTrackMalFormed() const; void sendTrackSummary(bool hasMultipleTracks); // Write the boxes @@ -1513,12 +1489,7 @@ status_t MPEG4Writer::Track::start(MetaData *params) { mNumSttsTableEntries = 0; mNumCttsTableEntries = 0; mMdatSizeBytes = 0; - mIsMediaTimeAdjustmentOn = false; - mPrevMediaTimeAdjustTimestampUs = 0; - mMediaTimeAdjustNumFrames = 0; - mPrevMediaTimeAdjustSample = 0; - mTotalDriftTimeToAdjustUs = 0; - mPrevTotalAccumDriftTimeUs = 0; + mMaxChunkDurationUs = 0; mHasNegativeCttsDeltaDuration = false; @@ -1817,128 +1788,6 @@ status_t MPEG4Writer::Track::makeAVCCodecSpecificData( } /* -* The video track's media time adjustment for real-time applications -* is described as follows: -* -* First, the media time adjustment is done for every period of -* kVideoMediaTimeAdjustPeriodTimeUs. kVideoMediaTimeAdjustPeriodTimeUs -* is currently a fixed value chosen heuristically. The value of -* kVideoMediaTimeAdjustPeriodTimeUs should not be very large or very small -* for two considerations: on one hand, a relatively large value -* helps reduce large fluctuation of drift time in the audio encoding -* path; while on the other hand, a relatively small value helps keep -* restoring synchronization in audio/video more frequently. Note for the -* very first period of kVideoMediaTimeAdjustPeriodTimeUs, there is -* no media time adjustment for the video track. -* -* Second, the total accumulated audio track time drift found -* in a period of kVideoMediaTimeAdjustPeriodTimeUs is distributed -* over a stream of incoming video frames. The number of video frames -* affected is determined based on the number of recorded video frames -* within the past kVideoMediaTimeAdjustPeriodTimeUs period. -* We choose to distribute the drift time over only a portion -* (rather than all) of the total number of recorded video frames -* in order to make sure that the video track media time adjustment is -* completed for the current period before the next video track media -* time adjustment period starts. Currently, the portion chosen is a -* half (0.5). -* -* Last, various additional checks are performed to ensure that -* the actual audio encoding path does not have too much drift. -* In particular, 1) we want to limit the average incremental time -* adjustment for each video frame to be less than a threshold -* for a single period of kVideoMediaTimeAdjustPeriodTimeUs. -* Currently, the threshold is set to 5 ms. If the average incremental -* media time adjustment for a video frame is larger than the -* threshold, the audio encoding path has too much time drift. -* 2) We also want to limit the total time drift in the audio -* encoding path to be less than a threshold for a period of -* kVideoMediaTimeAdjustPeriodTimeUs. Currently, the threshold -* is 0.5% of kVideoMediaTimeAdjustPeriodTimeUs. If the time drift of -* the audio encoding path is larger than the threshold, the audio -* encoding path has too much time drift. We treat the large time -* drift of the audio encoding path as errors, since there is no -* way to keep audio/video in synchronization for real-time -* applications if the time drift is too large unless we drop some -* video frames, which has its own problems that we don't want -* to get into for the time being. -*/ -void MPEG4Writer::Track::adjustMediaTime(int64_t *timestampUs) { - if (*timestampUs - mPrevMediaTimeAdjustTimestampUs >= - kVideoMediaTimeAdjustPeriodTimeUs) { - - LOGV("New media time adjustment period at %lld us", *timestampUs); - mIsMediaTimeAdjustmentOn = true; - mMediaTimeAdjustNumFrames = - (mNumSamples - mPrevMediaTimeAdjustSample) >> 1; - - mPrevMediaTimeAdjustTimestampUs = *timestampUs; - mPrevMediaTimeAdjustSample = mNumSamples; - int64_t totalAccumDriftTimeUs = mOwner->getDriftTimeUs(); - mTotalDriftTimeToAdjustUs = - totalAccumDriftTimeUs - mPrevTotalAccumDriftTimeUs; - - mPrevTotalAccumDriftTimeUs = totalAccumDriftTimeUs; - - // Check on incremental adjusted time per frame - int64_t adjustTimePerFrameUs = - mTotalDriftTimeToAdjustUs / mMediaTimeAdjustNumFrames; - - if (adjustTimePerFrameUs < 0) { - adjustTimePerFrameUs = -adjustTimePerFrameUs; - } - if (adjustTimePerFrameUs >= 5000) { - LOGE("Adjusted time per video frame is %lld us", - adjustTimePerFrameUs); - CHECK(!"Video frame time adjustment is too large!"); - } - - // Check on total accumulated time drift within a period of - // kVideoMediaTimeAdjustPeriodTimeUs. - int64_t driftPercentage = (mTotalDriftTimeToAdjustUs * 1000) - / kVideoMediaTimeAdjustPeriodTimeUs; - - if (driftPercentage < 0) { - driftPercentage = -driftPercentage; - } - if (driftPercentage > 5) { - LOGE("Audio track has time drift %lld us over %lld us", - mTotalDriftTimeToAdjustUs, - kVideoMediaTimeAdjustPeriodTimeUs); - - CHECK(!"The audio track media time drifts too much!"); - } - - } - - if (mIsMediaTimeAdjustmentOn) { - if (mNumSamples - mPrevMediaTimeAdjustSample <= - mMediaTimeAdjustNumFrames) { - - // Do media time incremental adjustment - int64_t incrementalAdjustTimeUs = - (mTotalDriftTimeToAdjustUs * - (mNumSamples - mPrevMediaTimeAdjustSample)) - / mMediaTimeAdjustNumFrames; - - *timestampUs += - (incrementalAdjustTimeUs + mPrevTotalAccumDriftTimeUs); - - LOGV("Incremental video frame media time adjustment: %lld us", - (incrementalAdjustTimeUs + mPrevTotalAccumDriftTimeUs)); - } else { - // Within the remaining adjustment period, - // no incremental adjustment is needed. - *timestampUs += - (mTotalDriftTimeToAdjustUs + mPrevTotalAccumDriftTimeUs); - - LOGV("Fixed video frame media time adjustment: %lld us", - (mTotalDriftTimeToAdjustUs + mPrevTotalAccumDriftTimeUs)); - } - } -} - -/* * Updates the drift time from the audio track so that * the video track can get the updated drift time information * from the file writer. The fluctuation of the drift time of the audio @@ -1975,7 +1824,17 @@ status_t MPEG4Writer::Track::threadEntry() { int64_t previousPausedDurationUs = 0; int64_t timestampUs = 0; int64_t cttsDeltaTimeUs = 0; + bool hasBFrames = false; +#if 1 + // XXX: Samsung's video encoder's output buffer timestamp + // is not correct. see bug 4724339 + char value[PROPERTY_VALUE_MAX]; + if (property_get("rw.media.record.hasb", value, NULL) && + (!strcasecmp(value, "true") || !strcasecmp(value, "1"))) { + hasBFrames = true; + } +#endif if (mIsAudio) { prctl(PR_SET_NAME, (unsigned long)"AudioTrackEncoding", 0, 0, 0); } else { @@ -2071,32 +1930,6 @@ status_t MPEG4Writer::Track::threadEntry() { int32_t isSync = false; meta_data->findInt32(kKeyIsSyncFrame, &isSync); - - /* - * The original timestamp found in the data buffer will be modified as below: - * - * There is a playback offset into this track if the track's start time - * is not the same as the movie start time, which will be recorded in edst - * box of the output file. The playback offset is to make sure that the - * starting time of the audio/video tracks are synchronized. Although the - * track's media timestamp may be subject to various modifications - * as outlined below, the track's playback offset time remains unchanged - * once the first data buffer of the track is received. - * - * The media time stamp will be calculated by subtracting the playback offset - * (and potential pause durations) from the original timestamp in the buffer. - * - * If this track is a video track for a real-time recording application with - * both audio and video tracks, its media timestamp will subject to further - * modification based on the media clock of the audio track. This modification - * is needed for the purpose of maintaining good audio/video synchronization. - * - * If the recording session is paused and resumed multiple times, the track - * media timestamp will be modified as if the recording session had never been - * paused at all during playback of the recorded output file. In other words, - * the output file will have no memory of pause/resume durations. - * - */ CHECK(meta_data->findInt64(kKeyTime, ×tampUs)); //////////////////////////////////////////////////////////////////////////////// @@ -2118,7 +1951,7 @@ status_t MPEG4Writer::Track::threadEntry() { timestampUs -= previousPausedDurationUs; CHECK(timestampUs >= 0); - if (!mIsAudio) { + if (!mIsAudio && hasBFrames) { /* * Composition time: timestampUs * Decoding time: decodingTimeUs @@ -2137,32 +1970,13 @@ status_t MPEG4Writer::Track::threadEntry() { timestampUs, cttsDeltaTimeUs); } - // Media time adjustment for real-time applications if (mIsRealTimeRecording) { if (mIsAudio) { updateDriftTime(meta_data); - } else { - adjustMediaTime(×tampUs); } } CHECK(timestampUs >= 0); - if (mNumSamples > 1) { - if (timestampUs <= lastTimestampUs) { - LOGW("Frame arrives too late!"); - // Don't drop the late frame, since dropping a frame may cause - // problems later during playback - - // The idea here is to avoid having two or more samples with the - // same timestamp in the output file. - if (mTimeScale >= 1000000LL) { - timestampUs = lastTimestampUs + 1; - } else { - timestampUs = lastTimestampUs + (1000000LL + (mTimeScale >> 1)) / mTimeScale; - } - } - } - LOGV("%s media time stamp: %lld and previous paused duration %lld", mIsAudio? "Audio": "Video", timestampUs, previousPausedDurationUs); if (timestampUs > mTrackDurationUs) { @@ -2269,11 +2083,10 @@ status_t MPEG4Writer::Track::threadEntry() { } - if (mSampleSizes.empty() || // no samples written - (!mIsAudio && mNumStssTableEntries == 0) || // no sync frames for video - (OK != checkCodecSpecificData())) { // no codec specific data + if (isTrackMalFormed()) { err = ERROR_MALFORMED; } + mOwner->trackProgressStatus(mTrackId, -1, err); // Last chunk @@ -2323,6 +2136,24 @@ status_t MPEG4Writer::Track::threadEntry() { return err; } +bool MPEG4Writer::Track::isTrackMalFormed() const { + if (mSampleSizes.empty()) { // no samples written + LOGE("The number of recorded samples is 0"); + return true; + } + + if (!mIsAudio && mNumStssTableEntries == 0) { // no sync frames for video + LOGE("There are no sync frames for video track"); + return true; + } + + if (OK != checkCodecSpecificData()) { // no codec specific data + return true; + } + + return false; +} + void MPEG4Writer::Track::sendTrackSummary(bool hasMultipleTracks) { // Send track summary only if test mode is enabled. diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp index c4fcc79..bb8a8be 100644 --- a/media/libstagefright/OMXCodec.cpp +++ b/media/libstagefright/OMXCodec.cpp @@ -1983,7 +1983,14 @@ OMXCodec::BufferInfo* OMXCodec::dequeueBufferFromNativeWindow() { int64_t OMXCodec::retrieveDecodingTimeUs(bool isCodecSpecific) { CHECK(mIsEncoder); - CHECK(!mDecodingTimeList.empty()); + + if (mDecodingTimeList.empty()) { + CHECK(mNoMoreOutputData); + // No corresponding input frame available. + // This could happen when EOS is reached. + return 0; + } + List<int64_t>::iterator it = mDecodingTimeList.begin(); int64_t timeUs = *it; @@ -2152,11 +2159,6 @@ void OMXCodec::on_message(const omx_message &msg) { buffer->meta_data()->setInt32(kKeyIsUnreadable, true); } - if (mIsEncoder) { - int64_t decodingTimeUs = retrieveDecodingTimeUs(isCodecSpecific); - buffer->meta_data()->setInt64(kKeyDecodingTime, decodingTimeUs); - } - buffer->meta_data()->setPointer( kKeyPlatformPrivate, msg.u.extended_buffer_data.platform_private); @@ -2170,6 +2172,11 @@ void OMXCodec::on_message(const omx_message &msg) { mNoMoreOutputData = true; } + if (mIsEncoder) { + int64_t decodingTimeUs = retrieveDecodingTimeUs(isCodecSpecific); + buffer->meta_data()->setInt64(kKeyDecodingTime, decodingTimeUs); + } + if (mTargetTimeUs >= 0) { CHECK(msg.u.extended_buffer_data.timestamp <= mTargetTimeUs); diff --git a/media/libstagefright/OggExtractor.cpp b/media/libstagefright/OggExtractor.cpp index 1560b8e..29e6907 100644 --- a/media/libstagefright/OggExtractor.cpp +++ b/media/libstagefright/OggExtractor.cpp @@ -730,8 +730,9 @@ status_t MyVorbisExtractor::verifyHeader( off64_t size; if (mSource->getSize(&size) == OK) { uint64_t bps = approxBitrate(); - - mMeta->setInt64(kKeyDuration, size * 8000000ll / bps); + if (bps != 0) { + mMeta->setInt64(kKeyDuration, size * 8000000ll / bps); + } } break; } diff --git a/media/libstagefright/SampleTable.cpp b/media/libstagefright/SampleTable.cpp index eb135ab..a8a094e 100644 --- a/media/libstagefright/SampleTable.cpp +++ b/media/libstagefright/SampleTable.cpp @@ -416,12 +416,16 @@ void SampleTable::buildSampleEntriesTable() { uint32_t delta = mTimeToSample[2 * i + 1]; for (uint32_t j = 0; j < n; ++j) { - CHECK(sampleIndex < mNumSampleSizes); + if (sampleIndex < mNumSampleSizes) { + // Technically this should always be the case if the file + // is well-formed, but you know... there's (gasp) malformed + // content out there. - mSampleTimeEntries[sampleIndex].mSampleIndex = sampleIndex; + mSampleTimeEntries[sampleIndex].mSampleIndex = sampleIndex; - mSampleTimeEntries[sampleIndex].mCompositionTime = - sampleTime + getCompositionTimeOffset(sampleIndex); + mSampleTimeEntries[sampleIndex].mCompositionTime = + sampleTime + getCompositionTimeOffset(sampleIndex); + } ++sampleIndex; sampleTime += delta; diff --git a/media/libstagefright/TimedEventQueue.cpp b/media/libstagefright/TimedEventQueue.cpp index a08eb7b..100d8a3 100644 --- a/media/libstagefright/TimedEventQueue.cpp +++ b/media/libstagefright/TimedEventQueue.cpp @@ -30,7 +30,6 @@ #include <sys/prctl.h> #include <sys/time.h> -#include <sys/resource.h> #include <media/stagefright/MediaDebug.h> diff --git a/media/libstagefright/codecs/aacdec/SoftAAC.cpp b/media/libstagefright/codecs/aacdec/SoftAAC.cpp index 7ce6128..bbd6dbb 100644 --- a/media/libstagefright/codecs/aacdec/SoftAAC.cpp +++ b/media/libstagefright/codecs/aacdec/SoftAAC.cpp @@ -367,7 +367,15 @@ void SoftAAC::onQueueFilled(OMX_U32 portIndex) { inHeader->nFilledLen -= mConfig->inputBufferUsedLength; inHeader->nOffset += mConfig->inputBufferUsedLength; } else { + LOGW("AAC decoder returned error %d, substituting silence", + decoderErr); + memset(outHeader->pBuffer + outHeader->nOffset, 0, numOutBytes); + + // Discard input buffer. + inHeader->nFilledLen = 0; + + // fall through } if (mUpsamplingFactor == 2) { diff --git a/media/libstagefright/codecs/on2/dec/SoftVPX.cpp b/media/libstagefright/codecs/on2/dec/SoftVPX.cpp index e9ce719..7e83163 100644 --- a/media/libstagefright/codecs/on2/dec/SoftVPX.cpp +++ b/media/libstagefright/codecs/on2/dec/SoftVPX.cpp @@ -117,11 +117,27 @@ void SoftVPX::initPorts() { addPort(def); } +static int GetCPUCoreCount() { + int cpuCoreCount = 1; +#if defined(_SC_NPROCESSORS_ONLN) + cpuCoreCount = sysconf(_SC_NPROCESSORS_ONLN); +#else + // _SC_NPROC_ONLN must be defined... + cpuCoreCount = sysconf(_SC_NPROC_ONLN); +#endif + CHECK(cpuCoreCount >= 1); + LOGV("Number of CPU cores: %d", cpuCoreCount); + return cpuCoreCount; +} + status_t SoftVPX::initDecoder() { mCtx = new vpx_codec_ctx_t; vpx_codec_err_t vpx_err; + vpx_codec_dec_cfg_t cfg; + memset(&cfg, 0, sizeof(vpx_codec_dec_cfg_t)); + cfg.threads = GetCPUCoreCount(); if ((vpx_err = vpx_codec_dec_init( - (vpx_codec_ctx_t *)mCtx, &vpx_codec_vp8_dx_algo, NULL, 0))) { + (vpx_codec_ctx_t *)mCtx, &vpx_codec_vp8_dx_algo, &cfg, 0))) { LOGE("on2 decoder failed to initialize. (%d)", vpx_err); return UNKNOWN_ERROR; } diff --git a/media/libstagefright/httplive/LiveSession.cpp b/media/libstagefright/httplive/LiveSession.cpp index 012d9ad..165683e 100644 --- a/media/libstagefright/httplive/LiveSession.cpp +++ b/media/libstagefright/httplive/LiveSession.cpp @@ -296,6 +296,8 @@ sp<M3UParser> LiveSession::fetchPlaylist(const char *url) { new M3UParser(url, buffer->data(), buffer->size()); if (playlist->initCheck() != OK) { + LOGE("failed to parse .m3u8 playlist"); + return NULL; } diff --git a/media/libstagefright/httplive/M3UParser.cpp b/media/libstagefright/httplive/M3UParser.cpp index 2eb180a..765f795 100644 --- a/media/libstagefright/httplive/M3UParser.cpp +++ b/media/libstagefright/httplive/M3UParser.cpp @@ -179,7 +179,7 @@ status_t M3UParser::parse(const void *_data, size_t size) { if (mIsVariantPlaylist) { return ERROR_MALFORMED; } - err = parseMetaData(line, &itemMeta, "duration"); + err = parseMetaDataDuration(line, &itemMeta, "durationUs"); } else if (line.startsWith("#EXT-X-DISCONTINUITY")) { if (mIsVariantPlaylist) { return ERROR_MALFORMED; @@ -203,9 +203,9 @@ status_t M3UParser::parse(const void *_data, size_t size) { if (!line.startsWith("#")) { if (!mIsVariantPlaylist) { - int32_t durationSecs; + int64_t durationUs; if (itemMeta == NULL - || !itemMeta->findInt32("duration", &durationSecs)) { + || !itemMeta->findInt64("durationUs", &durationUs)) { return ERROR_MALFORMED; } } @@ -252,6 +252,30 @@ status_t M3UParser::parseMetaData( } // static +status_t M3UParser::parseMetaDataDuration( + const AString &line, sp<AMessage> *meta, const char *key) { + ssize_t colonPos = line.find(":"); + + if (colonPos < 0) { + return ERROR_MALFORMED; + } + + double x; + status_t err = ParseDouble(line.c_str() + colonPos + 1, &x); + + if (err != OK) { + return err; + } + + if (meta->get() == NULL) { + *meta = new AMessage; + } + (*meta)->setInt64(key, (int64_t)x * 1E6); + + return OK; +} + +// static status_t M3UParser::parseStreamInf( const AString &line, sp<AMessage> *meta) { ssize_t colonPos = line.find(":"); @@ -412,4 +436,18 @@ status_t M3UParser::ParseInt32(const char *s, int32_t *x) { return OK; } +// static +status_t M3UParser::ParseDouble(const char *s, double *x) { + char *end; + double dval = strtod(s, &end); + + if (end == s || (*end != '\0' && *end != ',')) { + return ERROR_MALFORMED; + } + + *x = dval; + + return OK; +} + } // namespace android diff --git a/media/libstagefright/include/M3UParser.h b/media/libstagefright/include/M3UParser.h index 63895b4..478582d 100644 --- a/media/libstagefright/include/M3UParser.h +++ b/media/libstagefright/include/M3UParser.h @@ -63,6 +63,9 @@ private: static status_t parseMetaData( const AString &line, sp<AMessage> *meta, const char *key); + static status_t parseMetaDataDuration( + const AString &line, sp<AMessage> *meta, const char *key); + static status_t parseStreamInf( const AString &line, sp<AMessage> *meta); @@ -70,6 +73,7 @@ private: const AString &line, sp<AMessage> *meta, const AString &baseURI); static status_t ParseInt32(const char *s, int32_t *x); + static status_t ParseDouble(const char *s, double *x); DISALLOW_EVIL_CONSTRUCTORS(M3UParser); }; diff --git a/media/libstagefright/omx/OMX.cpp b/media/libstagefright/omx/OMX.cpp index 14968e8..d23aa3a 100644 --- a/media/libstagefright/omx/OMX.cpp +++ b/media/libstagefright/omx/OMX.cpp @@ -21,7 +21,6 @@ #include <dlfcn.h> #include <sys/prctl.h> -#include <sys/resource.h> #include "../include/OMX.h" diff --git a/media/libstagefright/omx/OMXMaster.cpp b/media/libstagefright/omx/OMXMaster.cpp index 545e6d4..504d470 100644 --- a/media/libstagefright/omx/OMXMaster.cpp +++ b/media/libstagefright/omx/OMXMaster.cpp @@ -86,7 +86,11 @@ void OMXMaster::addPlugin(OMXPluginBase *plugin) { mPluginByComponentName.add(name8, plugin); } - CHECK_EQ(err, OMX_ErrorNoMore); + + if (err != OMX_ErrorNoMore) { + LOGE("OMX plugin failed w/ error 0x%08x after registering %d " + "components", err, mPluginByComponentName.size()); + } } void OMXMaster::clearPlugins() { diff --git a/media/libstagefright/timedtext/Android.mk b/media/libstagefright/timedtext/Android.mk index 9a6062c..59d0e15 100644 --- a/media/libstagefright/timedtext/Android.mk +++ b/media/libstagefright/timedtext/Android.mk @@ -2,6 +2,7 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ + TextDescriptions.cpp \ TimedTextParser.cpp \ TimedTextPlayer.cpp diff --git a/media/libstagefright/timedtext/TextDescriptions.cpp b/media/libstagefright/timedtext/TextDescriptions.cpp new file mode 100644 index 0000000..f9c1fe0 --- /dev/null +++ b/media/libstagefright/timedtext/TextDescriptions.cpp @@ -0,0 +1,385 @@ +/* + * Copyright (C) 2011 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 "TextDescriptions.h" +#include <media/stagefright/Utils.h> +#include <media/stagefright/MediaErrors.h> + +namespace android { + +TextDescriptions::TextDescriptions() { +} + +status_t TextDescriptions::getParcelOfDescriptions( + const uint8_t *data, ssize_t size, + uint32_t flags, int timeMs, Parcel *parcel) { + parcel->freeData(); + + if (flags & IN_BAND_TEXT_3GPP) { + if (flags & GLOBAL_DESCRIPTIONS) { + return extract3GPPGlobalDescriptions(data, size, parcel, 0); + } else if (flags & LOCAL_DESCRIPTIONS) { + return extract3GPPLocalDescriptions(data, size, timeMs, parcel, 0); + } + } else if (flags & OUT_OF_BAND_TEXT_SRT) { + if (flags & LOCAL_DESCRIPTIONS) { + return extractSRTLocalDescriptions(data, size, timeMs, parcel); + } + } + + return ERROR_UNSUPPORTED; +} + +// Parse the SRT text sample, and store the timing and text sample in a Parcel. +// The Parcel will be sent to MediaPlayer.java through event, and will be +// parsed in TimedText.java. +status_t TextDescriptions::extractSRTLocalDescriptions( + const uint8_t *data, ssize_t size, int timeMs, Parcel *parcel) { + parcel->writeInt32(KEY_LOCAL_SETTING); + parcel->writeInt32(KEY_START_TIME); + parcel->writeInt32(timeMs); + + parcel->writeInt32(KEY_STRUCT_TEXT); + // write the size of the text sample + parcel->writeInt32(size); + // write the text sample as a byte array + parcel->writeInt32(size); + parcel->write(data, size); + + return OK; +} + +// Extract the local 3GPP display descriptions. 3GPP local descriptions +// are appended to the text sample if any. The descriptions could include +// information such as text styles, highlights, karaoke and so on. They +// are contained in different boxes, such as 'styl' box contains text +// styles, and 'krok' box contains karaoke timing and positions. +status_t TextDescriptions::extract3GPPLocalDescriptions( + const uint8_t *data, ssize_t size, + int timeMs, Parcel *parcel, int depth) { + if (depth == 0) { + parcel->writeInt32(KEY_LOCAL_SETTING); + + // write start time to display this text sample + parcel->writeInt32(KEY_START_TIME); + parcel->writeInt32(timeMs); + + ssize_t textLen = (*data) << 8 | (*(data + 1)); + + // write text sample length and text sample itself + parcel->writeInt32(KEY_STRUCT_TEXT); + parcel->writeInt32(textLen); + parcel->writeInt32(textLen); + parcel->write(data + 2, textLen); + + if (size > textLen) { + data += (textLen + 2); + size -= (textLen + 2); + } else { + return OK; + } + } + + const uint8_t *tmpData = data; + ssize_t chunkSize = U32_AT(tmpData); + uint32_t chunkType = U32_AT(tmpData + 4); + + if (chunkSize <= 0) { + return OK; + } + + tmpData += 8; + + switch(chunkType) { + // 'styl' box specifies the style of the text. + case FOURCC('s', 't', 'y', 'l'): + { + uint16_t count = U16_AT(tmpData); + + tmpData += 2; + + for (int i = 0; i < count; i++) { + parcel->writeInt32(KEY_STRUCT_STYLE_LIST); + parcel->writeInt32(KEY_START_CHAR); + parcel->writeInt32(U16_AT(tmpData)); + + parcel->writeInt32(KEY_END_CHAR); + parcel->writeInt32(U16_AT(tmpData + 2)); + + parcel->writeInt32(KEY_FONT_ID); + parcel->writeInt32(U16_AT(tmpData + 4)); + + parcel->writeInt32(KEY_STYLE_FLAGS); + parcel->writeInt32(*(tmpData + 6)); + + parcel->writeInt32(KEY_FONT_SIZE); + parcel->writeInt32(*(tmpData + 7)); + + parcel->writeInt32(KEY_TEXT_COLOR_RGBA); + uint32_t rgba = *(tmpData + 8) << 24 | *(tmpData + 9) << 16 + | *(tmpData + 10) << 8 | *(tmpData + 11); + parcel->writeInt32(rgba); + + tmpData += 12; + } + + break; + } + // 'krok' box. The number of highlight events is specified, and each + // event is specified by a starting and ending char offset and an end + // time for the event. + case FOURCC('k', 'r', 'o', 'k'): + { + + parcel->writeInt32(KEY_STRUCT_KARAOKE_LIST); + + int startTime = U32_AT(tmpData); + uint16_t count = U16_AT(tmpData + 4); + parcel->writeInt32(count); + + tmpData += 6; + int lastEndTime = 0; + + for (int i = 0; i < count; i++) { + parcel->writeInt32(startTime + lastEndTime); + + lastEndTime = U32_AT(tmpData); + parcel->writeInt32(lastEndTime); + + parcel->writeInt32(U16_AT(tmpData + 4)); + parcel->writeInt32(U16_AT(tmpData + 6)); + + tmpData += 8; + } + + break; + } + // 'hlit' box specifies highlighted text + case FOURCC('h', 'l', 'i', 't'): + { + parcel->writeInt32(KEY_STRUCT_HIGHLIGHT_LIST); + + // the start char offset to highlight + parcel->writeInt32(U16_AT(tmpData)); + // the last char offset to highlight + parcel->writeInt32(U16_AT(tmpData + 2)); + + break; + } + // 'hclr' box specifies the RGBA color: 8 bits each of + // red, green, blue, and an alpha(transparency) value + case FOURCC('h', 'c', 'l', 'r'): + { + parcel->writeInt32(KEY_HIGHLIGHT_COLOR_RGBA); + + uint32_t rgba = *(tmpData) << 24 | *(tmpData + 1) << 16 + | *(tmpData + 2) << 8 | *(tmpData + 3); + parcel->writeInt32(rgba); + + break; + } + // 'dlay' box specifies a delay after a scroll in and/or + // before scroll out. + case FOURCC('d', 'l', 'a', 'y'): + { + parcel->writeInt32(KEY_SCROLL_DELAY); + + uint32_t delay = *(tmpData) << 24 | *(tmpData + 1) << 16 + | *(tmpData + 2) << 8 | *(tmpData + 3); + parcel->writeInt32(delay); + + break; + } + // 'href' box for hyper text link + case FOURCC('h', 'r', 'e', 'f'): + { + parcel->writeInt32(KEY_STRUCT_HYPER_TEXT_LIST); + + // the start offset of the text to be linked + parcel->writeInt32(U16_AT(tmpData)); + // the end offset of the text + parcel->writeInt32(U16_AT(tmpData + 2)); + + // the number of bytes in the following URL + int len = *(tmpData + 4); + parcel->writeInt32(len); + + // the linked-to URL + parcel->writeInt32(len); + parcel->write(tmpData + 5, len); + + tmpData += (5 + len); + + // the number of bytes in the following "alt" string + len = *tmpData; + parcel->writeInt32(len); + + // an "alt" string for user display + parcel->writeInt32(len); + parcel->write(tmpData + 1, len); + + break; + } + // 'tbox' box to indicate the position of the text with values + // of top, left, bottom and right + case FOURCC('t', 'b', 'o', 'x'): + { + parcel->writeInt32(KEY_STRUCT_TEXT_POS); + parcel->writeInt32(U16_AT(tmpData)); + parcel->writeInt32(U16_AT(tmpData + 2)); + parcel->writeInt32(U16_AT(tmpData + 4)); + parcel->writeInt32(U16_AT(tmpData + 6)); + + break; + } + // 'blnk' to specify the char range to be blinked + case FOURCC('b', 'l', 'n', 'k'): + { + parcel->writeInt32(KEY_STRUCT_BLINKING_TEXT_LIST); + + // start char offset + parcel->writeInt32(U16_AT(tmpData)); + // end char offset + parcel->writeInt32(U16_AT(tmpData + 2)); + + break; + } + // 'twrp' box specifies text wrap behavior. If the value if 0x00, + // then no wrap. If it's 0x01, then automatic 'soft' wrap is enabled. + // 0x02-0xff are reserved. + case FOURCC('t', 'w', 'r', 'p'): + { + parcel->writeInt32(KEY_WRAP_TEXT); + parcel->writeInt32(*tmpData); + + break; + } + default: + { + break; + } + } + + if (size > chunkSize) { + data += chunkSize; + size -= chunkSize; + // continue to parse next box + return extract3GPPLocalDescriptions(data, size, 0, parcel, 1); + } + + return OK; +} + +// To extract box 'tx3g' defined in 3GPP TS 26.245, and store it in a Parcel +status_t TextDescriptions::extract3GPPGlobalDescriptions( + const uint8_t *data, ssize_t size, Parcel *parcel, int depth) { + + ssize_t chunkSize = U32_AT(data); + uint32_t chunkType = U32_AT(data + 4); + const uint8_t *tmpData = data; + tmpData += 8; + + if (size < chunkSize) { + return OK; + } + + if (depth == 0) { + parcel->writeInt32(KEY_GLOBAL_SETTING); + } + switch(chunkType) { + case FOURCC('t', 'x', '3', 'g'): + { + tmpData += 8; // skip the first 8 bytes + parcel->writeInt32(KEY_DISPLAY_FLAGS); + parcel->writeInt32(U32_AT(tmpData)); + + parcel->writeInt32(KEY_STRUCT_JUSTIFICATION); + parcel->writeInt32(tmpData[4]); + parcel->writeInt32(tmpData[5]); + + parcel->writeInt32(KEY_BACKGROUND_COLOR_RGBA); + uint32_t rgba = *(tmpData + 6) << 24 | *(tmpData + 7) << 16 + | *(tmpData + 8) << 8 | *(tmpData + 9); + parcel->writeInt32(rgba); + + tmpData += 10; + parcel->writeInt32(KEY_STRUCT_TEXT_POS); + parcel->writeInt32(U16_AT(tmpData)); + parcel->writeInt32(U16_AT(tmpData + 2)); + parcel->writeInt32(U16_AT(tmpData + 4)); + parcel->writeInt32(U16_AT(tmpData + 6)); + + tmpData += 8; + parcel->writeInt32(KEY_STRUCT_STYLE_LIST); + parcel->writeInt32(KEY_START_CHAR); + parcel->writeInt32(U16_AT(tmpData)); + + parcel->writeInt32(KEY_END_CHAR); + parcel->writeInt32(U16_AT(tmpData + 2)); + + parcel->writeInt32(KEY_FONT_ID); + parcel->writeInt32(U16_AT(tmpData + 4)); + + parcel->writeInt32(KEY_STYLE_FLAGS); + parcel->writeInt32(*(tmpData + 6)); + + parcel->writeInt32(KEY_FONT_SIZE); + parcel->writeInt32(*(tmpData + 7)); + + parcel->writeInt32(KEY_TEXT_COLOR_RGBA); + rgba = *(tmpData + 8) << 24 | *(tmpData + 9) << 16 + | *(tmpData + 10) << 8 | *(tmpData + 11); + parcel->writeInt32(rgba); + + tmpData += 12; + parcel->writeInt32(KEY_STRUCT_FONT_LIST); + uint16_t count = U16_AT(tmpData); + parcel->writeInt32(count); + + tmpData += 2; + for (int i = 0; i < count; i++) { + // font ID + parcel->writeInt32(U16_AT(tmpData)); + + // font name length + parcel->writeInt32(*(tmpData + 2)); + + int len = *(tmpData + 2); + + parcel->write(tmpData + 3, len); + tmpData += 3 + len; + } + + break; + } + default: + { + break; + } + } + + data += chunkSize; + size -= chunkSize; + + if (size > 0) { + // continue to extract next 'tx3g' + return extract3GPPGlobalDescriptions(data, size, parcel, 1); + } + + return OK; +} + +} // namespace android diff --git a/media/libstagefright/timedtext/TextDescriptions.h b/media/libstagefright/timedtext/TextDescriptions.h new file mode 100644 index 0000000..0144917 --- /dev/null +++ b/media/libstagefright/timedtext/TextDescriptions.h @@ -0,0 +1,84 @@ + /* + * Copyright (C) 2011 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 TEXT_DESCRIPTIONS_H_ + +#define TEXT_DESCRIPTIONS_H_ + +#include <binder/Parcel.h> +#include <media/stagefright/foundation/ABase.h> + +namespace android { + +class TextDescriptions { +public: + enum { + IN_BAND_TEXT_3GPP = 0x01, + OUT_OF_BAND_TEXT_SRT = 0x02, + + GLOBAL_DESCRIPTIONS = 0x100, + LOCAL_DESCRIPTIONS = 0x200, + }; + + static status_t getParcelOfDescriptions( + const uint8_t *data, ssize_t size, + uint32_t flags, int timeMs, Parcel *parcel); +private: + TextDescriptions(); + + enum { + // These keys must be in sync with the keys in TimedText.java + KEY_DISPLAY_FLAGS = 1, // int + KEY_STYLE_FLAGS = 2, // int + KEY_BACKGROUND_COLOR_RGBA = 3, // int + KEY_HIGHLIGHT_COLOR_RGBA = 4, // int + KEY_SCROLL_DELAY = 5, // int + KEY_WRAP_TEXT = 6, // int + KEY_START_TIME = 7, // int + KEY_STRUCT_BLINKING_TEXT_LIST = 8, // List<CharPos> + KEY_STRUCT_FONT_LIST = 9, // List<Font> + KEY_STRUCT_HIGHLIGHT_LIST = 10, // List<CharPos> + KEY_STRUCT_HYPER_TEXT_LIST = 11, // List<HyperText> + KEY_STRUCT_KARAOKE_LIST = 12, // List<Karaoke> + KEY_STRUCT_STYLE_LIST = 13, // List<Style> + KEY_STRUCT_TEXT_POS = 14, // TextPos + KEY_STRUCT_JUSTIFICATION = 15, // Justification + KEY_STRUCT_TEXT = 16, // Text + + KEY_GLOBAL_SETTING = 101, + KEY_LOCAL_SETTING = 102, + KEY_START_CHAR = 103, + KEY_END_CHAR = 104, + KEY_FONT_ID = 105, + KEY_FONT_SIZE = 106, + KEY_TEXT_COLOR_RGBA = 107, + }; + + static status_t extractSRTLocalDescriptions( + const uint8_t *data, ssize_t size, + int timeMs, Parcel *parcel); + static status_t extract3GPPGlobalDescriptions( + const uint8_t *data, ssize_t size, + Parcel *parcel, int depth); + static status_t extract3GPPLocalDescriptions( + const uint8_t *data, ssize_t size, + int timeMs, Parcel *parcel, int depth); + + DISALLOW_EVIL_CONSTRUCTORS(TextDescriptions); +}; + +} // namespace android +#endif // TEXT_DESCRIPTIONS_H_ diff --git a/media/libstagefright/timedtext/TimedTextPlayer.cpp b/media/libstagefright/timedtext/TimedTextPlayer.cpp index 50bb16d..7c8a747 100644 --- a/media/libstagefright/timedtext/TimedTextPlayer.cpp +++ b/media/libstagefright/timedtext/TimedTextPlayer.cpp @@ -19,6 +19,7 @@ #include <utils/Log.h> #include <binder/IPCThreadState.h> + #include <media/stagefright/MediaDebug.h> #include <media/stagefright/MediaDefs.h> #include <media/stagefright/MediaErrors.h> @@ -27,9 +28,11 @@ #include <media/stagefright/MediaBuffer.h> #include <media/stagefright/FileSource.h> #include <media/stagefright/Utils.h> + #include "include/AwesomePlayer.h" #include "TimedTextPlayer.h" #include "TimedTextParser.h" +#include "TextDescriptions.h" namespace android { @@ -92,10 +95,11 @@ status_t TimedTextPlayer::start(uint8_t index) { return BAD_VALUE; } + status_t err; if (index < mTextTrackVector.size()) { // start an in-band text mSource = mTextTrackVector.itemAt(index); - status_t err = mSource->start(); + err = mSource->start(); if (err != OK) { return err; @@ -112,13 +116,17 @@ status_t TimedTextPlayer::start(uint8_t index) { mTextParser = new TimedTextParser(); } - status_t err; if ((err = mTextParser->init(mOutOfBandSource, fileType)) != OK) { return err; } mTextType = kOutOfBandText; } + // send sample description format + if ((err = extractAndSendGlobalDescriptions()) != OK) { + return err; + } + int64_t positionUs; mObserver->getPosition(&positionUs); seekTo(positionUs); @@ -211,21 +219,17 @@ void TimedTextPlayer::onTextEvent() { } mTextEventPending = false; + if (mData.dataSize() > 0) { + notifyListener(MEDIA_TIMED_TEXT, &mData); + mData.freeData(); + } + MediaSource::ReadOptions options; if (mSeeking) { options.setSeekTo(mSeekTimeUs, MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC); mSeeking = false; - if (mTextType == kInBandText) { - if (mTextBuffer != NULL) { - mTextBuffer->release(); - mTextBuffer = NULL; - } - } else { - mText.clear(); - } - notifyListener(MEDIA_TIMED_TEXT); //empty text to clear the screen } @@ -233,32 +237,12 @@ void TimedTextPlayer::onTextEvent() { mObserver->getPosition(&positionUs); if (mTextType == kInBandText) { - if (mTextBuffer != NULL) { - uint8_t *tmp = (uint8_t *)(mTextBuffer->data()); - size_t len = (*tmp) << 8 | (*(tmp + 1)); - - notifyListener(MEDIA_TIMED_TEXT, - tmp + 2, - len); - - mTextBuffer->release(); - mTextBuffer = NULL; - - } - if (mSource->read(&mTextBuffer, &options) != OK) { return; } mTextBuffer->meta_data()->findInt64(kKeyTime, &timeUs); } else { - if (mText.size() > 0) { - notifyListener(MEDIA_TIMED_TEXT, - mText.c_str(), - mText.size()); - mText.clear(); - } - int64_t endTimeUs; if (mTextParser->getText( &mText, &timeUs, &endTimeUs, &options) != OK) { @@ -266,6 +250,19 @@ void TimedTextPlayer::onTextEvent() { } } + if (timeUs > 0) { + extractAndAppendLocalDescriptions(timeUs); + } + + if (mTextType == kInBandText) { + if (mTextBuffer != NULL) { + mTextBuffer->release(); + mTextBuffer = NULL; + } + } else { + mText.clear(); + } + //send the text now if (timeUs <= positionUs + 100000ll) { postTextEvent(); @@ -297,7 +294,8 @@ status_t TimedTextPlayer::setParameter(int key, const Parcel &request) { Mutex::Autolock autoLock(mLock); if (key == KEY_PARAMETER_TIMED_TEXT_ADD_OUT_OF_BAND_SOURCE) { - String8 uri = request.readString8(); + const String16 uri16 = request.readString16(); + String8 uri = String8(uri16); KeyedVector<String8, String8> headers; // To support local subtitle file only for now @@ -327,21 +325,92 @@ status_t TimedTextPlayer::setParameter(int key, const Parcel &request) { return INVALID_OPERATION; } -void TimedTextPlayer::notifyListener( - int msg, const void *data, size_t size) { +void TimedTextPlayer::notifyListener(int msg, const Parcel *parcel) { if (mListener != NULL) { sp<MediaPlayerBase> listener = mListener.promote(); if (listener != NULL) { - if (size > 0) { - mData.freeData(); - mData.write(data, size); - - listener->sendEvent(msg, 0, 0, &mData); + if (parcel && (parcel->dataSize() > 0)) { + listener->sendEvent(msg, 0, 0, parcel); } else { // send an empty timed text to clear the screen listener->sendEvent(msg); } } } } + +// Each text sample consists of a string of text, optionally with sample +// modifier description. The modifier description could specify a new +// text style for the string of text. These descriptions are present only +// if they are needed. This method is used to extract the modifier +// description and append it at the end of the text. +status_t TimedTextPlayer::extractAndAppendLocalDescriptions(int64_t timeUs) { + const void *data; + size_t size = 0; + int32_t flag = TextDescriptions::LOCAL_DESCRIPTIONS; + + if (mTextType == kInBandText) { + const char *mime; + CHECK(mSource->getFormat()->findCString(kKeyMIMEType, &mime)); + + if (!strcasecmp(mime, MEDIA_MIMETYPE_TEXT_3GPP)) { + flag |= TextDescriptions::IN_BAND_TEXT_3GPP; + data = mTextBuffer->data(); + size = mTextBuffer->size(); + } else { + // support 3GPP only for now + return ERROR_UNSUPPORTED; + } + } else { + data = mText.c_str(); + size = mText.size(); + flag |= TextDescriptions::OUT_OF_BAND_TEXT_SRT; + } + + if ((size > 0) && (flag != TextDescriptions::LOCAL_DESCRIPTIONS)) { + mData.freeData(); + return TextDescriptions::getParcelOfDescriptions( + (const uint8_t *)data, size, flag, timeUs / 1000, &mData); + } + + return OK; +} + +// To extract and send the global text descriptions for all the text samples +// in the text track or text file. +status_t TimedTextPlayer::extractAndSendGlobalDescriptions() { + const void *data; + size_t size = 0; + int32_t flag = TextDescriptions::GLOBAL_DESCRIPTIONS; + + if (mTextType == kInBandText) { + const char *mime; + CHECK(mSource->getFormat()->findCString(kKeyMIMEType, &mime)); + + // support 3GPP only for now + if (!strcasecmp(mime, MEDIA_MIMETYPE_TEXT_3GPP)) { + uint32_t type; + // get the 'tx3g' box content. This box contains the text descriptions + // used to render the text track + if (!mSource->getFormat()->findData( + kKeyTextFormatData, &type, &data, &size)) { + return ERROR_MALFORMED; + } + + flag |= TextDescriptions::IN_BAND_TEXT_3GPP; + } + } + + if ((size > 0) && (flag != TextDescriptions::GLOBAL_DESCRIPTIONS)) { + Parcel parcel; + if (TextDescriptions::getParcelOfDescriptions( + (const uint8_t *)data, size, flag, 0, &parcel) == OK) { + if (parcel.dataSize() > 0) { + notifyListener(MEDIA_TIMED_TEXT, &parcel); + } + } + } + + return OK; +} } diff --git a/media/libstagefright/timedtext/TimedTextPlayer.h b/media/libstagefright/timedtext/TimedTextPlayer.h index 590760b..a744db5 100644 --- a/media/libstagefright/timedtext/TimedTextPlayer.h +++ b/media/libstagefright/timedtext/TimedTextPlayer.h @@ -103,8 +103,10 @@ private: void postTextEvent(int64_t delayUs = -1); void cancelTextEvent(); - void notifyListener( - int msg, const void *data = NULL, size_t size = 0); + void notifyListener(int msg, const Parcel *parcel = NULL); + + status_t extractAndAppendLocalDescriptions(int64_t timeUs); + status_t extractAndSendGlobalDescriptions(); DISALLOW_EVIL_CONSTRUCTORS(TimedTextPlayer); }; diff --git a/media/mtp/MtpServer.cpp b/media/mtp/MtpServer.cpp index 4a8fd3e..bc04e8c 100644 --- a/media/mtp/MtpServer.cpp +++ b/media/mtp/MtpServer.cpp @@ -95,10 +95,11 @@ static const MtpEventCode kSupportedEventCodes[] = { MTP_EVENT_STORE_REMOVED, }; -MtpServer::MtpServer(int fd, MtpDatabase* database, +MtpServer::MtpServer(int fd, MtpDatabase* database, bool ptp, int fileGroup, int filePerm, int directoryPerm) : mFD(fd), mDatabase(database), + mPtp(ptp), mFileGroup(fileGroup), mFilePermission(filePerm), mDirectoryPermission(directoryPerm), @@ -426,9 +427,20 @@ MtpResponseCode MtpServer::doGetDeviceInfo() { // fill in device info mData.putUInt16(MTP_STANDARD_VERSION); - mData.putUInt32(6); // MTP Vendor Extension ID + if (mPtp) { + mData.putUInt32(0); + } else { + // MTP Vendor Extension ID + mData.putUInt32(6); + } mData.putUInt16(MTP_STANDARD_VERSION); - string.set("microsoft.com: 1.0; android.com: 1.0;"); + if (mPtp) { + // no extensions + string.set(""); + } else { + // MTP extensions + string.set("microsoft.com: 1.0; android.com: 1.0;"); + } mData.putString(string); // MTP Extensions mData.putUInt16(0); //Functional Mode mData.putAUInt16(kSupportedOperationCodes, @@ -533,12 +545,10 @@ MtpResponseCode MtpServer::doGetObjectHandles() { MtpStorageID storageID = mRequest.getParameter(1); // 0xFFFFFFFF for all storage MtpObjectFormat format = mRequest.getParameter(2); // 0 for all formats MtpObjectHandle parent = mRequest.getParameter(3); // 0xFFFFFFFF for objects with no parent - // 0x00000000 for all objects? + // 0x00000000 for all objects if (!hasStorage(storageID)) return MTP_RESPONSE_INVALID_STORAGE_ID; - if (parent == 0xFFFFFFFF) - parent = 0; MtpObjectHandleList* handles = mDatabase->getObjectList(storageID, format, parent); mData.putAUInt32(handles); @@ -552,11 +562,9 @@ MtpResponseCode MtpServer::doGetNumObjects() { MtpStorageID storageID = mRequest.getParameter(1); // 0xFFFFFFFF for all storage MtpObjectFormat format = mRequest.getParameter(2); // 0 for all formats MtpObjectHandle parent = mRequest.getParameter(3); // 0xFFFFFFFF for objects with no parent - // 0x00000000 for all objects? + // 0x00000000 for all objects if (!hasStorage(storageID)) return MTP_RESPONSE_INVALID_STORAGE_ID; - if (parent == 0xFFFFFFFF) - parent = 0; int count = mDatabase->getNumObjects(storageID, format, parent); if (count >= 0) { diff --git a/media/mtp/MtpServer.h b/media/mtp/MtpServer.h index 859a18e..dfa8258 100644 --- a/media/mtp/MtpServer.h +++ b/media/mtp/MtpServer.h @@ -39,6 +39,9 @@ private: MtpDatabase* mDatabase; + // appear as a PTP device + bool mPtp; + // group to own new files and folders int mFileGroup; // permissions for new files and directories @@ -87,7 +90,7 @@ private: Vector<ObjectEdit*> mObjectEditList; public: - MtpServer(int fd, MtpDatabase* database, + MtpServer(int fd, MtpDatabase* database, bool ptp, int fileGroup, int filePerm, int directoryPerm); virtual ~MtpServer(); |