• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.tv.tuner.exoplayer;
18 
19 import android.util.Log;
20 
21 import com.google.android.exoplayer.ExoPlaybackException;
22 import com.google.android.exoplayer.MediaClock;
23 import com.google.android.exoplayer.MediaFormat;
24 import com.google.android.exoplayer.MediaFormatHolder;
25 import com.google.android.exoplayer.SampleHolder;
26 import com.google.android.exoplayer.SampleSource;
27 import com.google.android.exoplayer.TrackRenderer;
28 import com.google.android.exoplayer.util.Assertions;
29 import com.android.tv.tuner.cc.Cea708Parser;
30 import com.android.tv.tuner.data.Cea708Data.CaptionEvent;
31 
32 import java.io.IOException;
33 
34 /**
35  * A {@link TrackRenderer} for CEA-708 textual subtitles.
36  */
37 public class Cea708TextTrackRenderer extends TrackRenderer implements
38         Cea708Parser.OnCea708ParserListener {
39     private static final String TAG = "Cea708TextTrackRenderer";
40     private static final boolean DEBUG = false;
41 
42     public static final int MSG_SERVICE_NUMBER = 1;
43     public static final int MSG_ENABLE_CLOSED_CAPTION = 2;
44 
45     // According to CEA-708B, the maximum value of closed caption bandwidth is 9600bps.
46     private static final int DEFAULT_INPUT_BUFFER_SIZE = 9600 / 8;
47 
48     private final SampleSource.SampleSourceReader mSource;
49     private final SampleHolder mSampleHolder;
50     private final MediaFormatHolder mFormatHolder;
51     private int mServiceNumber;
52     private boolean mInputStreamEnded;
53     private long mCurrentPositionUs;
54     private long mPresentationTimeUs;
55     private int mTrackIndex;
56     private boolean mRenderingDisabled;
57     private Cea708Parser mCea708Parser;
58     private CcListener mCcListener;
59 
60     public interface CcListener {
emitEvent(CaptionEvent captionEvent)61         void emitEvent(CaptionEvent captionEvent);
clearCaption()62         void clearCaption();
discoverServiceNumber(int serviceNumber)63         void discoverServiceNumber(int serviceNumber);
64     }
65 
Cea708TextTrackRenderer(SampleSource source)66     public Cea708TextTrackRenderer(SampleSource source) {
67         mSource = source.register();
68         mTrackIndex = -1;
69         mSampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_DIRECT);
70         mSampleHolder.ensureSpaceForWrite(DEFAULT_INPUT_BUFFER_SIZE);
71         mFormatHolder = new MediaFormatHolder();
72     }
73 
74     @Override
getMediaClock()75     protected MediaClock getMediaClock() {
76         return null;
77     }
78 
handlesMimeType(String mimeType)79     private boolean handlesMimeType(String mimeType) {
80         return mimeType.equals(MpegTsSampleExtractor.MIMETYPE_TEXT_CEA_708);
81     }
82 
83     @Override
doPrepare(long positionUs)84     protected boolean doPrepare(long positionUs) throws ExoPlaybackException {
85         boolean sourcePrepared = mSource.prepare(positionUs);
86         if (!sourcePrepared) {
87             return false;
88         }
89         int trackCount = mSource.getTrackCount();
90         for (int i = 0; i < trackCount; ++i) {
91             MediaFormat trackFormat = mSource.getFormat(i);
92             if (handlesMimeType(trackFormat.mimeType)) {
93                 mTrackIndex = i;
94                 clearDecodeState();
95                 return true;
96             }
97         }
98         // TODO: Check this case. (Source do not have the proper mime type.)
99         return true;
100     }
101 
102     @Override
onEnabled(int track, long positionUs, boolean joining)103     protected void onEnabled(int track, long positionUs, boolean joining) {
104         Assertions.checkArgument(mTrackIndex != -1 && track == 0);
105         mSource.enable(mTrackIndex, positionUs);
106         mInputStreamEnded = false;
107         mPresentationTimeUs = positionUs;
108         mCurrentPositionUs = Long.MIN_VALUE;
109     }
110 
111     @Override
onDisabled()112     protected void onDisabled() {
113         mSource.disable(mTrackIndex);
114     }
115 
116     @Override
onReleased()117     protected void onReleased() {
118         mSource.release();
119         mCea708Parser = null;
120     }
121 
122     @Override
isEnded()123     protected boolean isEnded() {
124         return mInputStreamEnded;
125     }
126 
127     @Override
isReady()128     protected boolean isReady() {
129         // Since this track will be fed by {@link VideoTrackRenderer},
130         // it is not required to control transition between ready state and buffering state.
131         return true;
132     }
133 
134     @Override
getTrackCount()135     protected int getTrackCount() {
136         return mTrackIndex < 0 ? 0 : 1;
137     }
138 
139     @Override
getFormat(int track)140     protected MediaFormat getFormat(int track) {
141         Assertions.checkArgument(mTrackIndex != -1 && track == 0);
142         return mSource.getFormat(mTrackIndex);
143     }
144 
145     @Override
maybeThrowError()146     protected void maybeThrowError() throws ExoPlaybackException {
147         try {
148             mSource.maybeThrowError();
149         } catch (IOException e) {
150             throw new ExoPlaybackException(e);
151         }
152     }
153 
154     @Override
doSomeWork(long positionUs, long elapsedRealtimeUs)155     protected void doSomeWork(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
156         try {
157             mPresentationTimeUs = positionUs;
158             if (!mInputStreamEnded) {
159                 processOutput();
160                 feedInputBuffer();
161             }
162         } catch (IOException e) {
163             throw new ExoPlaybackException(e);
164         }
165     }
166 
processOutput()167     private boolean processOutput() {
168         return !mInputStreamEnded && mCea708Parser != null &&
169                 mCea708Parser.processClosedCaptions(mPresentationTimeUs);
170     }
171 
feedInputBuffer()172     private boolean feedInputBuffer() throws IOException, ExoPlaybackException {
173         if (mInputStreamEnded) {
174             return false;
175         }
176         long discontinuity = mSource.readDiscontinuity(mTrackIndex);
177         if (discontinuity != SampleSource.NO_DISCONTINUITY) {
178             if (DEBUG) {
179                 Log.d(TAG, "Read discontinuity happened");
180             }
181 
182             // TODO: handle input discontinuity for trickplay.
183             clearDecodeState();
184             mPresentationTimeUs = discontinuity;
185             return false;
186         }
187         mSampleHolder.data.clear();
188         mSampleHolder.size = 0;
189         int result = mSource.readData(mTrackIndex, mPresentationTimeUs,
190                 mFormatHolder, mSampleHolder);
191         switch (result) {
192             case SampleSource.NOTHING_READ: {
193                 return false;
194             }
195             case SampleSource.FORMAT_READ: {
196                 if (DEBUG) {
197                     Log.i(TAG, "Format was read again");
198                 }
199                 return true;
200             }
201             case SampleSource.END_OF_STREAM: {
202                 if (DEBUG) {
203                     Log.i(TAG, "End of stream from SampleSource");
204                 }
205                 mInputStreamEnded = true;
206                 return false;
207             }
208             case SampleSource.SAMPLE_READ: {
209                 mSampleHolder.data.flip();
210                 if (mCea708Parser != null && !mRenderingDisabled) {
211                     mCea708Parser.parseClosedCaption(mSampleHolder.data, mSampleHolder.timeUs);
212                 }
213                 return true;
214             }
215         }
216         return false;
217     }
218 
clearDecodeState()219     private void clearDecodeState() {
220         mCea708Parser = new Cea708Parser();
221         mCea708Parser.setListener(this);
222         mCea708Parser.setListenServiceNumber(mServiceNumber);
223     }
224 
225     @Override
getDurationUs()226     protected long getDurationUs() {
227         return mSource.getFormat(mTrackIndex).durationUs;
228     }
229 
230     @Override
getBufferedPositionUs()231     protected long getBufferedPositionUs() {
232         return mSource.getBufferedPositionUs();
233     }
234 
235     @Override
seekTo(long currentPositionUs)236     protected void seekTo(long currentPositionUs) throws ExoPlaybackException {
237         mSource.seekToUs(currentPositionUs);
238         mInputStreamEnded = false;
239         mPresentationTimeUs = currentPositionUs;
240         mCurrentPositionUs = Long.MIN_VALUE;
241     }
242 
243     @Override
onStarted()244     protected void onStarted() {
245         // do nothing.
246     }
247 
248     @Override
onStopped()249     protected void onStopped() {
250         // do nothing.
251     }
252 
setServiceNumber(int serviceNumber)253     private void setServiceNumber(int serviceNumber) {
254         mServiceNumber = serviceNumber;
255         if (mCea708Parser != null) {
256             mCea708Parser.setListenServiceNumber(serviceNumber);
257         }
258     }
259 
260     @Override
emitEvent(CaptionEvent event)261     public void emitEvent(CaptionEvent event) {
262         if (mCcListener != null) {
263             mCcListener.emitEvent(event);
264         }
265     }
266 
267     @Override
discoverServiceNumber(int serviceNumber)268     public void discoverServiceNumber(int serviceNumber) {
269         if (mCcListener != null) {
270             mCcListener.discoverServiceNumber(serviceNumber);
271         }
272     }
273 
setCcListener(CcListener ccListener)274     public void setCcListener(CcListener ccListener) {
275         mCcListener = ccListener;
276     }
277 
278     @Override
handleMessage(int messageType, Object message)279     public void handleMessage(int messageType, Object message) throws ExoPlaybackException {
280         switch (messageType) {
281             case MSG_SERVICE_NUMBER:
282                 setServiceNumber((int) message);
283                 break;
284             case MSG_ENABLE_CLOSED_CAPTION:
285                 boolean renderingDisabled = (Boolean) message == false;
286                 if (mRenderingDisabled != renderingDisabled) {
287                     mRenderingDisabled = renderingDisabled;
288                     if (mRenderingDisabled) {
289                         if (mCea708Parser != null) {
290                             mCea708Parser.clear();
291                         }
292                         if (mCcListener != null) {
293                             mCcListener.clearCaption();
294                         }
295                     }
296                 }
297                 break;
298             default:
299                 super.handleMessage(messageType, message);
300         }
301     }
302 }
303