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