diff options
Diffstat (limited to 'tuner/src/com/android/tv/tuner/exoplayer/Cea708TextTrackRenderer.java')
-rw-r--r-- | tuner/src/com/android/tv/tuner/exoplayer/Cea708TextTrackRenderer.java | 305 |
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); + } + } +} |