• 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.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 
init()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      */
MpegTsSampleSourceExtractor(MediaDataSource source, CacheManager cacheManager, PlaybackCacheListener cacheListener)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      */
MpegTsSampleSourceExtractor(CacheManager cacheManager, PlaybackCacheListener cacheListener)88     public MpegTsSampleSourceExtractor(CacheManager cacheManager,
89             PlaybackCacheListener cacheListener) {
90         mSampleExtractor = new ReplaySampleSourceExtractor(cacheManager, cacheListener);
91         init();
92     }
93 
94     @Override
prepare()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
getTrackFormats()128     public MediaFormat[] getTrackFormats() {
129         return mTrackFormats;
130     }
131 
132     @Override
selectTrack(int index)133     public void selectTrack(int index) {
134         if (index == mCea708TextTrackIndex) {
135             mCea708TextTrackSelected = true;
136             return;
137         }
138         mSampleExtractor.selectTrack(index);
139     }
140 
141     @Override
deselectTrack(int index)142     public void deselectTrack(int index) {
143         if (index == mCea708TextTrackIndex) {
144             mCea708TextTrackSelected = false;
145             return;
146         }
147         mSampleExtractor.deselectTrack(index);
148     }
149 
150     @Override
getBufferedPositionUs()151     public long getBufferedPositionUs() {
152         return mSampleExtractor.getBufferedPositionUs();
153     }
154 
155     @Override
seekTo(long positionUs)156     public void seekTo(long positionUs) {
157         mSampleExtractor.seekTo(positionUs);
158     }
159 
160     @Override
getTrackMediaFormat(int track, MediaFormatHolder outMediaFormatHolder)161     public void getTrackMediaFormat(int track, MediaFormatHolder outMediaFormatHolder) {
162         if (track != mCea708TextTrackIndex) {
163             mSampleExtractor.getTrackMediaFormat(track, outMediaFormatHolder);
164         }
165     }
166 
167     @Override
readSample(int track, SampleHolder sampleHolder)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
release()205     public void release() {
206         mSampleExtractor.release();
207         mVideoTrackIndex = -1;
208         mCea708TextTrackIndex = -1;
209         mCea708TextTrackSelected = false;
210     }
211 
212     @Override
continueBuffering(long positionUs)213     public boolean continueBuffering(long positionUs) {
214         return mSampleExtractor.continueBuffering(positionUs);
215     }
216 
217     private abstract class CcParser {
mayParseClosedCaption(ByteBuffer buffer, long presentationTimeUs)218         abstract void mayParseClosedCaption(ByteBuffer buffer, long presentationTimeUs);
219 
parseClosedCaption(ByteBuffer buffer, int offset, long presentationTimeUs)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
mayParseClosedCaption(ByteBuffer buffer, long presentationTimeUs)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
mayParseClosedCaption(ByteBuffer buffer, long presentationTimeUs)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