1 /*
2  * Copyright (C) 2016 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 package org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.ts;
17 
18 import android.util.Pair;
19 import org.mozilla.thirdparty.com.google.android.exoplayer2.C;
20 import org.mozilla.thirdparty.com.google.android.exoplayer2.Format;
21 import org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.ExtractorOutput;
22 import org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.TrackOutput;
23 import org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
24 import org.mozilla.thirdparty.com.google.android.exoplayer2.util.MimeTypes;
25 import org.mozilla.thirdparty.com.google.android.exoplayer2.util.NalUnitUtil;
26 import org.mozilla.thirdparty.com.google.android.exoplayer2.util.ParsableByteArray;
27 import java.util.Arrays;
28 import java.util.Collections;
29 
30 /**
31  * Parses a continuous H262 byte stream and extracts individual frames.
32  */
33 public final class H262Reader implements ElementaryStreamReader {
34 
35   private static final int START_PICTURE = 0x00;
36   private static final int START_SEQUENCE_HEADER = 0xB3;
37   private static final int START_EXTENSION = 0xB5;
38   private static final int START_GROUP = 0xB8;
39   private static final int START_USER_DATA = 0xB2;
40 
41   private String formatId;
42   private TrackOutput output;
43 
44   // Maps (frame_rate_code - 1) indices to values, as defined in ITU-T H.262 Table 6-4.
45   private static final double[] FRAME_RATE_VALUES = new double[] {
46       24000d / 1001, 24, 25, 30000d / 1001, 30, 50, 60000d / 1001, 60};
47 
48   // State that should not be reset on seek.
49   private boolean hasOutputFormat;
50   private long frameDurationUs;
51 
52   private final UserDataReader userDataReader;
53   private final ParsableByteArray userDataParsable;
54 
55   // State that should be reset on seek.
56   private final boolean[] prefixFlags;
57   private final CsdBuffer csdBuffer;
58   private final NalUnitTargetBuffer userData;
59   private long totalBytesWritten;
60   private boolean startedFirstSample;
61 
62   // Per packet state that gets reset at the start of each packet.
63   private long pesTimeUs;
64 
65   // Per sample state that gets reset at the start of each sample.
66   private long samplePosition;
67   private long sampleTimeUs;
68   private boolean sampleIsKeyframe;
69   private boolean sampleHasPicture;
70 
H262Reader()71   public H262Reader() {
72     this(null);
73   }
74 
H262Reader(UserDataReader userDataReader)75   /* package */ H262Reader(UserDataReader userDataReader) {
76     this.userDataReader = userDataReader;
77     prefixFlags = new boolean[4];
78     csdBuffer = new CsdBuffer(128);
79     if (userDataReader != null) {
80       userData = new NalUnitTargetBuffer(START_USER_DATA, 128);
81       userDataParsable = new ParsableByteArray();
82     } else {
83       userData = null;
84       userDataParsable = null;
85     }
86   }
87 
88   @Override
seek()89   public void seek() {
90     NalUnitUtil.clearPrefixFlags(prefixFlags);
91     csdBuffer.reset();
92     if (userDataReader != null) {
93       userData.reset();
94     }
95     totalBytesWritten = 0;
96     startedFirstSample = false;
97   }
98 
99   @Override
createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator)100   public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
101     idGenerator.generateNewId();
102     formatId = idGenerator.getFormatId();
103     output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_VIDEO);
104     if (userDataReader != null) {
105       userDataReader.createTracks(extractorOutput, idGenerator);
106     }
107   }
108 
109   @Override
packetStarted(long pesTimeUs, @TsPayloadReader.Flags int flags)110   public void packetStarted(long pesTimeUs, @TsPayloadReader.Flags int flags) {
111     // TODO (Internal b/32267012): Consider using random access indicator.
112     this.pesTimeUs = pesTimeUs;
113   }
114 
115   @Override
consume(ParsableByteArray data)116   public void consume(ParsableByteArray data) {
117     int offset = data.getPosition();
118     int limit = data.limit();
119     byte[] dataArray = data.data;
120 
121     // Append the data to the buffer.
122     totalBytesWritten += data.bytesLeft();
123     output.sampleData(data, data.bytesLeft());
124 
125     while (true) {
126       int startCodeOffset = NalUnitUtil.findNalUnit(dataArray, offset, limit, prefixFlags);
127 
128       if (startCodeOffset == limit) {
129         // We've scanned to the end of the data without finding another start code.
130         if (!hasOutputFormat) {
131           csdBuffer.onData(dataArray, offset, limit);
132         }
133         if (userDataReader != null) {
134           userData.appendToNalUnit(dataArray, offset, limit);
135         }
136         return;
137       }
138 
139       // We've found a start code with the following value.
140       int startCodeValue = data.data[startCodeOffset + 3] & 0xFF;
141       // This is the number of bytes from the current offset to the start of the next start
142       // code. It may be negative if the start code started in the previously consumed data.
143       int lengthToStartCode = startCodeOffset - offset;
144 
145       if (!hasOutputFormat) {
146         if (lengthToStartCode > 0) {
147           csdBuffer.onData(dataArray, offset, startCodeOffset);
148         }
149         // This is the number of bytes belonging to the next start code that have already been
150         // passed to csdBuffer.
151         int bytesAlreadyPassed = lengthToStartCode < 0 ? -lengthToStartCode : 0;
152         if (csdBuffer.onStartCode(startCodeValue, bytesAlreadyPassed)) {
153           // The csd data is complete, so we can decode and output the media format.
154           Pair<Format, Long> result = parseCsdBuffer(csdBuffer, formatId);
155           output.format(result.first);
156           frameDurationUs = result.second;
157           hasOutputFormat = true;
158         }
159       }
160       if (userDataReader != null) {
161         int bytesAlreadyPassed = 0;
162         if (lengthToStartCode > 0) {
163           userData.appendToNalUnit(dataArray, offset, startCodeOffset);
164         } else {
165           bytesAlreadyPassed = -lengthToStartCode;
166         }
167 
168         if (userData.endNalUnit(bytesAlreadyPassed)) {
169           int unescapedLength = NalUnitUtil.unescapeStream(userData.nalData, userData.nalLength);
170           userDataParsable.reset(userData.nalData, unescapedLength);
171           userDataReader.consume(sampleTimeUs, userDataParsable);
172         }
173 
174         if (startCodeValue == START_USER_DATA && data.data[startCodeOffset + 2] == 0x1) {
175           userData.startNalUnit(startCodeValue);
176         }
177       }
178       if (startCodeValue == START_PICTURE || startCodeValue == START_SEQUENCE_HEADER) {
179         int bytesWrittenPastStartCode = limit - startCodeOffset;
180         if (startedFirstSample && sampleHasPicture && hasOutputFormat) {
181           // Output the sample.
182           @C.BufferFlags int flags = sampleIsKeyframe ? C.BUFFER_FLAG_KEY_FRAME : 0;
183           int size = (int) (totalBytesWritten - samplePosition) - bytesWrittenPastStartCode;
184           output.sampleMetadata(sampleTimeUs, flags, size, bytesWrittenPastStartCode, null);
185         }
186         if (!startedFirstSample || sampleHasPicture) {
187           // Start the next sample.
188           samplePosition = totalBytesWritten - bytesWrittenPastStartCode;
189           sampleTimeUs = pesTimeUs != C.TIME_UNSET ? pesTimeUs
190               : (startedFirstSample ? (sampleTimeUs + frameDurationUs) : 0);
191           sampleIsKeyframe = false;
192           pesTimeUs = C.TIME_UNSET;
193           startedFirstSample = true;
194         }
195         sampleHasPicture = startCodeValue == START_PICTURE;
196       } else if (startCodeValue == START_GROUP) {
197         sampleIsKeyframe = true;
198       }
199 
200       offset = startCodeOffset + 3;
201     }
202   }
203 
204   @Override
packetFinished()205   public void packetFinished() {
206     // Do nothing.
207   }
208 
209   /**
210    * Parses the {@link Format} and frame duration from a csd buffer.
211    *
212    * @param csdBuffer The csd buffer.
213    * @param formatId The id for the generated format. May be null.
214    * @return A pair consisting of the {@link Format} and the frame duration in microseconds, or
215    *     0 if the duration could not be determined.
216    */
parseCsdBuffer(CsdBuffer csdBuffer, String formatId)217   private static Pair<Format, Long> parseCsdBuffer(CsdBuffer csdBuffer, String formatId) {
218     byte[] csdData = Arrays.copyOf(csdBuffer.data, csdBuffer.length);
219 
220     int firstByte = csdData[4] & 0xFF;
221     int secondByte = csdData[5] & 0xFF;
222     int thirdByte = csdData[6] & 0xFF;
223     int width = (firstByte << 4) | (secondByte >> 4);
224     int height = (secondByte & 0x0F) << 8 | thirdByte;
225 
226     float pixelWidthHeightRatio = 1f;
227     int aspectRatioCode = (csdData[7] & 0xF0) >> 4;
228     switch(aspectRatioCode) {
229       case 2:
230         pixelWidthHeightRatio = (4 * height) / (float) (3 * width);
231         break;
232       case 3:
233         pixelWidthHeightRatio = (16 * height) / (float) (9 * width);
234         break;
235       case 4:
236         pixelWidthHeightRatio = (121 * height) / (float) (100 * width);
237         break;
238       default:
239         // Do nothing.
240         break;
241     }
242 
243     Format format = Format.createVideoSampleFormat(formatId, MimeTypes.VIDEO_MPEG2, null,
244         Format.NO_VALUE, Format.NO_VALUE, width, height, Format.NO_VALUE,
245         Collections.singletonList(csdData), Format.NO_VALUE, pixelWidthHeightRatio, null);
246 
247     long frameDurationUs = 0;
248     int frameRateCodeMinusOne = (csdData[7] & 0x0F) - 1;
249     if (0 <= frameRateCodeMinusOne && frameRateCodeMinusOne < FRAME_RATE_VALUES.length) {
250       double frameRate = FRAME_RATE_VALUES[frameRateCodeMinusOne];
251       int sequenceExtensionPosition = csdBuffer.sequenceExtensionPosition;
252       int frameRateExtensionN = (csdData[sequenceExtensionPosition + 9] & 0x60) >> 5;
253       int frameRateExtensionD = (csdData[sequenceExtensionPosition + 9] & 0x1F);
254       if (frameRateExtensionN != frameRateExtensionD) {
255         frameRate *= (frameRateExtensionN + 1d) / (frameRateExtensionD + 1);
256       }
257       frameDurationUs = (long) (C.MICROS_PER_SECOND / frameRate);
258     }
259 
260     return Pair.create(format, frameDurationUs);
261   }
262 
263   private static final class CsdBuffer {
264 
265     private static final byte[] START_CODE = new byte[] {0, 0, 1};
266 
267     private boolean isFilling;
268 
269     public int length;
270     public int sequenceExtensionPosition;
271     public byte[] data;
272 
CsdBuffer(int initialCapacity)273     public CsdBuffer(int initialCapacity) {
274       data = new byte[initialCapacity];
275     }
276 
277     /**
278      * Resets the buffer, clearing any data that it holds.
279      */
reset()280     public void reset() {
281       isFilling = false;
282       length = 0;
283       sequenceExtensionPosition = 0;
284     }
285 
286     /**
287      * Called when a start code is encountered in the stream.
288      *
289      * @param startCodeValue The start code value.
290      * @param bytesAlreadyPassed The number of bytes of the start code that have been passed to
291      *     {@link #onData(byte[], int, int)}, or 0.
292      * @return Whether the csd data is now complete. If true is returned, neither
293      *     this method nor {@link #onData(byte[], int, int)} should be called again without an
294      *     interleaving call to {@link #reset()}.
295      */
onStartCode(int startCodeValue, int bytesAlreadyPassed)296     public boolean onStartCode(int startCodeValue, int bytesAlreadyPassed) {
297       if (isFilling) {
298         length -= bytesAlreadyPassed;
299         if (sequenceExtensionPosition == 0 && startCodeValue == START_EXTENSION) {
300           sequenceExtensionPosition = length;
301         } else {
302           isFilling = false;
303           return true;
304         }
305       } else if (startCodeValue == START_SEQUENCE_HEADER) {
306         isFilling = true;
307       }
308       onData(START_CODE, 0, START_CODE.length);
309       return false;
310     }
311 
312     /**
313      * Called to pass stream data.
314      *
315      * @param newData Holds the data being passed.
316      * @param offset The offset of the data in {@code data}.
317      * @param limit The limit (exclusive) of the data in {@code data}.
318      */
onData(byte[] newData, int offset, int limit)319     public void onData(byte[] newData, int offset, int limit) {
320       if (!isFilling) {
321         return;
322       }
323       int readLength = limit - offset;
324       if (data.length < length + readLength) {
325         data = Arrays.copyOf(data, (length + readLength) * 2);
326       }
327       System.arraycopy(newData, offset, data, length, readLength);
328       length += readLength;
329     }
330 
331   }
332 
333 }
334