Support for CEA-708 closed caption

Bug: 24314667
Change-Id: I5e04f7646e40eb8f358666c8b109eccb6617a246
This commit is contained in:
Jaesung Chung
2015-12-07 21:46:40 +09:00
parent 81cdf2b216
commit 978bf5eff0
5 changed files with 2381 additions and 169 deletions

View File

@@ -22,6 +22,7 @@ import android.content.DialogInterface;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.media.AudioManager;
import android.media.Cea708CaptionRenderer;
import android.media.ClosedCaptionRenderer;
import android.media.MediaFormat;
import android.media.MediaPlayer;
@@ -328,6 +329,7 @@ public class VideoView extends SurfaceView
context, mMediaPlayer.getMediaTimeProvider(), mMediaPlayer);
controller.registerRenderer(new WebVttRenderer(context));
controller.registerRenderer(new TtmlRenderer(context));
controller.registerRenderer(new Cea708CaptionRenderer(context));
controller.registerRenderer(new ClosedCaptionRenderer(context));
mMediaPlayer.setSubtitleAnchor(controller, this);

View File

@@ -28,6 +28,7 @@ import android.graphics.Paint.Style;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.text.Layout.Alignment;
import android.text.SpannableStringBuilder;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.AttributeSet;
@@ -54,8 +55,8 @@ public class SubtitleView extends View {
/** Temporary rectangle used for computing line bounds. */
private final RectF mLineBounds = new RectF();
/** Reusable string builder used for holding text. */
private final StringBuilder mText = new StringBuilder();
/** Reusable spannable string builder used for holding text. */
private final SpannableStringBuilder mText = new SpannableStringBuilder();
private Alignment mAlignment;
private TextPaint mTextPaint;
@@ -141,7 +142,7 @@ public class SubtitleView extends View {
}
public void setText(CharSequence text) {
mText.setLength(0);
mText.clear();
mText.append(text);
mHasMeasurements = false;

File diff suppressed because it is too large Load Diff

View File

@@ -23,12 +23,9 @@ import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.os.Parcel;
import android.text.ParcelableSpan;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.TextPaint;
import android.text.TextUtils;
import android.text.style.CharacterStyle;
import android.text.style.StyleSpan;
import android.text.style.UnderlineSpan;
@@ -52,7 +49,7 @@ import java.util.Vector;
/** @hide */
public class ClosedCaptionRenderer extends SubtitleController.Renderer {
private final Context mContext;
private ClosedCaptionWidget mRenderingWidget;
private Cea608CCWidget mCCWidget;
public ClosedCaptionRenderer(Context context) {
mContext = context;
@@ -61,31 +58,35 @@ public class ClosedCaptionRenderer extends SubtitleController.Renderer {
@Override
public boolean supports(MediaFormat format) {
if (format.containsKey(MediaFormat.KEY_MIME)) {
return format.getString(MediaFormat.KEY_MIME).equals(
MediaPlayer.MEDIA_MIMETYPE_TEXT_CEA_608);
String mimeType = format.getString(MediaFormat.KEY_MIME);
return MediaPlayer.MEDIA_MIMETYPE_TEXT_CEA_608.equals(mimeType);
}
return false;
}
@Override
public SubtitleTrack createTrack(MediaFormat format) {
if (mRenderingWidget == null) {
mRenderingWidget = new ClosedCaptionWidget(mContext);
String mimeType = format.getString(MediaFormat.KEY_MIME);
if (MediaPlayer.MEDIA_MIMETYPE_TEXT_CEA_608.equals(mimeType)) {
if (mCCWidget == null) {
mCCWidget = new Cea608CCWidget(mContext);
}
return new Cea608CaptionTrack(mCCWidget, format);
}
return new ClosedCaptionTrack(mRenderingWidget, format);
throw new RuntimeException("No matching format: " + format.toString());
}
}
/** @hide */
class ClosedCaptionTrack extends SubtitleTrack {
private final ClosedCaptionWidget mRenderingWidget;
private final CCParser mCCParser;
class Cea608CaptionTrack extends SubtitleTrack {
private final Cea608CCParser mCCParser;
private final Cea608CCWidget mRenderingWidget;
ClosedCaptionTrack(ClosedCaptionWidget renderingWidget, MediaFormat format) {
Cea608CaptionTrack(Cea608CCWidget renderingWidget, MediaFormat format) {
super(format);
mRenderingWidget = renderingWidget;
mCCParser = new CCParser(renderingWidget);
mCCParser = new Cea608CCParser(mRenderingWidget);
}
@Override
@@ -104,6 +105,149 @@ class ClosedCaptionTrack extends SubtitleTrack {
}
}
/**
* Abstract widget class to render a closed caption track.
*
* @hide
*/
abstract class ClosedCaptionWidget extends ViewGroup implements SubtitleTrack.RenderingWidget {
/** @hide */
interface ClosedCaptionLayout {
void setCaptionStyle(CaptionStyle captionStyle);
void setFontScale(float scale);
}
private static final CaptionStyle DEFAULT_CAPTION_STYLE = CaptionStyle.DEFAULT;
/** Captioning manager, used to obtain and track caption properties. */
private final CaptioningManager mManager;
/** Current caption style. */
protected CaptionStyle mCaptionStyle;
/** Callback for rendering changes. */
protected OnChangedListener mListener;
/** Concrete layout of CC. */
protected ClosedCaptionLayout mClosedCaptionLayout;
/** Whether a caption style change listener is registered. */
private boolean mHasChangeListener;
public ClosedCaptionWidget(Context context) {
this(context, null);
}
public ClosedCaptionWidget(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ClosedCaptionWidget(Context context, AttributeSet attrs, int defStyle) {
this(context, attrs, defStyle, 0);
}
public ClosedCaptionWidget(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
// Cannot render text over video when layer type is hardware.
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
mManager = (CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE);
mCaptionStyle = DEFAULT_CAPTION_STYLE.applyStyle(mManager.getUserStyle());
mClosedCaptionLayout = createCaptionLayout(context);
mClosedCaptionLayout.setCaptionStyle(mCaptionStyle);
mClosedCaptionLayout.setFontScale(mManager.getFontScale());
addView((ViewGroup) mClosedCaptionLayout, LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT);
requestLayout();
}
public abstract ClosedCaptionLayout createCaptionLayout(Context context);
@Override
public void setOnChangedListener(OnChangedListener listener) {
mListener = listener;
}
@Override
public void setSize(int width, int height) {
final int widthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
final int heightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
measure(widthSpec, heightSpec);
layout(0, 0, width, height);
}
@Override
public void setVisible(boolean visible) {
if (visible) {
setVisibility(View.VISIBLE);
} else {
setVisibility(View.GONE);
}
manageChangeListener();
}
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
manageChangeListener();
}
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
manageChangeListener();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
((ViewGroup) mClosedCaptionLayout).measure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
((ViewGroup) mClosedCaptionLayout).layout(l, t, r, b);
}
/**
* Manages whether this renderer is listening for caption style changes.
*/
private final CaptioningChangeListener mCaptioningListener = new CaptioningChangeListener() {
@Override
public void onUserStyleChanged(CaptionStyle userStyle) {
mCaptionStyle = DEFAULT_CAPTION_STYLE.applyStyle(userStyle);
mClosedCaptionLayout.setCaptionStyle(mCaptionStyle);
}
@Override
public void onFontScaleChanged(float fontScale) {
mClosedCaptionLayout.setFontScale(fontScale);
}
};
private void manageChangeListener() {
final boolean needsListener = isAttachedToWindow() && getVisibility() == View.VISIBLE;
if (mHasChangeListener != needsListener) {
mHasChangeListener = needsListener;
if (needsListener) {
mManager.addCaptioningChangeListener(mCaptioningListener);
} else {
mManager.removeCaptioningChangeListener(mCaptioningListener);
}
}
}
}
/**
* @hide
*
@@ -113,11 +257,11 @@ class ClosedCaptionTrack extends SubtitleTrack {
* display change with styled text for rendering.
*
*/
class CCParser {
class Cea608CCParser {
public static final int MAX_ROWS = 15;
public static final int MAX_COLS = 32;
private static final String TAG = "CCParser";
private static final String TAG = "Cea608CCParser";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final int INVALID = -1;
@@ -160,11 +304,11 @@ class CCParser {
private CCMemory mNonDisplay = new CCMemory();
private CCMemory mTextMem = new CCMemory();
CCParser(DisplayListener listener) {
Cea608CCParser(DisplayListener listener) {
mListener = listener;
}
void parse(byte[] data) {
public void parse(byte[] data) {
CCData[] ccData = CCData.fromByteArray(data);
for (int i = 0; i < ccData.length; i++) {
@@ -184,8 +328,8 @@ class CCParser {
}
interface DisplayListener {
public void onDisplayChanged(SpannableStringBuilder[] styledTexts);
public CaptionStyle getCaptionStyle();
void onDisplayChanged(SpannableStringBuilder[] styledTexts);
CaptionStyle getCaptionStyle();
}
private CCMemory getMemory() {
@@ -480,6 +624,33 @@ class CCParser {
}
}
/**
* Mutable version of BackgroundSpan to facilitate text rendering with edge styles.
*
* @hide
*/
public static class MutableBackgroundColorSpan extends CharacterStyle
implements UpdateAppearance {
private int mColor;
public MutableBackgroundColorSpan(int color) {
mColor = color;
}
public void setBackgroundColor(int color) {
mColor = color;
}
public int getBackgroundColor() {
return mColor;
}
@Override
public void updateDrawState(TextPaint ds) {
ds.bgColor = mColor;
}
}
/* CCLineBuilder keeps track of displayable chars, as well as
* MidRow styles and PACs, for a single line of CC memory.
*
@@ -682,8 +853,7 @@ class CCParser {
}
SpannableStringBuilder[] getStyledText(CaptionStyle captionStyle) {
ArrayList<SpannableStringBuilder> rows =
new ArrayList<SpannableStringBuilder>(MAX_ROWS);
ArrayList<SpannableStringBuilder> rows = new ArrayList<>(MAX_ROWS);
for (int i = 1; i <= MAX_ROWS; i++) {
rows.add(mLines[i] != null ?
mLines[i].getStyledText(captionStyle) : null);
@@ -1043,128 +1213,40 @@ class CCParser {
}
}
/**
* Mutable version of BackgroundSpan to facilitate text rendering with edge
* styles.
*
* @hide
*/
class MutableBackgroundColorSpan extends CharacterStyle implements UpdateAppearance {
private int mColor;
public MutableBackgroundColorSpan(int color) {
mColor = color;
}
public void setBackgroundColor(int color) {
mColor = color;
}
public int getBackgroundColor() {
return mColor;
}
@Override
public void updateDrawState(TextPaint ds) {
ds.bgColor = mColor;
}
}
/**
* Widget capable of rendering CEA-608 closed captions.
*
* @hide
*/
class ClosedCaptionWidget extends ViewGroup implements
SubtitleTrack.RenderingWidget,
CCParser.DisplayListener {
private static final String TAG = "ClosedCaptionWidget";
class Cea608CCWidget extends ClosedCaptionWidget implements Cea608CCParser.DisplayListener {
private static final Rect mTextBounds = new Rect();
private static final String mDummyText = "1234567890123456789012345678901234";
private static final CaptionStyle DEFAULT_CAPTION_STYLE = CaptionStyle.DEFAULT;
/** Captioning manager, used to obtain and track caption properties. */
private final CaptioningManager mManager;
/** Callback for rendering changes. */
private OnChangedListener mListener;
/** Current caption style. */
private CaptionStyle mCaptionStyle;
/* Closed caption layout. */
private CCLayout mClosedCaptionLayout;
/** Whether a caption style change listener is registered. */
private boolean mHasChangeListener;
public ClosedCaptionWidget(Context context) {
public Cea608CCWidget(Context context) {
this(context, null);
}
public ClosedCaptionWidget(Context context, AttributeSet attrs) {
this(context, null, 0);
public Cea608CCWidget(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ClosedCaptionWidget(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
public Cea608CCWidget(Context context, AttributeSet attrs, int defStyle) {
this(context, attrs, defStyle, 0);
}
// Cannot render text over video when layer type is hardware.
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
mManager = (CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE);
mCaptionStyle = DEFAULT_CAPTION_STYLE.applyStyle(mManager.getUserStyle());
mClosedCaptionLayout = new CCLayout(context);
mClosedCaptionLayout.setCaptionStyle(mCaptionStyle);
addView(mClosedCaptionLayout, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
requestLayout();
public Cea608CCWidget(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
public void setOnChangedListener(OnChangedListener listener) {
mListener = listener;
}
@Override
public void setSize(int width, int height) {
final int widthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
final int heightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
measure(widthSpec, heightSpec);
layout(0, 0, width, height);
}
@Override
public void setVisible(boolean visible) {
if (visible) {
setVisibility(View.VISIBLE);
} else {
setVisibility(View.GONE);
}
manageChangeListener();
}
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
manageChangeListener();
}
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
manageChangeListener();
public ClosedCaptionLayout createCaptionLayout(Context context) {
return new CCLayout(context);
}
@Override
public void onDisplayChanged(SpannableStringBuilder[] styledTexts) {
mClosedCaptionLayout.update(styledTexts);
((CCLayout) mClosedCaptionLayout).update(styledTexts);
if (mListener != null) {
mListener.onChanged(this);
@@ -1176,41 +1258,6 @@ class ClosedCaptionWidget extends ViewGroup implements
return mCaptionStyle;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mClosedCaptionLayout.measure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
mClosedCaptionLayout.layout(l, t, r, b);
}
/**
* Manages whether this renderer is listening for caption style changes.
*/
private final CaptioningChangeListener mCaptioningListener = new CaptioningChangeListener() {
@Override
public void onUserStyleChanged(CaptionStyle userStyle) {
mCaptionStyle = DEFAULT_CAPTION_STYLE.applyStyle(userStyle);
mClosedCaptionLayout.setCaptionStyle(mCaptionStyle);
}
};
private void manageChangeListener() {
final boolean needsListener = isAttachedToWindow() && getVisibility() == View.VISIBLE;
if (mHasChangeListener != needsListener) {
mHasChangeListener = needsListener;
if (needsListener) {
mManager.addCaptioningChangeListener(mCaptioningListener);
} else {
mManager.removeCaptioningChangeListener(mCaptioningListener);
}
}
}
private static class CCLineBox extends TextView {
private static final float FONT_PADDING_RATIO = 0.75f;
private static final float EDGE_OUTLINE_RATIO = 0.1f;
@@ -1260,8 +1307,7 @@ class ClosedCaptionWidget extends ViewGroup implements
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
float fontSize = MeasureSpec.getSize(heightMeasureSpec)
* FONT_PADDING_RATIO;
float fontSize = MeasureSpec.getSize(heightMeasureSpec) * FONT_PADDING_RATIO;
setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize);
mOutlineWidth = EDGE_OUTLINE_RATIO * fontSize + 1.0f;
@@ -1358,8 +1404,8 @@ class ClosedCaptionWidget extends ViewGroup implements
CharSequence text = getText();
if (text instanceof Spannable) {
Spannable spannable = (Spannable) text;
MutableBackgroundColorSpan[] bgSpans = spannable.getSpans(
0, spannable.length(), MutableBackgroundColorSpan.class);
Cea608CCParser.MutableBackgroundColorSpan[] bgSpans = spannable.getSpans(
0, spannable.length(), Cea608CCParser.MutableBackgroundColorSpan.class);
for (int i = 0; i < bgSpans.length; i++) {
bgSpans[i].setBackgroundColor(color);
}
@@ -1367,8 +1413,8 @@ class ClosedCaptionWidget extends ViewGroup implements
}
}
private static class CCLayout extends LinearLayout {
private static final int MAX_ROWS = CCParser.MAX_ROWS;
private static class CCLayout extends LinearLayout implements ClosedCaptionLayout {
private static final int MAX_ROWS = Cea608CCParser.MAX_ROWS;
private static final float SAFE_AREA_RATIO = 0.9f;
private final CCLineBox[] mLineBoxes = new CCLineBox[MAX_ROWS];
@@ -1383,12 +1429,18 @@ class ClosedCaptionWidget extends ViewGroup implements
}
}
void setCaptionStyle(CaptionStyle captionStyle) {
@Override
public void setCaptionStyle(CaptionStyle captionStyle) {
for (int i = 0; i < MAX_ROWS; i++) {
mLineBoxes[i].setCaptionStyle(captionStyle);
}
}
@Override
public void setFontScale(float fontScale) {
// Ignores the font scale changes of the system wide CC preference.
}
void update(SpannableStringBuilder[] textBuffer) {
for (int i = 0; i < MAX_ROWS; i++) {
if (textBuffer[i] != null) {
@@ -1455,4 +1507,4 @@ class ClosedCaptionWidget extends ViewGroup implements
}
}
}
};
}

View File

@@ -2124,6 +2124,12 @@ public class MediaPlayer implements SubtitleController.Listener
*/
public static final String MEDIA_MIMETYPE_TEXT_CEA_608 = "text/cea-608";
/**
* MIME type for CEA-708 closed caption data.
* @hide
*/
public static final String MEDIA_MIMETYPE_TEXT_CEA_708 = "text/cea-708";
/*
* A helper function to check if the mime type is supported by media framework.
*/