Home | History | Annotate | Download | only in exoplayer
      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 {
     59         void emitEvent(CaptionEvent captionEvent);
     60         void discoverServiceNumber(int serviceNumber);
     61     }
     62 
     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
     72     protected MediaClock getMediaClock() {
     73         return null;
     74     }
     75 
     76     private boolean handlesMimeType(String mimeType) {
     77         return mimeType.equals(MpegTsSampleSourceExtractor.MIMETYPE_TEXT_CEA_708);
     78     }
     79 
     80     @Override
     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
    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
    109     protected void onDisabled() {
    110         mSource.disable(mTrackIndex);
    111     }
    112 
    113     @Override
    114     protected void onReleased() {
    115         mSource.release();
    116         mCea708Parser = null;
    117     }
    118 
    119     @Override
    120     protected boolean isEnded() {
    121         return mInputStreamEnded;
    122     }
    123 
    124     @Override
    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
    132     protected int getTrackCount() {
    133         return mTrackIndex < 0 ? 0 : 1;
    134     }
    135 
    136     @Override
    137     protected MediaFormat getFormat(int track) {
    138         Assertions.checkArgument(mTrackIndex != -1 && track == 0);
    139         return mSource.getFormat(mTrackIndex);
    140     }
    141 
    142     @Override
    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
    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 
    164     private boolean processOutput() {
    165         if (mInputStreamEnded) {
    166             return false;
    167         }
    168         return mCea708Parser != null && mCea708Parser.processClosedCaptions(mPresentationTimeUs);
    169     }
    170 
    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 
    218     private void clearDecodeState() {
    219         mCea708Parser = new Cea708Parser();
    220         mCea708Parser.setListener(this);
    221         mCea708Parser.setListenServiceNumber(mServiceNumber);
    222     }
    223 
    224     @Override
    225     protected long getDurationUs() {
    226         return mSource.getFormat(mTrackIndex).durationUs;
    227     }
    228 
    229     @Override
    230     protected long getBufferedPositionUs() {
    231         return mSource.getBufferedPositionUs();
    232     }
    233 
    234     @Override
    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
    243     protected void onStarted() {
    244         // do nothing.
    245     }
    246 
    247     @Override
    248     protected void onStopped() {
    249         // do nothing.
    250     }
    251 
    252     private void setServiceNumber(int serviceNumber) {
    253         mServiceNumber = serviceNumber;
    254         if (mCea708Parser != null) {
    255             mCea708Parser.setListenServiceNumber(serviceNumber);
    256         }
    257     }
    258 
    259     @Override
    260     public void emitEvent(CaptionEvent event) {
    261         if (mCcListener != null) {
    262             mCcListener.emitEvent(event);
    263         }
    264     }
    265 
    266     @Override
    267     public void discoverServiceNumber(int serviceNumber) {
    268         if (mCcListener != null) {
    269             mCcListener.discoverServiceNumber(serviceNumber);
    270         }
    271     }
    272 
    273     public void setCcListener(CcListener ccListener) {
    274         mCcListener = ccListener;
    275     }
    276 
    277     @Override
    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