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.media.MediaDataSource;
     20 
     21 import com.google.android.exoplayer.MediaFormat;
     22 import com.google.android.exoplayer.MediaFormatHolder;
     23 import com.google.android.exoplayer.MediaFormatUtil;
     24 import com.google.android.exoplayer.SampleHolder;
     25 import com.google.android.exoplayer.SampleSource;
     26 import com.google.android.exoplayer.util.MimeTypes;
     27 import com.android.usbtuner.exoplayer.cache.CacheManager;
     28 import com.android.usbtuner.tvinput.PlaybackCacheListener;
     29 
     30 import java.io.IOException;
     31 import java.nio.ByteBuffer;
     32 
     33 /**
     34  * Extracts samples from {@link MediaDataSource} for MPEG-TS streams.
     35  */
     36 public final class MpegTsSampleSourceExtractor implements SampleExtractor {
     37     public static final String MIMETYPE_TEXT_CEA_708 = "text/cea-708";
     38 
     39     private static final int CC_BUFFER_SIZE_IN_BYTES = 9600 / 8;
     40 
     41     private final SampleExtractor mSampleExtractor;
     42     private MediaFormat[] mTrackFormats;
     43     private boolean[] mGotEos;
     44     private int mVideoTrackIndex;
     45     private int mCea708TextTrackIndex;
     46     private boolean mCea708TextTrackSelected;
     47     private ByteBuffer mCea708CcBuffer;
     48 
     49     private long mCea708PresentationTimeUs;
     50     private CcParser mCcParser;
     51 
     52     private void init() {
     53         mVideoTrackIndex = -1;
     54         mCea708TextTrackIndex = -1;
     55         mCea708CcBuffer = ByteBuffer.allocate(CC_BUFFER_SIZE_IN_BYTES);
     56         mCea708PresentationTimeUs = -1;
     57         mCea708TextTrackSelected = false;
     58     }
     59 
     60     /**
     61      * Creates MpegTsSampleSourceExtractor for {@link MediaDataSource}.
     62      *
     63      * @param source the {@link MediaDataSource} to extract from
     64      * @param cacheManager the manager for reading & writing samples backed by physical storage
     65      * @param cacheListener the {@link com.android.usbtuner.tvinput.PlaybackCacheListener}
     66      *                      to notify cache storage status change
     67      */
     68     public MpegTsSampleSourceExtractor(MediaDataSource source,
     69             CacheManager cacheManager, PlaybackCacheListener cacheListener) {
     70         if (cacheManager == null || cacheManager.isDisabled()) {
     71             mSampleExtractor =
     72                     new PlaySampleExtractor(source, cacheManager, cacheListener, false);
     73 
     74         } else {
     75             mSampleExtractor =
     76                     new PlaySampleExtractor(source, cacheManager, cacheListener, true);
     77         }
     78         init();
     79     }
     80 
     81     /**
     82      * Creates MpegTsSampleSourceExtractor for a recorded program.
     83      *
     84      * @param cacheManager the samples provider which is stored in physical storage
     85      * @param cacheListener the {@link com.android.usbtuner.tvinput.PlaybackCacheListener}
     86      *                      to notify cache storage status change
     87      */
     88     public MpegTsSampleSourceExtractor(CacheManager cacheManager,
     89             PlaybackCacheListener cacheListener) {
     90         mSampleExtractor = new ReplaySampleSourceExtractor(cacheManager, cacheListener);
     91         init();
     92     }
     93 
     94     @Override
     95     public boolean prepare() throws IOException {
     96         if(!mSampleExtractor.prepare()) {
     97             return false;
     98         }
     99         MediaFormat trackFormats[] = mSampleExtractor.getTrackFormats();
    100         int trackCount = trackFormats.length;
    101         mGotEos = new boolean[trackCount];
    102 
    103         for (int i = 0; i < trackCount; ++i) {
    104             String mime = trackFormats[i].mimeType;
    105             if (MimeTypes.isVideo(mime) && mVideoTrackIndex == -1) {
    106                 mVideoTrackIndex = i;
    107                 if (android.media.MediaFormat.MIMETYPE_VIDEO_MPEG2.equals(mime)) {
    108                     mCcParser = new Mpeg2CcParser();
    109                 } else if (android.media.MediaFormat.MIMETYPE_VIDEO_AVC.equals(mime)) {
    110                     mCcParser = new H264CcParser();
    111                 }
    112             }
    113         }
    114 
    115         if (mVideoTrackIndex != -1) {
    116             mCea708TextTrackIndex = trackCount;
    117         }
    118         mTrackFormats = new MediaFormat[mCea708TextTrackIndex < 0 ? trackCount : trackCount + 1];
    119         System.arraycopy(trackFormats, 0, mTrackFormats, 0, trackCount);
    120         if (mCea708TextTrackIndex >= 0) {
    121             mTrackFormats[trackCount] = MediaFormatUtil.createTextMediaFormat(MIMETYPE_TEXT_CEA_708,
    122                     mTrackFormats[0].durationUs);
    123         }
    124         return true;
    125     }
    126 
    127     @Override
    128     public MediaFormat[] getTrackFormats() {
    129         return mTrackFormats;
    130     }
    131 
    132     @Override
    133     public void selectTrack(int index) {
    134         if (index == mCea708TextTrackIndex) {
    135             mCea708TextTrackSelected = true;
    136             return;
    137         }
    138         mSampleExtractor.selectTrack(index);
    139     }
    140 
    141     @Override
    142     public void deselectTrack(int index) {
    143         if (index == mCea708TextTrackIndex) {
    144             mCea708TextTrackSelected = false;
    145             return;
    146         }
    147         mSampleExtractor.deselectTrack(index);
    148     }
    149 
    150     @Override
    151     public long getBufferedPositionUs() {
    152         return mSampleExtractor.getBufferedPositionUs();
    153     }
    154 
    155     @Override
    156     public void seekTo(long positionUs) {
    157         mSampleExtractor.seekTo(positionUs);
    158     }
    159 
    160     @Override
    161     public void getTrackMediaFormat(int track, MediaFormatHolder outMediaFormatHolder) {
    162         if (track != mCea708TextTrackIndex) {
    163             mSampleExtractor.getTrackMediaFormat(track, outMediaFormatHolder);
    164         }
    165     }
    166 
    167     @Override
    168     public int readSample(int track, SampleHolder sampleHolder) {
    169         if (track == mCea708TextTrackIndex) {
    170             if (mCea708TextTrackSelected && mCea708CcBuffer.position() > 0) {
    171                 mCea708CcBuffer.flip();
    172                 sampleHolder.timeUs = mCea708PresentationTimeUs;
    173                 sampleHolder.data.put(mCea708CcBuffer);
    174                 mCea708CcBuffer.clear();
    175                 return SampleSource.SAMPLE_READ;
    176             } else {
    177                 return mVideoTrackIndex < 0 || mGotEos[mVideoTrackIndex]
    178                         ? SampleSource.END_OF_STREAM : SampleSource.NOTHING_READ;
    179             }
    180         }
    181 
    182         // Should read CC track first.
    183         if (mCea708TextTrackSelected && mCea708CcBuffer.position() > 0) {
    184             return mGotEos[track] ? SampleSource.END_OF_STREAM : SampleSource.NOTHING_READ;
    185         }
    186 
    187         int result = mSampleExtractor.readSample(track, sampleHolder);
    188         switch (result) {
    189             case SampleSource.END_OF_STREAM: {
    190                 mGotEos[track] = true;
    191                 break;
    192             }
    193             case SampleSource.SAMPLE_READ: {
    194                 if (mCea708TextTrackSelected && track == mVideoTrackIndex
    195                         && sampleHolder.data != null) {
    196                     mCcParser.mayParseClosedCaption(sampleHolder.data, sampleHolder.timeUs);
    197                 }
    198                 break;
    199             }
    200         }
    201         return result;
    202     }
    203 
    204     @Override
    205     public void release() {
    206         mSampleExtractor.release();
    207         mVideoTrackIndex = -1;
    208         mCea708TextTrackIndex = -1;
    209         mCea708TextTrackSelected = false;
    210     }
    211 
    212     @Override
    213     public boolean continueBuffering(long positionUs) {
    214         return mSampleExtractor.continueBuffering(positionUs);
    215     }
    216 
    217     private abstract class CcParser {
    218         abstract void mayParseClosedCaption(ByteBuffer buffer, long presentationTimeUs);
    219 
    220         protected void parseClosedCaption(ByteBuffer buffer, int offset, long presentationTimeUs) {
    221             // For the details of user_data_type_structure, see ATSC A/53 Part 4 - Table 6.9.
    222             int pos = offset;
    223             if (pos + 2 >= buffer.position()) {
    224                 return;
    225             }
    226             boolean processCcDataFlag = (buffer.get(pos) & 64) != 0;
    227             int ccCount = buffer.get(pos) & 0x1f;
    228             pos += 2;
    229             if (!processCcDataFlag || pos + 3 * ccCount >= buffer.position() || ccCount == 0) {
    230                 return;
    231             }
    232             for (int i = 0; i < 3 * ccCount; i++) {
    233                 mCea708CcBuffer.put(buffer.get(pos + i));
    234             }
    235             mCea708PresentationTimeUs = presentationTimeUs;
    236         }
    237     }
    238 
    239     private class Mpeg2CcParser extends CcParser {
    240         @Override
    241         public void mayParseClosedCaption(ByteBuffer buffer, long presentationTimeUs) {
    242             int pos = 0;
    243             while (pos + 9 < buffer.position()) {
    244                 // Find the start prefix code of private user data.
    245                 if (buffer.get(pos) == 0
    246                         && buffer.get(pos + 1) == 0
    247                         && buffer.get(pos + 2) == 1
    248                         && (buffer.get(pos + 3) & 0xff) == 0xb2) {
    249                     // ATSC closed caption data embedded in MPEG2VIDEO stream has 'GA94' user
    250                     // identifier and user data type code 3.
    251                     if (buffer.get(pos + 4) == 'G'
    252                             && buffer.get(pos + 5) == 'A'
    253                             && buffer.get(pos + 6) == '9'
    254                             && buffer.get(pos + 7) == '4'
    255                             && buffer.get(pos + 8) == 3) {
    256                         parseClosedCaption(buffer, pos + 9, presentationTimeUs);
    257                     }
    258                     pos += 9;
    259                 } else {
    260                     ++pos;
    261                 }
    262             }
    263         }
    264     }
    265 
    266     private class H264CcParser extends CcParser {
    267         @Override
    268         public void mayParseClosedCaption(ByteBuffer buffer, long presentationTimeUs) {
    269             int pos = 0;
    270             while (pos + 7 < buffer.position()) {
    271                 // Find the start prefix code of a NAL Unit.
    272                 if (buffer.get(pos) == 0
    273                         && buffer.get(pos + 1) == 0
    274                         && buffer.get(pos + 2) == 1) {
    275                     int nalType = buffer.get(pos + 3) & 0x1f;
    276                     int payloadType = buffer.get(pos + 4) & 0xff;
    277 
    278                     // ATSC closed caption data embedded in H264 private user data has NAL type 6,
    279                     // payload type 4, and 'GA94' user identifier for ATSC.
    280                     if (nalType == 6 && payloadType == 4 && buffer.get(pos + 9) == 'G'
    281                             && buffer.get(pos + 10) == 'A'
    282                             && buffer.get(pos + 11) == '9'
    283                             && buffer.get(pos + 12) == '4') {
    284                         parseClosedCaption(buffer, pos + 14, presentationTimeUs);
    285                     }
    286                     pos += 7;
    287                 } else {
    288                     ++pos;
    289                 }
    290             }
    291         }
    292     }
    293 }
    294