Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 019caf42 authored by Gloria Wang's avatar Gloria Wang Committed by Android (Google) Code Review
Browse files

Merge "Timed text display format support: 1. Extract 3GPP global format...

Merge "Timed text display format support: 1. Extract 3GPP global format descriptions 2. Extract 3GPP local format descriptions 3. Define data structure (TimedText) for applications to retrieve the format metadata"
parents 06e87407 eaa5d8f9
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -118,6 +118,9 @@ enum {

    // The language code for this media
    kKeyMediaLanguage     = 'lang',  // cstring

    // To store the timed text format data
    kKeyTextFormatData    = 'text',  // raw data
};

enum {
+12 −5
Original line number Diff line number Diff line
@@ -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);
    }

    /**
+655 −0
Original line number Diff line number Diff line
/*
 * 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);
        }
    }
}
+56 −6
Original line number Diff line number Diff line
@@ -889,12 +889,18 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
            uint32_t entry_count = U32_AT(&buffer[4]);

            if (entry_count > 1) {
                // 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;
            *offset = data_offset + 8;
@@ -1324,9 +1330,53 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
            return parseDrmSINF(offset, data_offset);
        }

        case FOURCC('t', 'x', '3', 'g'):
        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'):
        {
            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;
+1 −0
Original line number Diff line number Diff line
@@ -2,6 +2,7 @@ LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)

LOCAL_SRC_FILES:=                 \
        TextDescriptions.cpp      \
        TimedTextParser.cpp       \
        TimedTextPlayer.cpp

Loading