summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
Diffstat (limited to 'media')
-rw-r--r--media/java/android/media/MediaPlayer.java17
-rw-r--r--media/java/android/media/TimedText.java655
-rwxr-xr-xmedia/java/android/media/videoeditor/VideoEditorImpl.java33
-rw-r--r--media/java/android/mtp/MtpDatabase.java90
-rw-r--r--media/java/android/mtp/MtpServer.java6
-rw-r--r--media/jni/android_mtp_MtpServer.cpp12
-rwxr-xr-xmedia/jni/mediaeditor/VideoEditorClasses.cpp5
-rw-r--r--media/jni/soundpool/SoundPool.cpp1
-rw-r--r--media/libmedia/ToneGenerator.cpp1
-rw-r--r--media/libmediaplayerservice/MetadataRetrieverClient.cpp1
-rw-r--r--media/libstagefright/AACWriter.cpp1
-rw-r--r--media/libstagefright/AMRWriter.cpp1
-rw-r--r--media/libstagefright/MPEG4Extractor.cpp62
-rw-r--r--media/libstagefright/MPEG4Writer.cpp237
-rw-r--r--media/libstagefright/OMXCodec.cpp19
-rw-r--r--media/libstagefright/OggExtractor.cpp5
-rw-r--r--media/libstagefright/SampleTable.cpp12
-rw-r--r--media/libstagefright/TimedEventQueue.cpp1
-rw-r--r--media/libstagefright/codecs/aacdec/SoftAAC.cpp8
-rw-r--r--media/libstagefright/codecs/on2/dec/SoftVPX.cpp18
-rw-r--r--media/libstagefright/httplive/LiveSession.cpp2
-rw-r--r--media/libstagefright/httplive/M3UParser.cpp44
-rw-r--r--media/libstagefright/include/M3UParser.h4
-rw-r--r--media/libstagefright/omx/OMX.cpp1
-rw-r--r--media/libstagefright/omx/OMXMaster.cpp6
-rw-r--r--media/libstagefright/timedtext/Android.mk1
-rw-r--r--media/libstagefright/timedtext/TextDescriptions.cpp385
-rw-r--r--media/libstagefright/timedtext/TextDescriptions.h84
-rw-r--r--media/libstagefright/timedtext/TimedTextPlayer.cpp147
-rw-r--r--media/libstagefright/timedtext/TimedTextPlayer.h6
-rw-r--r--media/mtp/MtpServer.cpp26
-rw-r--r--media/mtp/MtpServer.h5
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, &timestampUs));
////////////////////////////////////////////////////////////////////////////////
@@ -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(&timestampUs);
}
}
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();