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.usbtuner.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.usbtuner.cc.Cea708Parser; 30 import com.android.usbtuner.cc.Cea708Parser.OnCea708ParserListener; 31 import com.android.usbtuner.data.Cea708Data.CaptionEvent; 32 33 import java.io.IOException; 34 35 /** 36 * A {@link TrackRenderer} for CEA-708 textual subtitles. 37 */ 38 public class Cea708TextTrackRenderer extends TrackRenderer implements 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 SampleSource.SampleSourceReader mSource; 48 private SampleHolder mSampleHolder; 49 private 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(MpegTsSampleSourceExtractor.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 if (mInputStreamEnded) { 166 return false; 167 } 168 return mCea708Parser != null && mCea708Parser.processClosedCaptions(mPresentationTimeUs); 169 } 170 feedInputBuffer()171 private boolean feedInputBuffer() throws IOException, ExoPlaybackException { 172 if (mInputStreamEnded) { 173 return false; 174 } 175 long discontinuity = mSource.readDiscontinuity(mTrackIndex); 176 if (discontinuity != SampleSource.NO_DISCONTINUITY) { 177 if (DEBUG) { 178 Log.d(TAG, "Read discontinuity happened"); 179 } 180 181 // TODO: handle input discontinuity for trickplay. 182 clearDecodeState(); 183 mPresentationTimeUs = discontinuity; 184 return false; 185 } 186 mSampleHolder.data.clear(); 187 mSampleHolder.size = 0; 188 int result = mSource.readData(mTrackIndex, mPresentationTimeUs, 189 mFormatHolder, mSampleHolder); 190 switch (result) { 191 case SampleSource.NOTHING_READ: { 192 return false; 193 } 194 case SampleSource.FORMAT_READ: { 195 if (DEBUG) { 196 Log.i(TAG, "Format was read again"); 197 } 198 return true; 199 } 200 case SampleSource.END_OF_STREAM: { 201 if (DEBUG) { 202 Log.i(TAG, "End of stream from SampleSource"); 203 } 204 mInputStreamEnded = true; 205 return false; 206 } 207 case SampleSource.SAMPLE_READ: { 208 mSampleHolder.data.flip(); 209 if (mCea708Parser != null) { 210 mCea708Parser.parseClosedCaption(mSampleHolder.data, mSampleHolder.timeUs); 211 } 212 return true; 213 } 214 } 215 return false; 216 } 217 clearDecodeState()218 private void clearDecodeState() { 219 mCea708Parser = new Cea708Parser(); 220 mCea708Parser.setListener(this); 221 mCea708Parser.setListenServiceNumber(mServiceNumber); 222 } 223 224 @Override getDurationUs()225 protected long getDurationUs() { 226 return mSource.getFormat(mTrackIndex).durationUs; 227 } 228 229 @Override getBufferedPositionUs()230 protected long getBufferedPositionUs() { 231 return mSource.getBufferedPositionUs(); 232 } 233 234 @Override seekTo(long currentPositionUs)235 protected void seekTo(long currentPositionUs) throws ExoPlaybackException { 236 mSource.seekToUs(currentPositionUs); 237 mInputStreamEnded = false; 238 mPresentationTimeUs = currentPositionUs; 239 mCurrentPositionUs = Long.MIN_VALUE; 240 } 241 242 @Override onStarted()243 protected void onStarted() { 244 // do nothing. 245 } 246 247 @Override onStopped()248 protected void onStopped() { 249 // do nothing. 250 } 251 setServiceNumber(int serviceNumber)252 private void setServiceNumber(int serviceNumber) { 253 mServiceNumber = serviceNumber; 254 if (mCea708Parser != null) { 255 mCea708Parser.setListenServiceNumber(serviceNumber); 256 } 257 } 258 259 @Override emitEvent(CaptionEvent event)260 public void emitEvent(CaptionEvent event) { 261 if (mCcListener != null) { 262 mCcListener.emitEvent(event); 263 } 264 } 265 266 @Override discoverServiceNumber(int serviceNumber)267 public void discoverServiceNumber(int serviceNumber) { 268 if (mCcListener != null) { 269 mCcListener.discoverServiceNumber(serviceNumber); 270 } 271 } 272 setCcListener(CcListener ccListener)273 public void setCcListener(CcListener ccListener) { 274 mCcListener = ccListener; 275 } 276 277 @Override handleMessage(int messageType, Object message)278 public void handleMessage(int messageType, Object message) throws ExoPlaybackException { 279 if (messageType == MSG_SERVICE_NUMBER) { 280 setServiceNumber((int) message); 281 } else { 282 super.handleMessage(messageType, message); 283 } 284 } 285 } 286