• 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 
44     // According to CEA-708B, the maximum value of closed caption bandwidth is 9600bps.
45     private static final int DEFAULT_INPUT_BUFFER_SIZE = 9600 / 8;
46 
47     private final SampleSource.SampleSourceReader mSource;
48     private final SampleHolder mSampleHolder;
49     private final MediaFormatHolder mFormatHolder;
50     private int mServiceNumber;
51     private boolean mInputStreamEnded;
52     private long mCurrentPositionUs;
53     private long mPresentationTimeUs;
54     private int mTrackIndex;
55     private Cea708Parser mCea708Parser;
56     private CcListener mCcListener;
57 
58     public interface CcListener {
emitEvent(CaptionEvent captionEvent)59         void emitEvent(CaptionEvent captionEvent);
discoverServiceNumber(int serviceNumber)60         void discoverServiceNumber(int serviceNumber);
61     }
62 
Cea708TextTrackRenderer(SampleSource source)63     public Cea708TextTrackRenderer(SampleSource source) {
64         mSource = source.register();
65         mTrackIndex = -1;
66         mSampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_DIRECT);
67         mSampleHolder.ensureSpaceForWrite(DEFAULT_INPUT_BUFFER_SIZE);
68         mFormatHolder = new MediaFormatHolder();
69     }
70 
71     @Override
getMediaClock()72     protected MediaClock getMediaClock() {
73         return null;
74     }
75 
handlesMimeType(String mimeType)76     private boolean handlesMimeType(String mimeType) {
77         return mimeType.equals(MpegTsSampleExtractor.MIMETYPE_TEXT_CEA_708);
78     }
79 
80     @Override
doPrepare(long positionUs)81     protected boolean doPrepare(long positionUs) throws ExoPlaybackException {
82         boolean sourcePrepared = mSource.prepare(positionUs);
83         if (!sourcePrepared) {
84             return false;
85         }
86         int trackCount = mSource.getTrackCount();
87         for (int i = 0; i < trackCount; ++i) {
88             MediaFormat trackFormat = mSource.getFormat(i);
89             if (handlesMimeType(trackFormat.mimeType)) {
90                 mTrackIndex = i;
91                 clearDecodeState();
92                 return true;
93             }
94         }
95         // TODO: Check this case. (Source do not have the proper mime type.)
96         return true;
97     }
98 
99     @Override
onEnabled(int track, long positionUs, boolean joining)100     protected void onEnabled(int track, long positionUs, boolean joining) {
101         Assertions.checkArgument(mTrackIndex != -1 && track == 0);
102         mSource.enable(mTrackIndex, positionUs);
103         mInputStreamEnded = false;
104         mPresentationTimeUs = positionUs;
105         mCurrentPositionUs = Long.MIN_VALUE;
106     }
107 
108     @Override
onDisabled()109     protected void onDisabled() {
110         mSource.disable(mTrackIndex);
111     }
112 
113     @Override
onReleased()114     protected void onReleased() {
115         mSource.release();
116         mCea708Parser = null;
117     }
118 
119     @Override
isEnded()120     protected boolean isEnded() {
121         return mInputStreamEnded;
122     }
123 
124     @Override
isReady()125     protected boolean isReady() {
126         // Since this track will be fed by {@link VideoTrackRenderer},
127         // it is not required to control transition between ready state and buffering state.
128         return true;
129     }
130 
131     @Override
getTrackCount()132     protected int getTrackCount() {
133         return mTrackIndex < 0 ? 0 : 1;
134     }
135 
136     @Override
getFormat(int track)137     protected MediaFormat getFormat(int track) {
138         Assertions.checkArgument(mTrackIndex != -1 && track == 0);
139         return mSource.getFormat(mTrackIndex);
140     }
141 
142     @Override
maybeThrowError()143     protected void maybeThrowError() throws ExoPlaybackException {
144         try {
145             mSource.maybeThrowError();
146         } catch (IOException e) {
147             throw new ExoPlaybackException(e);
148         }
149     }
150 
151     @Override
doSomeWork(long positionUs, long elapsedRealtimeUs)152     protected void doSomeWork(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
153         try {
154             mPresentationTimeUs = positionUs;
155             if (!mInputStreamEnded) {
156                 processOutput();
157                 feedInputBuffer();
158             }
159         } catch (IOException e) {
160             throw new ExoPlaybackException(e);
161         }
162     }
163 
processOutput()164     private boolean processOutput() {
165         return !mInputStreamEnded && mCea708Parser != null &&
166                 mCea708Parser.processClosedCaptions(mPresentationTimeUs);
167     }
168 
feedInputBuffer()169     private boolean feedInputBuffer() throws IOException, ExoPlaybackException {
170         if (mInputStreamEnded) {
171             return false;
172         }
173         long discontinuity = mSource.readDiscontinuity(mTrackIndex);
174         if (discontinuity != SampleSource.NO_DISCONTINUITY) {
175             if (DEBUG) {
176                 Log.d(TAG, "Read discontinuity happened");
177             }
178 
179             // TODO: handle input discontinuity for trickplay.
180             clearDecodeState();
181             mPresentationTimeUs = discontinuity;
182             return false;
183         }
184         mSampleHolder.data.clear();
185         mSampleHolder.size = 0;
186         int result = mSource.readData(mTrackIndex, mPresentationTimeUs,
187                 mFormatHolder, mSampleHolder);
188         switch (result) {
189             case SampleSource.NOTHING_READ: {
190                 return false;
191             }
192             case SampleSource.FORMAT_READ: {
193                 if (DEBUG) {
194                     Log.i(TAG, "Format was read again");
195                 }
196                 return true;
197             }
198             case SampleSource.END_OF_STREAM: {
199                 if (DEBUG) {
200                     Log.i(TAG, "End of stream from SampleSource");
201                 }
202                 mInputStreamEnded = true;
203                 return false;
204             }
205             case SampleSource.SAMPLE_READ: {
206                 mSampleHolder.data.flip();
207                 if (mCea708Parser != null) {
208                     mCea708Parser.parseClosedCaption(mSampleHolder.data, mSampleHolder.timeUs);
209                 }
210                 return true;
211             }
212         }
213         return false;
214     }
215 
clearDecodeState()216     private void clearDecodeState() {
217         mCea708Parser = new Cea708Parser();
218         mCea708Parser.setListener(this);
219         mCea708Parser.setListenServiceNumber(mServiceNumber);
220     }
221 
222     @Override
getDurationUs()223     protected long getDurationUs() {
224         return mSource.getFormat(mTrackIndex).durationUs;
225     }
226 
227     @Override
getBufferedPositionUs()228     protected long getBufferedPositionUs() {
229         return mSource.getBufferedPositionUs();
230     }
231 
232     @Override
seekTo(long currentPositionUs)233     protected void seekTo(long currentPositionUs) throws ExoPlaybackException {
234         mSource.seekToUs(currentPositionUs);
235         mInputStreamEnded = false;
236         mPresentationTimeUs = currentPositionUs;
237         mCurrentPositionUs = Long.MIN_VALUE;
238     }
239 
240     @Override
onStarted()241     protected void onStarted() {
242         // do nothing.
243     }
244 
245     @Override
onStopped()246     protected void onStopped() {
247         // do nothing.
248     }
249 
setServiceNumber(int serviceNumber)250     private void setServiceNumber(int serviceNumber) {
251         mServiceNumber = serviceNumber;
252         if (mCea708Parser != null) {
253             mCea708Parser.setListenServiceNumber(serviceNumber);
254         }
255     }
256 
257     @Override
emitEvent(CaptionEvent event)258     public void emitEvent(CaptionEvent event) {
259         if (mCcListener != null) {
260             mCcListener.emitEvent(event);
261         }
262     }
263 
264     @Override
discoverServiceNumber(int serviceNumber)265     public void discoverServiceNumber(int serviceNumber) {
266         if (mCcListener != null) {
267             mCcListener.discoverServiceNumber(serviceNumber);
268         }
269     }
270 
setCcListener(CcListener ccListener)271     public void setCcListener(CcListener ccListener) {
272         mCcListener = ccListener;
273     }
274 
275     @Override
handleMessage(int messageType, Object message)276     public void handleMessage(int messageType, Object message) throws ExoPlaybackException {
277         if (messageType == MSG_SERVICE_NUMBER) {
278             setServiceNumber((int) message);
279         } else {
280             super.handleMessage(messageType, message);
281         }
282     }
283 }
284