aboutsummaryrefslogtreecommitdiff
path: root/tuner/src/com/android/tv/tuner/exoplayer/Cea708TextTrackRenderer.java
diff options
context:
space:
mode:
Diffstat (limited to 'tuner/src/com/android/tv/tuner/exoplayer/Cea708TextTrackRenderer.java')
-rw-r--r--tuner/src/com/android/tv/tuner/exoplayer/Cea708TextTrackRenderer.java305
1 files changed, 305 insertions, 0 deletions
diff --git a/tuner/src/com/android/tv/tuner/exoplayer/Cea708TextTrackRenderer.java b/tuner/src/com/android/tv/tuner/exoplayer/Cea708TextTrackRenderer.java
new file mode 100644
index 00000000..1f48c45b
--- /dev/null
+++ b/tuner/src/com/android/tv/tuner/exoplayer/Cea708TextTrackRenderer.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 2015 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 com.android.tv.tuner.exoplayer;
+
+import android.util.Log;
+import com.android.tv.tuner.cc.Cea708Parser;
+import com.android.tv.tuner.data.Cea708Data.CaptionEvent;
+import com.google.android.exoplayer.ExoPlaybackException;
+import com.google.android.exoplayer.MediaClock;
+import com.google.android.exoplayer.MediaFormat;
+import com.google.android.exoplayer.MediaFormatHolder;
+import com.google.android.exoplayer.SampleHolder;
+import com.google.android.exoplayer.SampleSource;
+import com.google.android.exoplayer.TrackRenderer;
+import com.google.android.exoplayer.util.Assertions;
+import java.io.IOException;
+
+/** A {@link TrackRenderer} for CEA-708 textual subtitles. */
+public class Cea708TextTrackRenderer extends TrackRenderer
+ implements Cea708Parser.OnCea708ParserListener {
+ private static final String TAG = "Cea708TextTrackRenderer";
+ private static final boolean DEBUG = false;
+
+ public static final int MSG_SERVICE_NUMBER = 1;
+ public static final int MSG_ENABLE_CLOSED_CAPTION = 2;
+
+ // According to CEA-708B, the maximum value of closed caption bandwidth is 9600bps.
+ private static final int DEFAULT_INPUT_BUFFER_SIZE = 9600 / 8;
+
+ private final SampleSource.SampleSourceReader mSource;
+ private final SampleHolder mSampleHolder;
+ private final MediaFormatHolder mFormatHolder;
+ private int mServiceNumber;
+ private boolean mInputStreamEnded;
+ private long mCurrentPositionUs;
+ private long mPresentationTimeUs;
+ private int mTrackIndex;
+ private boolean mRenderingDisabled;
+ private Cea708Parser mCea708Parser;
+ private CcListener mCcListener;
+
+ public interface CcListener {
+ void emitEvent(CaptionEvent captionEvent);
+
+ void clearCaption();
+
+ void discoverServiceNumber(int serviceNumber);
+ }
+
+ public Cea708TextTrackRenderer(SampleSource source) {
+ mSource = source.register();
+ mTrackIndex = -1;
+ mSampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_DIRECT);
+ mSampleHolder.ensureSpaceForWrite(DEFAULT_INPUT_BUFFER_SIZE);
+ mFormatHolder = new MediaFormatHolder();
+ }
+
+ @Override
+ protected MediaClock getMediaClock() {
+ return null;
+ }
+
+ private boolean handlesMimeType(String mimeType) {
+ return mimeType.equals(MpegTsSampleExtractor.MIMETYPE_TEXT_CEA_708);
+ }
+
+ @Override
+ protected boolean doPrepare(long positionUs) throws ExoPlaybackException {
+ boolean sourcePrepared = mSource.prepare(positionUs);
+ if (!sourcePrepared) {
+ return false;
+ }
+ int trackCount = mSource.getTrackCount();
+ for (int i = 0; i < trackCount; ++i) {
+ MediaFormat trackFormat = mSource.getFormat(i);
+ if (handlesMimeType(trackFormat.mimeType)) {
+ mTrackIndex = i;
+ clearDecodeState();
+ return true;
+ }
+ }
+ // TODO: Check this case. (Source do not have the proper mime type.)
+ return true;
+ }
+
+ @Override
+ protected void onEnabled(int track, long positionUs, boolean joining) {
+ Assertions.checkArgument(mTrackIndex != -1 && track == 0);
+ mSource.enable(mTrackIndex, positionUs);
+ mInputStreamEnded = false;
+ mPresentationTimeUs = positionUs;
+ mCurrentPositionUs = Long.MIN_VALUE;
+ }
+
+ @Override
+ protected void onDisabled() {
+ mSource.disable(mTrackIndex);
+ }
+
+ @Override
+ protected void onReleased() {
+ mSource.release();
+ mCea708Parser = null;
+ }
+
+ @Override
+ protected boolean isEnded() {
+ return mInputStreamEnded;
+ }
+
+ @Override
+ protected boolean isReady() {
+ // Since this track will be fed by {@link VideoTrackRenderer},
+ // it is not required to control transition between ready state and buffering state.
+ return true;
+ }
+
+ @Override
+ protected int getTrackCount() {
+ return mTrackIndex < 0 ? 0 : 1;
+ }
+
+ @Override
+ protected MediaFormat getFormat(int track) {
+ Assertions.checkArgument(mTrackIndex != -1 && track == 0);
+ return mSource.getFormat(mTrackIndex);
+ }
+
+ @Override
+ protected void maybeThrowError() throws ExoPlaybackException {
+ try {
+ mSource.maybeThrowError();
+ } catch (IOException e) {
+ throw new ExoPlaybackException(e);
+ }
+ }
+
+ @Override
+ protected void doSomeWork(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
+ try {
+ mPresentationTimeUs = positionUs;
+ if (!mInputStreamEnded) {
+ processOutput();
+ feedInputBuffer();
+ }
+ } catch (IOException e) {
+ throw new ExoPlaybackException(e);
+ }
+ }
+
+ private boolean processOutput() {
+ return !mInputStreamEnded
+ && mCea708Parser != null
+ && mCea708Parser.processClosedCaptions(mPresentationTimeUs);
+ }
+
+ private boolean feedInputBuffer() throws IOException, ExoPlaybackException {
+ if (mInputStreamEnded) {
+ return false;
+ }
+ long discontinuity = mSource.readDiscontinuity(mTrackIndex);
+ if (discontinuity != SampleSource.NO_DISCONTINUITY) {
+ if (DEBUG) {
+ Log.d(TAG, "Read discontinuity happened");
+ }
+
+ // TODO: handle input discontinuity for trickplay.
+ clearDecodeState();
+ mPresentationTimeUs = discontinuity;
+ return false;
+ }
+ mSampleHolder.data.clear();
+ mSampleHolder.size = 0;
+ int result =
+ mSource.readData(mTrackIndex, mPresentationTimeUs, mFormatHolder, mSampleHolder);
+ switch (result) {
+ case SampleSource.NOTHING_READ:
+ {
+ return false;
+ }
+ case SampleSource.FORMAT_READ:
+ {
+ if (DEBUG) {
+ Log.i(TAG, "Format was read again");
+ }
+ return true;
+ }
+ case SampleSource.END_OF_STREAM:
+ {
+ if (DEBUG) {
+ Log.i(TAG, "End of stream from SampleSource");
+ }
+ mInputStreamEnded = true;
+ return false;
+ }
+ case SampleSource.SAMPLE_READ:
+ {
+ mSampleHolder.data.flip();
+ if (mCea708Parser != null && !mRenderingDisabled) {
+ mCea708Parser.parseClosedCaption(mSampleHolder.data, mSampleHolder.timeUs);
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void clearDecodeState() {
+ mCea708Parser = new Cea708Parser();
+ mCea708Parser.setListener(this);
+ mCea708Parser.setListenServiceNumber(mServiceNumber);
+ }
+
+ @Override
+ protected long getDurationUs() {
+ return mSource.getFormat(mTrackIndex).durationUs;
+ }
+
+ @Override
+ protected long getBufferedPositionUs() {
+ return mSource.getBufferedPositionUs();
+ }
+
+ @Override
+ protected void seekTo(long currentPositionUs) throws ExoPlaybackException {
+ mSource.seekToUs(currentPositionUs);
+ mInputStreamEnded = false;
+ mPresentationTimeUs = currentPositionUs;
+ mCurrentPositionUs = Long.MIN_VALUE;
+ }
+
+ @Override
+ protected void onStarted() {
+ // do nothing.
+ }
+
+ @Override
+ protected void onStopped() {
+ // do nothing.
+ }
+
+ private void setServiceNumber(int serviceNumber) {
+ mServiceNumber = serviceNumber;
+ if (mCea708Parser != null) {
+ mCea708Parser.setListenServiceNumber(serviceNumber);
+ }
+ }
+
+ @Override
+ public void emitEvent(CaptionEvent event) {
+ if (mCcListener != null) {
+ mCcListener.emitEvent(event);
+ }
+ }
+
+ @Override
+ public void discoverServiceNumber(int serviceNumber) {
+ if (mCcListener != null) {
+ mCcListener.discoverServiceNumber(serviceNumber);
+ }
+ }
+
+ public void setCcListener(CcListener ccListener) {
+ mCcListener = ccListener;
+ }
+
+ @Override
+ public void handleMessage(int messageType, Object message) throws ExoPlaybackException {
+ switch (messageType) {
+ case MSG_SERVICE_NUMBER:
+ setServiceNumber((int) message);
+ break;
+ case MSG_ENABLE_CLOSED_CAPTION:
+ boolean renderingDisabled = (Boolean) message == false;
+ if (mRenderingDisabled != renderingDisabled) {
+ mRenderingDisabled = renderingDisabled;
+ if (mRenderingDisabled) {
+ if (mCea708Parser != null) {
+ mCea708Parser.clear();
+ }
+ if (mCcListener != null) {
+ mCcListener.clearCaption();
+ }
+ }
+ }
+ break;
+ default:
+ super.handleMessage(messageType, message);
+ }
+ }
+}