1 /* 2 * Copyright (C) 2019 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 android.media; 17 18 import android.annotation.CheckResult; 19 import android.annotation.IntDef; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.annotation.StringDef; 23 import android.media.MediaCodec.CryptoInfo; 24 import android.media.metrics.LogSessionId; 25 import android.os.Build; 26 import android.text.TextUtils; 27 import android.util.Log; 28 import android.util.Pair; 29 import android.util.SparseArray; 30 31 import androidx.annotation.RequiresApi; 32 33 import com.android.modules.utils.build.SdkLevel; 34 35 import com.google.android.exoplayer2.C; 36 import com.google.android.exoplayer2.Format; 37 import com.google.android.exoplayer2.ParserException; 38 import com.google.android.exoplayer2.drm.DrmInitData.SchemeData; 39 import com.google.android.exoplayer2.extractor.ChunkIndex; 40 import com.google.android.exoplayer2.extractor.DefaultExtractorInput; 41 import com.google.android.exoplayer2.extractor.Extractor; 42 import com.google.android.exoplayer2.extractor.ExtractorInput; 43 import com.google.android.exoplayer2.extractor.ExtractorOutput; 44 import com.google.android.exoplayer2.extractor.PositionHolder; 45 import com.google.android.exoplayer2.extractor.SeekMap.SeekPoints; 46 import com.google.android.exoplayer2.extractor.TrackOutput; 47 import com.google.android.exoplayer2.extractor.amr.AmrExtractor; 48 import com.google.android.exoplayer2.extractor.flac.FlacExtractor; 49 import com.google.android.exoplayer2.extractor.flv.FlvExtractor; 50 import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor; 51 import com.google.android.exoplayer2.extractor.mp3.Mp3Extractor; 52 import com.google.android.exoplayer2.extractor.mp4.FragmentedMp4Extractor; 53 import com.google.android.exoplayer2.extractor.mp4.Mp4Extractor; 54 import com.google.android.exoplayer2.extractor.ogg.OggExtractor; 55 import com.google.android.exoplayer2.extractor.ts.Ac3Extractor; 56 import com.google.android.exoplayer2.extractor.ts.Ac4Extractor; 57 import com.google.android.exoplayer2.extractor.ts.AdtsExtractor; 58 import com.google.android.exoplayer2.extractor.ts.DefaultTsPayloadReaderFactory; 59 import com.google.android.exoplayer2.extractor.ts.PsExtractor; 60 import com.google.android.exoplayer2.extractor.ts.TsExtractor; 61 import com.google.android.exoplayer2.extractor.wav.WavExtractor; 62 import com.google.android.exoplayer2.upstream.DataReader; 63 import com.google.android.exoplayer2.util.ParsableByteArray; 64 import com.google.android.exoplayer2.util.TimestampAdjuster; 65 import com.google.android.exoplayer2.video.ColorInfo; 66 import com.google.common.base.Ascii; 67 68 import java.io.EOFException; 69 import java.io.IOException; 70 import java.lang.annotation.Retention; 71 import java.lang.annotation.RetentionPolicy; 72 import java.lang.reflect.Constructor; 73 import java.lang.reflect.InvocationTargetException; 74 import java.nio.ByteBuffer; 75 import java.util.ArrayList; 76 import java.util.Arrays; 77 import java.util.Collections; 78 import java.util.HashMap; 79 import java.util.LinkedHashMap; 80 import java.util.List; 81 import java.util.Map; 82 import java.util.Objects; 83 import java.util.UUID; 84 import java.util.concurrent.ThreadLocalRandom; 85 import java.util.function.Function; 86 87 /** 88 * Parses media container formats and extracts contained media samples and metadata. 89 * 90 * <p>This class provides access to a battery of low-level media container parsers. Each instance of 91 * this class is associated to a specific media parser implementation which is suitable for 92 * extraction from a specific media container format. The media parser implementation assignment 93 * depends on the factory method (see {@link #create} and {@link #createByName}) used to create the 94 * instance. 95 * 96 * <p>Users must implement the following to use this class. 97 * 98 * <ul> 99 * <li>{@link InputReader}: Provides the media container's bytes to parse. 100 * <li>{@link OutputConsumer}: Provides a sink for all extracted data and metadata. 101 * </ul> 102 * 103 * <p>The following code snippet includes a usage example: 104 * 105 * <pre> 106 * MyOutputConsumer myOutputConsumer = new MyOutputConsumer(); 107 * MyInputReader myInputReader = new MyInputReader("www.example.com"); 108 * MediaParser mediaParser = MediaParser.create(myOutputConsumer); 109 * 110 * while (mediaParser.advance(myInputReader)) {} 111 * 112 * mediaParser.release(); 113 * mediaParser = null; 114 * </pre> 115 * 116 * <p>The following code snippet provides a rudimentary {@link OutputConsumer} sample implementation 117 * which extracts and publishes all video samples: 118 * 119 * <pre> 120 * class VideoOutputConsumer implements MediaParser.OutputConsumer { 121 * 122 * private byte[] sampleDataBuffer = new byte[4096]; 123 * private byte[] discardedDataBuffer = new byte[4096]; 124 * private int videoTrackIndex = -1; 125 * private int bytesWrittenCount = 0; 126 * 127 * @Override 128 * public void onSeekMapFound(int i, @NonNull MediaFormat mediaFormat) { 129 * // Do nothing. 130 * } 131 * 132 * @Override 133 * public void onTrackDataFound(int i, @NonNull TrackData trackData) { 134 * MediaFormat mediaFormat = trackData.mediaFormat; 135 * if (videoTrackIndex == -1 && 136 * mediaFormat 137 * .getString(MediaFormat.KEY_MIME, /* defaultValue= */ "") 138 * .startsWith("video/")) { 139 * videoTrackIndex = i; 140 * } 141 * } 142 * 143 * @Override 144 * public void onSampleDataFound(int trackIndex, @NonNull InputReader inputReader) 145 * throws IOException { 146 * int numberOfBytesToRead = (int) inputReader.getLength(); 147 * if (videoTrackIndex != trackIndex) { 148 * // Discard contents. 149 * inputReader.read( 150 * discardedDataBuffer, 151 * /* offset= */ 0, 152 * Math.min(discardDataBuffer.length, numberOfBytesToRead)); 153 * } else { 154 * ensureSpaceInBuffer(numberOfBytesToRead); 155 * int bytesRead = inputReader.read( 156 * sampleDataBuffer, bytesWrittenCount, numberOfBytesToRead); 157 * bytesWrittenCount += bytesRead; 158 * } 159 * } 160 * 161 * @Override 162 * public void onSampleCompleted( 163 * int trackIndex, 164 * long timeMicros, 165 * int flags, 166 * int size, 167 * int offset, 168 * @Nullable CryptoInfo cryptoData) { 169 * if (videoTrackIndex != trackIndex) { 170 * return; // It's not the video track. Ignore. 171 * } 172 * byte[] sampleData = new byte[size]; 173 * int sampleStartOffset = bytesWrittenCount - size - offset; 174 * System.arraycopy( 175 * sampleDataBuffer, 176 * sampleStartOffset, 177 * sampleData, 178 * /* destPos= */ 0, 179 * size); 180 * // Place trailing bytes at the start of the buffer. 181 * System.arraycopy( 182 * sampleDataBuffer, 183 * bytesWrittenCount - offset, 184 * sampleDataBuffer, 185 * /* destPos= */ 0, 186 * /* size= */ offset); 187 * bytesWrittenCount = bytesWrittenCount - offset; 188 * publishSample(sampleData, timeMicros, flags); 189 * } 190 * 191 * private void ensureSpaceInBuffer(int numberOfBytesToRead) { 192 * int requiredLength = bytesWrittenCount + numberOfBytesToRead; 193 * if (requiredLength > sampleDataBuffer.length) { 194 * sampleDataBuffer = Arrays.copyOf(sampleDataBuffer, requiredLength); 195 * } 196 * } 197 * 198 * } 199 * 200 * </pre> 201 */ 202 @RequiresApi(Build.VERSION_CODES.R) 203 public final class MediaParser { 204 205 /** 206 * Maps seek positions to {@link SeekPoint SeekPoints} in the stream. 207 * 208 * <p>A {@link SeekPoint} is a position in the stream from which a player may successfully start 209 * playing media samples. 210 */ 211 public static final class SeekMap { 212 213 /** Returned by {@link #getDurationMicros()} when the duration is unknown. */ 214 public static final int UNKNOWN_DURATION = Integer.MIN_VALUE; 215 216 /** 217 * For each {@link #getSeekPoints} call, returns a single {@link SeekPoint} whose {@link 218 * SeekPoint#timeMicros} matches the requested timestamp, and whose {@link 219 * SeekPoint#position} is 0. 220 * 221 * @hide 222 */ 223 public static final SeekMap DUMMY = new SeekMap(new DummyExoPlayerSeekMap()); 224 225 private final com.google.android.exoplayer2.extractor.SeekMap mExoPlayerSeekMap; 226 SeekMap(com.google.android.exoplayer2.extractor.SeekMap exoplayerSeekMap)227 private SeekMap(com.google.android.exoplayer2.extractor.SeekMap exoplayerSeekMap) { 228 mExoPlayerSeekMap = exoplayerSeekMap; 229 } 230 231 /** Returns whether seeking is supported. */ isSeekable()232 public boolean isSeekable() { 233 return mExoPlayerSeekMap.isSeekable(); 234 } 235 236 /** 237 * Returns the duration of the stream in microseconds or {@link #UNKNOWN_DURATION} if the 238 * duration is unknown. 239 */ getDurationMicros()240 public long getDurationMicros() { 241 long durationUs = mExoPlayerSeekMap.getDurationUs(); 242 return durationUs != C.TIME_UNSET ? durationUs : UNKNOWN_DURATION; 243 } 244 245 /** 246 * Obtains {@link SeekPoint SeekPoints} for the specified seek time in microseconds. 247 * 248 * <p>{@code getSeekPoints(timeMicros).first} contains the latest seek point for samples 249 * with timestamp equal to or smaller than {@code timeMicros}. 250 * 251 * <p>{@code getSeekPoints(timeMicros).second} contains the earliest seek point for samples 252 * with timestamp equal to or greater than {@code timeMicros}. If a seek point exists for 253 * {@code timeMicros}, the returned pair will contain the same {@link SeekPoint} twice. 254 * 255 * @param timeMicros A seek time in microseconds. 256 * @return The corresponding {@link SeekPoint SeekPoints}. 257 */ 258 @NonNull getSeekPoints(long timeMicros)259 public Pair<SeekPoint, SeekPoint> getSeekPoints(long timeMicros) { 260 SeekPoints seekPoints = mExoPlayerSeekMap.getSeekPoints(timeMicros); 261 return new Pair<>(toSeekPoint(seekPoints.first), toSeekPoint(seekPoints.second)); 262 } 263 } 264 265 /** Holds information associated with a track. */ 266 public static final class TrackData { 267 268 /** Holds {@link MediaFormat} information for the track. */ 269 @NonNull public final MediaFormat mediaFormat; 270 271 /** 272 * Holds {@link DrmInitData} necessary to acquire keys associated with the track, or null if 273 * the track has no encryption data. 274 */ 275 @Nullable public final DrmInitData drmInitData; 276 TrackData(MediaFormat mediaFormat, DrmInitData drmInitData)277 private TrackData(MediaFormat mediaFormat, DrmInitData drmInitData) { 278 this.mediaFormat = mediaFormat; 279 this.drmInitData = drmInitData; 280 } 281 } 282 283 /** Defines a seek point in a media stream. */ 284 public static final class SeekPoint { 285 286 /** A {@link SeekPoint} whose time and byte offset are both set to 0. */ 287 @NonNull public static final SeekPoint START = new SeekPoint(0, 0); 288 289 /** The time of the seek point, in microseconds. */ 290 public final long timeMicros; 291 292 /** The byte offset of the seek point. */ 293 public final long position; 294 295 /** 296 * @param timeMicros The time of the seek point, in microseconds. 297 * @param position The byte offset of the seek point. 298 */ SeekPoint(long timeMicros, long position)299 private SeekPoint(long timeMicros, long position) { 300 this.timeMicros = timeMicros; 301 this.position = position; 302 } 303 304 @Override 305 @NonNull toString()306 public String toString() { 307 return "[timeMicros=" + timeMicros + ", position=" + position + "]"; 308 } 309 310 @Override equals(@ullable Object obj)311 public boolean equals(@Nullable Object obj) { 312 if (this == obj) { 313 return true; 314 } 315 if (obj == null || getClass() != obj.getClass()) { 316 return false; 317 } 318 SeekPoint other = (SeekPoint) obj; 319 return timeMicros == other.timeMicros && position == other.position; 320 } 321 322 @Override hashCode()323 public int hashCode() { 324 int result = (int) timeMicros; 325 result = 31 * result + (int) position; 326 return result; 327 } 328 } 329 330 /** Provides input data to {@link MediaParser}. */ 331 public interface InputReader { 332 333 /** 334 * Reads up to {@code readLength} bytes of data and stores them into {@code buffer}, 335 * starting at index {@code offset}. 336 * 337 * <p>This method blocks until at least one byte is read, the end of input is detected, or 338 * an exception is thrown. The read position advances to the first unread byte. 339 * 340 * @param buffer The buffer into which the read data should be stored. 341 * @param offset The start offset into {@code buffer} at which data should be written. 342 * @param readLength The maximum number of bytes to read. 343 * @return The non-zero number of bytes read, or -1 if no data is available because the end 344 * of the input has been reached. 345 * @throws java.io.IOException If an error occurs reading from the source. 346 */ read(@onNull byte[] buffer, int offset, int readLength)347 int read(@NonNull byte[] buffer, int offset, int readLength) throws IOException; 348 349 /** Returns the current read position (byte offset) in the stream. */ getPosition()350 long getPosition(); 351 352 /** Returns the length of the input in bytes, or -1 if the length is unknown. */ getLength()353 long getLength(); 354 } 355 356 /** {@link InputReader} that allows setting the read position. */ 357 public interface SeekableInputReader extends InputReader { 358 359 /** 360 * Sets the read position at the given {@code position}. 361 * 362 * <p>{@link #advance} will immediately return after calling this method. 363 * 364 * @param position The position to seek to, in bytes. 365 */ seekToPosition(long position)366 void seekToPosition(long position); 367 } 368 369 /** Receives extracted media sample data and metadata from {@link MediaParser}. */ 370 public interface OutputConsumer { 371 372 /** 373 * Called when a {@link SeekMap} has been extracted from the stream. 374 * 375 * <p>This method is called at least once before any samples are {@link #onSampleCompleted 376 * complete}. May be called multiple times after that in order to add {@link SeekPoint 377 * SeekPoints}. 378 * 379 * @param seekMap The extracted {@link SeekMap}. 380 */ onSeekMapFound(@onNull SeekMap seekMap)381 void onSeekMapFound(@NonNull SeekMap seekMap); 382 383 /** 384 * Called when the number of tracks is found. 385 * 386 * @param numberOfTracks The number of tracks in the stream. 387 */ onTrackCountFound(int numberOfTracks)388 void onTrackCountFound(int numberOfTracks); 389 390 /** 391 * Called when new {@link TrackData} is found in the stream. 392 * 393 * @param trackIndex The index of the track for which the {@link TrackData} was extracted. 394 * @param trackData The extracted {@link TrackData}. 395 */ onTrackDataFound(int trackIndex, @NonNull TrackData trackData)396 void onTrackDataFound(int trackIndex, @NonNull TrackData trackData); 397 398 /** 399 * Called when sample data is found in the stream. 400 * 401 * <p>If the invocation of this method returns before the entire {@code inputReader} {@link 402 * InputReader#getLength() length} is consumed, the method will be called again for the 403 * implementer to read the remaining data. Implementers should surface any thrown {@link 404 * IOException} caused by reading from {@code input}. 405 * 406 * @param trackIndex The index of the track to which the sample data corresponds. 407 * @param inputReader The {@link InputReader} from which to read the data. 408 * @throws IOException If an exception occurs while reading from {@code inputReader}. 409 */ onSampleDataFound(int trackIndex, @NonNull InputReader inputReader)410 void onSampleDataFound(int trackIndex, @NonNull InputReader inputReader) throws IOException; 411 412 /** 413 * Called once all the data of a sample has been passed to {@link #onSampleDataFound}. 414 * 415 * <p>Includes sample metadata, like presentation timestamp and flags. 416 * 417 * @param trackIndex The index of the track to which the sample corresponds. 418 * @param timeMicros The media timestamp associated with the sample, in microseconds. 419 * @param flags Flags associated with the sample. See the {@code SAMPLE_FLAG_*} constants. 420 * @param size The size of the sample data, in bytes. 421 * @param offset The number of bytes that have been consumed by {@code 422 * onSampleDataFound(int, MediaParser.InputReader)} for the specified track, since the 423 * last byte belonging to the sample whose metadata is being passed. 424 * @param cryptoInfo Encryption data required to decrypt the sample. May be null for 425 * unencrypted samples. Implementors should treat any output {@link CryptoInfo} 426 * instances as immutable. MediaParser will not modify any output {@code cryptoInfos} 427 * and implementors should not modify them either. 428 */ onSampleCompleted( int trackIndex, long timeMicros, @SampleFlags int flags, int size, int offset, @Nullable CryptoInfo cryptoInfo)429 void onSampleCompleted( 430 int trackIndex, 431 long timeMicros, 432 @SampleFlags int flags, 433 int size, 434 int offset, 435 @Nullable CryptoInfo cryptoInfo); 436 } 437 438 /** 439 * Thrown if all parser implementations provided to {@link #create} failed to sniff the input 440 * content. 441 */ 442 public static final class UnrecognizedInputFormatException extends IOException { 443 444 /** 445 * Creates a new instance which signals that the parsers with the given names failed to 446 * parse the input. 447 */ 448 @NonNull 449 @CheckResult createForExtractors( @onNull String... extractorNames)450 private static UnrecognizedInputFormatException createForExtractors( 451 @NonNull String... extractorNames) { 452 StringBuilder builder = new StringBuilder(); 453 builder.append("None of the available parsers ( "); 454 builder.append(extractorNames[0]); 455 for (int i = 1; i < extractorNames.length; i++) { 456 builder.append(", "); 457 builder.append(extractorNames[i]); 458 } 459 builder.append(") could read the stream."); 460 return new UnrecognizedInputFormatException(builder.toString()); 461 } 462 UnrecognizedInputFormatException(String extractorNames)463 private UnrecognizedInputFormatException(String extractorNames) { 464 super(extractorNames); 465 } 466 } 467 468 /** Thrown when an error occurs while parsing a media stream. */ 469 public static final class ParsingException extends IOException { 470 ParsingException(ParserException cause)471 private ParsingException(ParserException cause) { 472 super(cause); 473 } 474 } 475 476 // Sample flags. 477 478 /** @hide */ 479 @Retention(RetentionPolicy.SOURCE) 480 @IntDef( 481 flag = true, 482 value = { 483 SAMPLE_FLAG_KEY_FRAME, 484 SAMPLE_FLAG_HAS_SUPPLEMENTAL_DATA, 485 SAMPLE_FLAG_LAST_SAMPLE, 486 SAMPLE_FLAG_ENCRYPTED, 487 SAMPLE_FLAG_DECODE_ONLY 488 }) 489 public @interface SampleFlags {} 490 /** Indicates that the sample holds a synchronization sample. */ 491 public static final int SAMPLE_FLAG_KEY_FRAME = MediaCodec.BUFFER_FLAG_KEY_FRAME; 492 /** 493 * Indicates that the sample has supplemental data. 494 * 495 * <p>Samples will not have this flag set unless the {@code 496 * "android.media.mediaparser.includeSupplementalData"} parameter is set to {@code true} via 497 * {@link #setParameter}. 498 * 499 * <p>Samples with supplemental data have the following sample data format: 500 * 501 * <ul> 502 * <li>If the {@code "android.media.mediaparser.inBandCryptoInfo"} parameter is set, all 503 * encryption information. 504 * <li>(4 bytes) {@code sample_data_size}: The size of the actual sample data, not including 505 * supplemental data or encryption information. 506 * <li>({@code sample_data_size} bytes): The media sample data. 507 * <li>(remaining bytes) The supplemental data. 508 * </ul> 509 */ 510 public static final int SAMPLE_FLAG_HAS_SUPPLEMENTAL_DATA = 1 << 28; 511 /** Indicates that the sample is known to contain the last media sample of the stream. */ 512 public static final int SAMPLE_FLAG_LAST_SAMPLE = 1 << 29; 513 /** Indicates that the sample is (at least partially) encrypted. */ 514 public static final int SAMPLE_FLAG_ENCRYPTED = 1 << 30; 515 /** Indicates that the sample should be decoded but not rendered. */ 516 public static final int SAMPLE_FLAG_DECODE_ONLY = 1 << 31; 517 518 // Parser implementation names. 519 520 /** @hide */ 521 @Retention(RetentionPolicy.SOURCE) 522 @StringDef( 523 prefix = {"PARSER_NAME_"}, 524 value = { 525 PARSER_NAME_UNKNOWN, 526 PARSER_NAME_MATROSKA, 527 PARSER_NAME_FMP4, 528 PARSER_NAME_MP4, 529 PARSER_NAME_MP3, 530 PARSER_NAME_ADTS, 531 PARSER_NAME_AC3, 532 PARSER_NAME_TS, 533 PARSER_NAME_FLV, 534 PARSER_NAME_OGG, 535 PARSER_NAME_PS, 536 PARSER_NAME_WAV, 537 PARSER_NAME_AMR, 538 PARSER_NAME_AC4, 539 PARSER_NAME_FLAC 540 }) 541 public @interface ParserName {} 542 543 /** Parser name returned by {@link #getParserName()} when no parser has been selected yet. */ 544 public static final String PARSER_NAME_UNKNOWN = "android.media.mediaparser.UNKNOWN"; 545 /** 546 * Parser for the Matroska container format, as defined in the <a 547 * href="https://matroska.org/technical/specs/">spec</a>. 548 */ 549 public static final String PARSER_NAME_MATROSKA = "android.media.mediaparser.MatroskaParser"; 550 /** 551 * Parser for fragmented files using the MP4 container format, as defined in ISO/IEC 14496-12. 552 */ 553 public static final String PARSER_NAME_FMP4 = "android.media.mediaparser.FragmentedMp4Parser"; 554 /** 555 * Parser for non-fragmented files using the MP4 container format, as defined in ISO/IEC 556 * 14496-12. 557 */ 558 public static final String PARSER_NAME_MP4 = "android.media.mediaparser.Mp4Parser"; 559 /** Parser for the MP3 container format, as defined in ISO/IEC 11172-3. */ 560 public static final String PARSER_NAME_MP3 = "android.media.mediaparser.Mp3Parser"; 561 /** Parser for the ADTS container format, as defined in ISO/IEC 13818-7. */ 562 public static final String PARSER_NAME_ADTS = "android.media.mediaparser.AdtsParser"; 563 /** 564 * Parser for the AC-3 container format, as defined in Digital Audio Compression Standard 565 * (AC-3). 566 */ 567 public static final String PARSER_NAME_AC3 = "android.media.mediaparser.Ac3Parser"; 568 /** Parser for the TS container format, as defined in ISO/IEC 13818-1. */ 569 public static final String PARSER_NAME_TS = "android.media.mediaparser.TsParser"; 570 /** 571 * Parser for the FLV container format, as defined in Adobe Flash Video File Format 572 * Specification. 573 */ 574 public static final String PARSER_NAME_FLV = "android.media.mediaparser.FlvParser"; 575 /** Parser for the OGG container format, as defined in RFC 3533. */ 576 public static final String PARSER_NAME_OGG = "android.media.mediaparser.OggParser"; 577 /** Parser for the PS container format, as defined in ISO/IEC 11172-1. */ 578 public static final String PARSER_NAME_PS = "android.media.mediaparser.PsParser"; 579 /** 580 * Parser for the WAV container format, as defined in Multimedia Programming Interface and Data 581 * Specifications. 582 */ 583 public static final String PARSER_NAME_WAV = "android.media.mediaparser.WavParser"; 584 /** Parser for the AMR container format, as defined in RFC 4867. */ 585 public static final String PARSER_NAME_AMR = "android.media.mediaparser.AmrParser"; 586 /** 587 * Parser for the AC-4 container format, as defined by Dolby AC-4: Audio delivery for 588 * Next-Generation Entertainment Services. 589 */ 590 public static final String PARSER_NAME_AC4 = "android.media.mediaparser.Ac4Parser"; 591 /** 592 * Parser for the FLAC container format, as defined in the <a 593 * href="https://xiph.org/flac/">spec</a>. 594 */ 595 public static final String PARSER_NAME_FLAC = "android.media.mediaparser.FlacParser"; 596 597 // MediaParser parameters. 598 599 /** @hide */ 600 @Retention(RetentionPolicy.SOURCE) 601 @StringDef( 602 prefix = {"PARAMETER_"}, 603 value = { 604 PARAMETER_ADTS_ENABLE_CBR_SEEKING, 605 PARAMETER_AMR_ENABLE_CBR_SEEKING, 606 PARAMETER_FLAC_DISABLE_ID3, 607 PARAMETER_MP4_IGNORE_EDIT_LISTS, 608 PARAMETER_MP4_IGNORE_TFDT_BOX, 609 PARAMETER_MP4_TREAT_VIDEO_FRAMES_AS_KEYFRAMES, 610 PARAMETER_MATROSKA_DISABLE_CUES_SEEKING, 611 PARAMETER_MP3_DISABLE_ID3, 612 PARAMETER_MP3_ENABLE_CBR_SEEKING, 613 PARAMETER_MP3_ENABLE_INDEX_SEEKING, 614 PARAMETER_TS_MODE, 615 PARAMETER_TS_ALLOW_NON_IDR_AVC_KEYFRAMES, 616 PARAMETER_TS_IGNORE_AAC_STREAM, 617 PARAMETER_TS_IGNORE_AVC_STREAM, 618 PARAMETER_TS_IGNORE_SPLICE_INFO_STREAM, 619 PARAMETER_TS_DETECT_ACCESS_UNITS, 620 PARAMETER_TS_ENABLE_HDMV_DTS_AUDIO_STREAMS, 621 PARAMETER_IN_BAND_CRYPTO_INFO, 622 PARAMETER_INCLUDE_SUPPLEMENTAL_DATA 623 }) 624 public @interface ParameterName {} 625 626 /** 627 * Sets whether constant bitrate seeking should be enabled for ADTS parsing. {@code boolean} 628 * expected. Default value is {@code false}. 629 */ 630 public static final String PARAMETER_ADTS_ENABLE_CBR_SEEKING = 631 "android.media.mediaparser.adts.enableCbrSeeking"; 632 /** 633 * Sets whether constant bitrate seeking should be enabled for AMR. {@code boolean} expected. 634 * Default value is {@code false}. 635 */ 636 public static final String PARAMETER_AMR_ENABLE_CBR_SEEKING = 637 "android.media.mediaparser.amr.enableCbrSeeking"; 638 /** 639 * Sets whether the ID3 track should be disabled for FLAC. {@code boolean} expected. Default 640 * value is {@code false}. 641 */ 642 public static final String PARAMETER_FLAC_DISABLE_ID3 = 643 "android.media.mediaparser.flac.disableId3"; 644 /** 645 * Sets whether MP4 parsing should ignore edit lists. {@code boolean} expected. Default value is 646 * {@code false}. 647 */ 648 public static final String PARAMETER_MP4_IGNORE_EDIT_LISTS = 649 "android.media.mediaparser.mp4.ignoreEditLists"; 650 /** 651 * Sets whether MP4 parsing should ignore the tfdt box. {@code boolean} expected. Default value 652 * is {@code false}. 653 */ 654 public static final String PARAMETER_MP4_IGNORE_TFDT_BOX = 655 "android.media.mediaparser.mp4.ignoreTfdtBox"; 656 /** 657 * Sets whether MP4 parsing should treat all video frames as key frames. {@code boolean} 658 * expected. Default value is {@code false}. 659 */ 660 public static final String PARAMETER_MP4_TREAT_VIDEO_FRAMES_AS_KEYFRAMES = 661 "android.media.mediaparser.mp4.treatVideoFramesAsKeyframes"; 662 /** 663 * Sets whether Matroska parsing should avoid seeking to the cues element. {@code boolean} 664 * expected. Default value is {@code false}. 665 * 666 * <p>If this flag is enabled and the cues element occurs after the first cluster, then the 667 * media is treated as unseekable. 668 */ 669 public static final String PARAMETER_MATROSKA_DISABLE_CUES_SEEKING = 670 "android.media.mediaparser.matroska.disableCuesSeeking"; 671 /** 672 * Sets whether the ID3 track should be disabled for MP3. {@code boolean} expected. Default 673 * value is {@code false}. 674 */ 675 public static final String PARAMETER_MP3_DISABLE_ID3 = 676 "android.media.mediaparser.mp3.disableId3"; 677 /** 678 * Sets whether constant bitrate seeking should be enabled for MP3. {@code boolean} expected. 679 * Default value is {@code false}. 680 */ 681 public static final String PARAMETER_MP3_ENABLE_CBR_SEEKING = 682 "android.media.mediaparser.mp3.enableCbrSeeking"; 683 /** 684 * Sets whether MP3 parsing should generate a time-to-byte mapping. {@code boolean} expected. 685 * Default value is {@code false}. 686 * 687 * <p>Enabling this flag may require to scan a significant portion of the file to compute a seek 688 * point. Therefore, it should only be used if: 689 * 690 * <ul> 691 * <li>the file is small, or 692 * <li>the bitrate is variable (or the type of bitrate is unknown) and the seeking metadata 693 * provided in the file is not precise enough (or is not present). 694 * </ul> 695 */ 696 public static final String PARAMETER_MP3_ENABLE_INDEX_SEEKING = 697 "android.media.mediaparser.mp3.enableIndexSeeking"; 698 /** 699 * Sets the operation mode for TS parsing. {@code String} expected. Valid values are {@code 700 * "single_pmt"}, {@code "multi_pmt"}, and {@code "hls"}. Default value is {@code "single_pmt"}. 701 * 702 * <p>The operation modes alter the way TS behaves so that it can handle certain kinds of 703 * commonly-occurring malformed media. 704 * 705 * <ul> 706 * <li>{@code "single_pmt"}: Only the first found PMT is parsed. Others are ignored, even if 707 * more PMTs are declared in the PAT. 708 * <li>{@code "multi_pmt"}: Behave as described in ISO/IEC 13818-1. 709 * <li>{@code "hls"}: Enable {@code "single_pmt"} mode, and ignore continuity counters. 710 * </ul> 711 */ 712 public static final String PARAMETER_TS_MODE = "android.media.mediaparser.ts.mode"; 713 /** 714 * Sets whether TS should treat samples consisting of non-IDR I slices as synchronization 715 * samples (key-frames). {@code boolean} expected. Default value is {@code false}. 716 */ 717 public static final String PARAMETER_TS_ALLOW_NON_IDR_AVC_KEYFRAMES = 718 "android.media.mediaparser.ts.allowNonIdrAvcKeyframes"; 719 /** 720 * Sets whether TS parsing should ignore AAC elementary streams. {@code boolean} expected. 721 * Default value is {@code false}. 722 */ 723 public static final String PARAMETER_TS_IGNORE_AAC_STREAM = 724 "android.media.mediaparser.ts.ignoreAacStream"; 725 /** 726 * Sets whether TS parsing should ignore AVC elementary streams. {@code boolean} expected. 727 * Default value is {@code false}. 728 */ 729 public static final String PARAMETER_TS_IGNORE_AVC_STREAM = 730 "android.media.mediaparser.ts.ignoreAvcStream"; 731 /** 732 * Sets whether TS parsing should ignore splice information streams. {@code boolean} expected. 733 * Default value is {@code false}. 734 */ 735 public static final String PARAMETER_TS_IGNORE_SPLICE_INFO_STREAM = 736 "android.media.mediaparser.ts.ignoreSpliceInfoStream"; 737 /** 738 * Sets whether TS parsing should split AVC stream into access units based on slice headers. 739 * {@code boolean} expected. Default value is {@code false}. 740 * 741 * <p>This flag should be left disabled if the stream contains access units delimiters in order 742 * to avoid unnecessary computational costs. 743 */ 744 public static final String PARAMETER_TS_DETECT_ACCESS_UNITS = 745 "android.media.mediaparser.ts.ignoreDetectAccessUnits"; 746 /** 747 * Sets whether TS parsing should handle HDMV DTS audio streams. {@code boolean} expected. 748 * Default value is {@code false}. 749 * 750 * <p>Enabling this flag will disable the detection of SCTE subtitles. 751 */ 752 public static final String PARAMETER_TS_ENABLE_HDMV_DTS_AUDIO_STREAMS = 753 "android.media.mediaparser.ts.enableHdmvDtsAudioStreams"; 754 /** 755 * Sets whether encryption data should be sent in-band with the sample data, as per {@link 756 * OutputConsumer#onSampleDataFound}. {@code boolean} expected. Default value is {@code false}. 757 * 758 * <p>If this parameter is set, encrypted samples' data will be prefixed with the encryption 759 * information bytes. The format for in-band encryption information is: 760 * 761 * <ul> 762 * <li>(1 byte) {@code encryption_signal_byte}: Most significant bit signals whether the 763 * encryption data contains subsample encryption data. The remaining bits contain {@code 764 * initialization_vector_size}. 765 * <li>({@code initialization_vector_size} bytes) Initialization vector. 766 * <li>If subsample encryption data is present, as per {@code encryption_signal_byte}, the 767 * encryption data also contains: 768 * <ul> 769 * <li>(2 bytes) {@code subsample_encryption_data_length}. 770 * <li>({@code subsample_encryption_data_length * 6} bytes) Subsample encryption data 771 * (repeated {@code subsample_encryption_data_length} times): 772 * <ul> 773 * <li>(2 bytes) Size of a clear section in sample. 774 * <li>(4 bytes) Size of an encryption section in sample. 775 * </ul> 776 * </ul> 777 * </ul> 778 * 779 * @hide 780 */ 781 public static final String PARAMETER_IN_BAND_CRYPTO_INFO = 782 "android.media.mediaparser.inBandCryptoInfo"; 783 /** 784 * Sets whether supplemental data should be included as part of the sample data. {@code boolean} 785 * expected. Default value is {@code false}. See {@link #SAMPLE_FLAG_HAS_SUPPLEMENTAL_DATA} for 786 * information about the sample data format. 787 * 788 * @hide 789 */ 790 public static final String PARAMETER_INCLUDE_SUPPLEMENTAL_DATA = 791 "android.media.mediaparser.includeSupplementalData"; 792 /** 793 * Sets whether sample timestamps may start from non-zero offsets. {@code boolean} expected. 794 * Default value is {@code false}. 795 * 796 * <p>When set to true, sample timestamps will not be offset to start from zero, and the media 797 * provided timestamps will be used instead. For example, transport stream sample timestamps 798 * will not be converted to a zero-based timebase. 799 * 800 * @hide 801 */ 802 public static final String PARAMETER_IGNORE_TIMESTAMP_OFFSET = 803 "android.media.mediaparser.ignoreTimestampOffset"; 804 /** 805 * Sets whether each track type should be eagerly exposed. {@code boolean} expected. Default 806 * value is {@code false}. 807 * 808 * <p>When set to true, each track type will be eagerly exposed through a call to {@link 809 * OutputConsumer#onTrackDataFound} containing a single-value {@link MediaFormat}. The key for 810 * the track type is {@code "track-type-string"}, and the possible values are {@code "video"}, 811 * {@code "audio"}, {@code "text"}, {@code "metadata"}, and {@code "unknown"}. 812 * 813 * @hide 814 */ 815 public static final String PARAMETER_EAGERLY_EXPOSE_TRACKTYPE = 816 "android.media.mediaparser.eagerlyExposeTrackType"; 817 /** 818 * Sets whether a dummy {@link SeekMap} should be exposed before starting extraction. {@code 819 * boolean} expected. Default value is {@code false}. 820 * 821 * <p>For each {@link SeekMap#getSeekPoints} call, the dummy {@link SeekMap} returns a single 822 * {@link SeekPoint} whose {@link SeekPoint#timeMicros} matches the requested timestamp, and 823 * whose {@link SeekPoint#position} is 0. 824 * 825 * @hide 826 */ 827 public static final String PARAMETER_EXPOSE_DUMMY_SEEKMAP = 828 "android.media.mediaparser.exposeDummySeekMap"; 829 830 /** 831 * Sets whether chunk indices available in the extracted media should be exposed as {@link 832 * MediaFormat MediaFormats}. {@code boolean} expected. Default value is {@link false}. 833 * 834 * <p>When set to true, any information about media segmentation will be exposed as a {@link 835 * MediaFormat} (with track index 0) containing four {@link ByteBuffer} elements under the 836 * following keys: 837 * 838 * <ul> 839 * <li>"chunk-index-int-sizes": Contains {@code ints} representing the sizes in bytes of each 840 * of the media segments. 841 * <li>"chunk-index-long-offsets": Contains {@code longs} representing the byte offsets of 842 * each segment in the stream. 843 * <li>"chunk-index-long-us-durations": Contains {@code longs} representing the media duration 844 * of each segment, in microseconds. 845 * <li>"chunk-index-long-us-times": Contains {@code longs} representing the start time of each 846 * segment, in microseconds. 847 * </ul> 848 * 849 * @hide 850 */ 851 public static final String PARAMETER_EXPOSE_CHUNK_INDEX_AS_MEDIA_FORMAT = 852 "android.media.mediaParser.exposeChunkIndexAsMediaFormat"; 853 /** 854 * Sets a list of closed-caption {@link MediaFormat MediaFormats} that should be exposed as part 855 * of the extracted media. {@code List<MediaFormat>} expected. Default value is an empty list. 856 * 857 * <p>Expected keys in the {@link MediaFormat} are: 858 * 859 * <ul> 860 * <p>{@link MediaFormat#KEY_MIME}: Determine the type of captions (for example, 861 * application/cea-608). Mandatory. 862 * <p>{@link MediaFormat#KEY_CAPTION_SERVICE_NUMBER}: Determine the channel on which the 863 * captions are transmitted. Optional. 864 * </ul> 865 * 866 * @hide 867 */ 868 public static final String PARAMETER_EXPOSE_CAPTION_FORMATS = 869 "android.media.mediaParser.exposeCaptionFormats"; 870 /** 871 * Sets whether the value associated with {@link #PARAMETER_EXPOSE_CAPTION_FORMATS} should 872 * override any in-band caption service declarations. {@code boolean} expected. Default value is 873 * {@link false}. 874 * 875 * <p>When {@code false}, any present in-band caption services information will override the 876 * values associated with {@link #PARAMETER_EXPOSE_CAPTION_FORMATS}. 877 * 878 * @hide 879 */ 880 public static final String PARAMETER_OVERRIDE_IN_BAND_CAPTION_DECLARATIONS = 881 "android.media.mediaParser.overrideInBandCaptionDeclarations"; 882 /** 883 * Sets whether a track for EMSG events should be exposed in case of parsing a container that 884 * supports them. {@code boolean} expected. Default value is {@link false}. 885 * 886 * @hide 887 */ 888 public static final String PARAMETER_EXPOSE_EMSG_TRACK = 889 "android.media.mediaParser.exposeEmsgTrack"; 890 891 // Private constants. 892 893 private static final String TAG = "MediaParser"; 894 private static final String JNI_LIBRARY_NAME = "mediaparser-jni"; 895 private static final Map<String, ExtractorFactory> EXTRACTOR_FACTORIES_BY_NAME; 896 private static final Map<String, Class> EXPECTED_TYPE_BY_PARAMETER_NAME; 897 private static final String TS_MODE_SINGLE_PMT = "single_pmt"; 898 private static final String TS_MODE_MULTI_PMT = "multi_pmt"; 899 private static final String TS_MODE_HLS = "hls"; 900 private static final int BYTES_PER_SUBSAMPLE_ENCRYPTION_ENTRY = 6; 901 private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; 902 private static final String MEDIAMETRICS_ELEMENT_SEPARATOR = "|"; 903 private static final int MEDIAMETRICS_MAX_STRING_SIZE = 200; 904 private static final int MEDIAMETRICS_PARAMETER_LIST_MAX_LENGTH; 905 /** 906 * Intentional error introduced to reported metrics to prevent identification of the parsed 907 * media. Note: Increasing this value may cause older hostside CTS tests to fail. 908 */ 909 private static final float MEDIAMETRICS_DITHER = .02f; 910 911 @IntDef( 912 value = { 913 STATE_READING_SIGNAL_BYTE, 914 STATE_READING_INIT_VECTOR, 915 STATE_READING_SUBSAMPLE_ENCRYPTION_SIZE, 916 STATE_READING_SUBSAMPLE_ENCRYPTION_DATA 917 }) 918 private @interface EncryptionDataReadState {} 919 920 private static final int STATE_READING_SIGNAL_BYTE = 0; 921 private static final int STATE_READING_INIT_VECTOR = 1; 922 private static final int STATE_READING_SUBSAMPLE_ENCRYPTION_SIZE = 2; 923 private static final int STATE_READING_SUBSAMPLE_ENCRYPTION_DATA = 3; 924 925 // Instance creation methods. 926 927 /** 928 * Creates an instance backed by the parser with the given {@code name}. The returned instance 929 * will attempt parsing without sniffing the content. 930 * 931 * @param name The name of the parser that will be associated with the created instance. 932 * @param outputConsumer The {@link OutputConsumer} to which track data and samples are pushed. 933 * @return A new instance. 934 * @throws IllegalArgumentException If an invalid name is provided. 935 */ 936 @NonNull createByName( @onNull @arserName String name, @NonNull OutputConsumer outputConsumer)937 public static MediaParser createByName( 938 @NonNull @ParserName String name, @NonNull OutputConsumer outputConsumer) { 939 String[] nameAsArray = new String[] {name}; 940 assertValidNames(nameAsArray); 941 return new MediaParser(outputConsumer, /* createdByName= */ true, name); 942 } 943 944 /** 945 * Creates an instance whose backing parser will be selected by sniffing the content during the 946 * first {@link #advance} call. Parser implementations will sniff the content in order of 947 * appearance in {@code parserNames}. 948 * 949 * @param outputConsumer The {@link OutputConsumer} to which extracted data is output. 950 * @param parserNames The names of the parsers to sniff the content with. If empty, a default 951 * array of names is used. 952 * @return A new instance. 953 */ 954 @NonNull create( @onNull OutputConsumer outputConsumer, @NonNull @ParserName String... parserNames)955 public static MediaParser create( 956 @NonNull OutputConsumer outputConsumer, @NonNull @ParserName String... parserNames) { 957 assertValidNames(parserNames); 958 if (parserNames.length == 0) { 959 parserNames = EXTRACTOR_FACTORIES_BY_NAME.keySet().toArray(new String[0]); 960 } 961 return new MediaParser(outputConsumer, /* createdByName= */ false, parserNames); 962 } 963 964 // Misc static methods. 965 966 /** 967 * Returns an immutable list with the names of the parsers that are suitable for container 968 * formats with the given {@link MediaFormat}. 969 * 970 * <p>A parser supports a {@link MediaFormat} if the mime type associated with {@link 971 * MediaFormat#KEY_MIME} corresponds to the supported container format. 972 * 973 * @param mediaFormat The {@link MediaFormat} to check support for. 974 * @return The parser names that support the given {@code mediaFormat}, or the list of all 975 * parsers available if no container specific format information is provided. 976 */ 977 @NonNull 978 @ParserName getParserNames(@onNull MediaFormat mediaFormat)979 public static List<String> getParserNames(@NonNull MediaFormat mediaFormat) { 980 String mimeType = mediaFormat.getString(MediaFormat.KEY_MIME); 981 mimeType = mimeType == null ? null : Ascii.toLowerCase(mimeType); 982 if (TextUtils.isEmpty(mimeType)) { 983 // No MIME type provided. Return all. 984 return Collections.unmodifiableList( 985 new ArrayList<>(EXTRACTOR_FACTORIES_BY_NAME.keySet())); 986 } 987 ArrayList<String> result = new ArrayList<>(); 988 switch (mimeType) { 989 case "video/x-matroska": 990 case "audio/x-matroska": 991 case "video/x-webm": 992 case "audio/x-webm": 993 result.add(PARSER_NAME_MATROSKA); 994 break; 995 case "video/mp4": 996 case "audio/mp4": 997 case "application/mp4": 998 result.add(PARSER_NAME_MP4); 999 result.add(PARSER_NAME_FMP4); 1000 break; 1001 case "audio/mpeg": 1002 result.add(PARSER_NAME_MP3); 1003 break; 1004 case "audio/aac": 1005 result.add(PARSER_NAME_ADTS); 1006 break; 1007 case "audio/ac3": 1008 result.add(PARSER_NAME_AC3); 1009 break; 1010 case "video/mp2t": 1011 case "audio/mp2t": 1012 result.add(PARSER_NAME_TS); 1013 break; 1014 case "video/x-flv": 1015 result.add(PARSER_NAME_FLV); 1016 break; 1017 case "video/ogg": 1018 case "audio/ogg": 1019 case "application/ogg": 1020 result.add(PARSER_NAME_OGG); 1021 break; 1022 case "video/mp2p": 1023 case "video/mp1s": 1024 result.add(PARSER_NAME_PS); 1025 break; 1026 case "audio/vnd.wave": 1027 case "audio/wav": 1028 case "audio/wave": 1029 case "audio/x-wav": 1030 result.add(PARSER_NAME_WAV); 1031 break; 1032 case "audio/amr": 1033 result.add(PARSER_NAME_AMR); 1034 break; 1035 case "audio/ac4": 1036 result.add(PARSER_NAME_AC4); 1037 break; 1038 case "audio/flac": 1039 case "audio/x-flac": 1040 result.add(PARSER_NAME_FLAC); 1041 break; 1042 default: 1043 // No parsers support the given mime type. Do nothing. 1044 break; 1045 } 1046 return Collections.unmodifiableList(result); 1047 } 1048 1049 // Private fields. 1050 1051 private final Map<String, Object> mParserParameters; 1052 private final OutputConsumer mOutputConsumer; 1053 private final String[] mParserNamesPool; 1054 private final PositionHolder mPositionHolder; 1055 private final InputReadingDataReader mExoDataReader; 1056 private final DataReaderAdapter mScratchDataReaderAdapter; 1057 private final ParsableByteArrayAdapter mScratchParsableByteArrayAdapter; 1058 @Nullable private final Constructor<DrmInitData.SchemeInitData> mSchemeInitDataConstructor; 1059 private final ArrayList<Format> mMuxedCaptionFormats; 1060 private boolean mInBandCryptoInfo; 1061 private boolean mIncludeSupplementalData; 1062 private boolean mIgnoreTimestampOffset; 1063 private boolean mEagerlyExposeTrackType; 1064 private boolean mExposeDummySeekMap; 1065 private boolean mExposeChunkIndexAsMediaFormat; 1066 private String mParserName; 1067 private Extractor mExtractor; 1068 private ExtractorInput mExtractorInput; 1069 private boolean mPendingExtractorInit; 1070 private long mPendingSeekPosition; 1071 private long mPendingSeekTimeMicros; 1072 private boolean mLoggedSchemeInitDataCreationException; 1073 private boolean mReleased; 1074 1075 // MediaMetrics fields. 1076 @Nullable private LogSessionId mLogSessionId; 1077 private final boolean mCreatedByName; 1078 private final SparseArray<Format> mTrackFormats; 1079 private String mLastObservedExceptionName; 1080 private long mDurationMillis; 1081 private long mResourceByteCount; 1082 1083 // Public methods. 1084 1085 /** 1086 * Sets parser-specific parameters which allow customizing behavior. 1087 * 1088 * <p>Must be called before the first call to {@link #advance}. 1089 * 1090 * @param parameterName The name of the parameter to set. See {@code PARAMETER_*} constants for 1091 * documentation on possible values. 1092 * @param value The value to set for the given {@code parameterName}. See {@code PARAMETER_*} 1093 * constants for documentation on the expected types. 1094 * @return This instance, for convenience. 1095 * @throws IllegalStateException If called after calling {@link #advance} on the same instance. 1096 */ 1097 @NonNull setParameter( @onNull @arameterName String parameterName, @NonNull Object value)1098 public MediaParser setParameter( 1099 @NonNull @ParameterName String parameterName, @NonNull Object value) { 1100 if (mExtractor != null) { 1101 throw new IllegalStateException( 1102 "setParameters() must be called before the first advance() call."); 1103 } 1104 Class expectedType = EXPECTED_TYPE_BY_PARAMETER_NAME.get(parameterName); 1105 // Ignore parameter names that are not contained in the map, in case the client is passing 1106 // a parameter that is being added in a future version of this library. 1107 if (expectedType != null && !expectedType.isInstance(value)) { 1108 throw new IllegalArgumentException( 1109 parameterName 1110 + " expects a " 1111 + expectedType.getSimpleName() 1112 + " but a " 1113 + value.getClass().getSimpleName() 1114 + " was passed."); 1115 } 1116 if (PARAMETER_TS_MODE.equals(parameterName) 1117 && !TS_MODE_SINGLE_PMT.equals(value) 1118 && !TS_MODE_HLS.equals(value) 1119 && !TS_MODE_MULTI_PMT.equals(value)) { 1120 throw new IllegalArgumentException(PARAMETER_TS_MODE + " does not accept: " + value); 1121 } 1122 if (PARAMETER_IN_BAND_CRYPTO_INFO.equals(parameterName)) { 1123 mInBandCryptoInfo = (boolean) value; 1124 } 1125 if (PARAMETER_INCLUDE_SUPPLEMENTAL_DATA.equals(parameterName)) { 1126 mIncludeSupplementalData = (boolean) value; 1127 } 1128 if (PARAMETER_IGNORE_TIMESTAMP_OFFSET.equals(parameterName)) { 1129 mIgnoreTimestampOffset = (boolean) value; 1130 } 1131 if (PARAMETER_EAGERLY_EXPOSE_TRACKTYPE.equals(parameterName)) { 1132 mEagerlyExposeTrackType = (boolean) value; 1133 } 1134 if (PARAMETER_EXPOSE_DUMMY_SEEKMAP.equals(parameterName)) { 1135 mExposeDummySeekMap = (boolean) value; 1136 } 1137 if (PARAMETER_EXPOSE_CHUNK_INDEX_AS_MEDIA_FORMAT.equals(parameterName)) { 1138 mExposeChunkIndexAsMediaFormat = (boolean) value; 1139 } 1140 if (PARAMETER_EXPOSE_CAPTION_FORMATS.equals(parameterName)) { 1141 setMuxedCaptionFormats((List<MediaFormat>) value); 1142 } 1143 mParserParameters.put(parameterName, value); 1144 return this; 1145 } 1146 1147 /** 1148 * Returns whether the given {@code parameterName} is supported by this parser. 1149 * 1150 * @param parameterName The parameter name to check support for. One of the {@code PARAMETER_*} 1151 * constants. 1152 * @return Whether the given {@code parameterName} is supported. 1153 */ supportsParameter(@onNull @arameterName String parameterName)1154 public boolean supportsParameter(@NonNull @ParameterName String parameterName) { 1155 return EXPECTED_TYPE_BY_PARAMETER_NAME.containsKey(parameterName); 1156 } 1157 1158 /** 1159 * Returns the name of the backing parser implementation. 1160 * 1161 * <p>If this instance was creating using {@link #createByName}, the provided name is returned. 1162 * If this instance was created using {@link #create}, this method will return {@link 1163 * #PARSER_NAME_UNKNOWN} until the first call to {@link #advance}, after which the name of the 1164 * backing parser implementation is returned. 1165 * 1166 * @return The name of the backing parser implementation, or null if the backing parser 1167 * implementation has not yet been selected. 1168 */ 1169 @NonNull 1170 @ParserName getParserName()1171 public String getParserName() { 1172 return mParserName; 1173 } 1174 1175 /** 1176 * Makes progress in the extraction of the input media stream, unless the end of the input has 1177 * been reached. 1178 * 1179 * <p>This method will block until some progress has been made. 1180 * 1181 * <p>If this instance was created using {@link #create}, the first call to this method will 1182 * sniff the content using the selected parser implementations. 1183 * 1184 * @param seekableInputReader The {@link SeekableInputReader} from which to obtain the media 1185 * container data. 1186 * @return Whether there is any data left to extract. Returns false if the end of input has been 1187 * reached. 1188 * @throws IOException If an error occurs while reading from the {@link SeekableInputReader}. 1189 * @throws UnrecognizedInputFormatException If the format cannot be recognized by any of the 1190 * underlying parser implementations. 1191 */ advance(@onNull SeekableInputReader seekableInputReader)1192 public boolean advance(@NonNull SeekableInputReader seekableInputReader) throws IOException { 1193 if (mExtractorInput == null) { 1194 // TODO: For efficiency, the same implementation should be used, by providing a 1195 // clearBuffers() method, or similar. 1196 long resourceLength = seekableInputReader.getLength(); 1197 if (mResourceByteCount == 0) { 1198 // For resource byte count metric collection, we only take into account the length 1199 // of the first provided input reader. 1200 mResourceByteCount = resourceLength; 1201 } 1202 mExtractorInput = 1203 new DefaultExtractorInput( 1204 mExoDataReader, seekableInputReader.getPosition(), resourceLength); 1205 } 1206 mExoDataReader.mInputReader = seekableInputReader; 1207 1208 if (mExtractor == null) { 1209 mPendingExtractorInit = true; 1210 if (!mParserName.equals(PARSER_NAME_UNKNOWN)) { 1211 mExtractor = createExtractor(mParserName); 1212 } else { 1213 for (String parserName : mParserNamesPool) { 1214 Extractor extractor = createExtractor(parserName); 1215 try { 1216 if (extractor.sniff(mExtractorInput)) { 1217 mParserName = parserName; 1218 mExtractor = extractor; 1219 mPendingExtractorInit = true; 1220 break; 1221 } 1222 } catch (EOFException e) { 1223 // Do nothing. 1224 } finally { 1225 mExtractorInput.resetPeekPosition(); 1226 } 1227 } 1228 if (mExtractor == null) { 1229 UnrecognizedInputFormatException exception = 1230 UnrecognizedInputFormatException.createForExtractors(mParserNamesPool); 1231 mLastObservedExceptionName = exception.getClass().getName(); 1232 throw exception; 1233 } 1234 return true; 1235 } 1236 } 1237 1238 if (mPendingExtractorInit) { 1239 if (mExposeDummySeekMap) { 1240 // We propagate the dummy seek map before initializing the extractor, in case the 1241 // extractor initialization outputs a seek map. 1242 mOutputConsumer.onSeekMapFound(SeekMap.DUMMY); 1243 } 1244 mExtractor.init(new ExtractorOutputAdapter()); 1245 mPendingExtractorInit = false; 1246 // We return after initialization to allow clients use any output information before 1247 // starting actual extraction. 1248 return true; 1249 } 1250 1251 if (isPendingSeek()) { 1252 mExtractor.seek(mPendingSeekPosition, mPendingSeekTimeMicros); 1253 removePendingSeek(); 1254 } 1255 1256 mPositionHolder.position = seekableInputReader.getPosition(); 1257 int result; 1258 try { 1259 result = mExtractor.read(mExtractorInput, mPositionHolder); 1260 } catch (Exception e) { 1261 mLastObservedExceptionName = e.getClass().getName(); 1262 if (e instanceof ParserException) { 1263 throw new ParsingException((ParserException) e); 1264 } else { 1265 throw e; 1266 } 1267 } 1268 if (result == Extractor.RESULT_END_OF_INPUT) { 1269 mExtractorInput = null; 1270 return false; 1271 } 1272 if (result == Extractor.RESULT_SEEK) { 1273 mExtractorInput = null; 1274 seekableInputReader.seekToPosition(mPositionHolder.position); 1275 } 1276 return true; 1277 } 1278 1279 /** 1280 * Seeks within the media container being extracted. 1281 * 1282 * <p>{@link SeekPoint SeekPoints} can be obtained from the {@link SeekMap} passed to {@link 1283 * OutputConsumer#onSeekMapFound(SeekMap)}. 1284 * 1285 * <p>Following a call to this method, the {@link InputReader} passed to the next invocation of 1286 * {@link #advance} must provide data starting from {@link SeekPoint#position} in the stream. 1287 * 1288 * @param seekPoint The {@link SeekPoint} to seek to. 1289 */ seek(@onNull SeekPoint seekPoint)1290 public void seek(@NonNull SeekPoint seekPoint) { 1291 if (mExtractor == null) { 1292 mPendingSeekPosition = seekPoint.position; 1293 mPendingSeekTimeMicros = seekPoint.timeMicros; 1294 } else { 1295 mExtractor.seek(seekPoint.position, seekPoint.timeMicros); 1296 } 1297 } 1298 1299 /** 1300 * Releases any acquired resources. 1301 * 1302 * <p>After calling this method, this instance becomes unusable and no other methods should be 1303 * invoked. 1304 */ release()1305 public void release() { 1306 mExtractorInput = null; 1307 mExtractor = null; 1308 if (mReleased) { 1309 // Nothing to do. 1310 return; 1311 } 1312 mReleased = true; 1313 1314 String trackMimeTypes = buildMediaMetricsString(format -> format.sampleMimeType); 1315 String trackCodecs = buildMediaMetricsString(format -> format.codecs); 1316 int videoWidth = -1; 1317 int videoHeight = -1; 1318 for (int i = 0; i < mTrackFormats.size(); i++) { 1319 Format format = mTrackFormats.valueAt(i); 1320 if (format.width != Format.NO_VALUE && format.height != Format.NO_VALUE) { 1321 videoWidth = format.width; 1322 videoHeight = format.height; 1323 break; 1324 } 1325 } 1326 1327 String alteredParameters = 1328 String.join( 1329 MEDIAMETRICS_ELEMENT_SEPARATOR, 1330 mParserParameters.keySet().toArray(new String[0])); 1331 alteredParameters = 1332 alteredParameters.substring( 1333 0, 1334 Math.min( 1335 alteredParameters.length(), 1336 MEDIAMETRICS_PARAMETER_LIST_MAX_LENGTH)); 1337 1338 nativeSubmitMetrics( 1339 SdkLevel.isAtLeastS() ? getLogSessionIdStringV31() : "", 1340 mParserName, 1341 mCreatedByName, 1342 String.join(MEDIAMETRICS_ELEMENT_SEPARATOR, mParserNamesPool), 1343 mLastObservedExceptionName, 1344 addDither(mResourceByteCount), 1345 addDither(mDurationMillis), 1346 trackMimeTypes, 1347 trackCodecs, 1348 alteredParameters, 1349 videoWidth, 1350 videoHeight); 1351 } 1352 1353 @RequiresApi(31) setLogSessionId(@onNull LogSessionId logSessionId)1354 public void setLogSessionId(@NonNull LogSessionId logSessionId) { 1355 this.mLogSessionId = Objects.requireNonNull(logSessionId); 1356 } 1357 1358 @RequiresApi(31) 1359 @NonNull getLogSessionId()1360 public LogSessionId getLogSessionId() { 1361 return mLogSessionId != null ? mLogSessionId : LogSessionId.LOG_SESSION_ID_NONE; 1362 } 1363 1364 // Private methods. 1365 MediaParser( OutputConsumer outputConsumer, boolean createdByName, String... parserNamesPool)1366 private MediaParser( 1367 OutputConsumer outputConsumer, boolean createdByName, String... parserNamesPool) { 1368 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { 1369 throw new UnsupportedOperationException("Android version must be R or greater."); 1370 } 1371 mParserParameters = new HashMap<>(); 1372 mOutputConsumer = outputConsumer; 1373 mParserNamesPool = parserNamesPool; 1374 mCreatedByName = createdByName; 1375 mParserName = createdByName ? parserNamesPool[0] : PARSER_NAME_UNKNOWN; 1376 mPositionHolder = new PositionHolder(); 1377 mExoDataReader = new InputReadingDataReader(); 1378 removePendingSeek(); 1379 mScratchDataReaderAdapter = new DataReaderAdapter(); 1380 mScratchParsableByteArrayAdapter = new ParsableByteArrayAdapter(); 1381 mSchemeInitDataConstructor = getSchemeInitDataConstructor(); 1382 mMuxedCaptionFormats = new ArrayList<>(); 1383 1384 // MediaMetrics. 1385 mTrackFormats = new SparseArray<>(); 1386 mLastObservedExceptionName = ""; 1387 mDurationMillis = -1; 1388 } 1389 buildMediaMetricsString(Function<Format, String> formatFieldGetter)1390 private String buildMediaMetricsString(Function<Format, String> formatFieldGetter) { 1391 StringBuilder stringBuilder = new StringBuilder(); 1392 for (int i = 0; i < mTrackFormats.size(); i++) { 1393 if (i > 0) { 1394 stringBuilder.append(MEDIAMETRICS_ELEMENT_SEPARATOR); 1395 } 1396 String fieldValue = formatFieldGetter.apply(mTrackFormats.valueAt(i)); 1397 stringBuilder.append(fieldValue != null ? fieldValue : ""); 1398 } 1399 return stringBuilder.substring( 1400 0, Math.min(stringBuilder.length(), MEDIAMETRICS_MAX_STRING_SIZE)); 1401 } 1402 setMuxedCaptionFormats(List<MediaFormat> mediaFormats)1403 private void setMuxedCaptionFormats(List<MediaFormat> mediaFormats) { 1404 mMuxedCaptionFormats.clear(); 1405 for (MediaFormat mediaFormat : mediaFormats) { 1406 mMuxedCaptionFormats.add(toExoPlayerCaptionFormat(mediaFormat)); 1407 } 1408 } 1409 isPendingSeek()1410 private boolean isPendingSeek() { 1411 return mPendingSeekPosition >= 0; 1412 } 1413 removePendingSeek()1414 private void removePendingSeek() { 1415 mPendingSeekPosition = -1; 1416 mPendingSeekTimeMicros = -1; 1417 } 1418 createExtractor(String parserName)1419 private Extractor createExtractor(String parserName) { 1420 int flags = 0; 1421 TimestampAdjuster timestampAdjuster = null; 1422 if (mIgnoreTimestampOffset) { 1423 timestampAdjuster = new TimestampAdjuster(TimestampAdjuster.MODE_NO_OFFSET); 1424 } 1425 switch (parserName) { 1426 case PARSER_NAME_MATROSKA: 1427 flags = 1428 getBooleanParameter(PARAMETER_MATROSKA_DISABLE_CUES_SEEKING) 1429 ? MatroskaExtractor.FLAG_DISABLE_SEEK_FOR_CUES 1430 : 0; 1431 return new MatroskaExtractor(flags); 1432 case PARSER_NAME_FMP4: 1433 flags |= 1434 getBooleanParameter(PARAMETER_EXPOSE_EMSG_TRACK) 1435 ? FragmentedMp4Extractor.FLAG_ENABLE_EMSG_TRACK 1436 : 0; 1437 flags |= 1438 getBooleanParameter(PARAMETER_MP4_IGNORE_EDIT_LISTS) 1439 ? FragmentedMp4Extractor.FLAG_WORKAROUND_IGNORE_EDIT_LISTS 1440 : 0; 1441 flags |= 1442 getBooleanParameter(PARAMETER_MP4_IGNORE_TFDT_BOX) 1443 ? FragmentedMp4Extractor.FLAG_WORKAROUND_IGNORE_TFDT_BOX 1444 : 0; 1445 flags |= 1446 getBooleanParameter(PARAMETER_MP4_TREAT_VIDEO_FRAMES_AS_KEYFRAMES) 1447 ? FragmentedMp4Extractor 1448 .FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME 1449 : 0; 1450 return new FragmentedMp4Extractor( 1451 flags, 1452 timestampAdjuster, 1453 /* sideloadedTrack= */ null, 1454 mMuxedCaptionFormats); 1455 case PARSER_NAME_MP4: 1456 flags |= 1457 getBooleanParameter(PARAMETER_MP4_IGNORE_EDIT_LISTS) 1458 ? Mp4Extractor.FLAG_WORKAROUND_IGNORE_EDIT_LISTS 1459 : 0; 1460 return new Mp4Extractor(flags); 1461 case PARSER_NAME_MP3: 1462 flags |= 1463 getBooleanParameter(PARAMETER_MP3_DISABLE_ID3) 1464 ? Mp3Extractor.FLAG_DISABLE_ID3_METADATA 1465 : 0; 1466 flags |= 1467 getBooleanParameter(PARAMETER_MP3_ENABLE_CBR_SEEKING) 1468 ? Mp3Extractor.FLAG_ENABLE_CONSTANT_BITRATE_SEEKING 1469 : 0; 1470 // TODO: Add index seeking once we update the ExoPlayer version. 1471 return new Mp3Extractor(flags); 1472 case PARSER_NAME_ADTS: 1473 flags |= 1474 getBooleanParameter(PARAMETER_ADTS_ENABLE_CBR_SEEKING) 1475 ? AdtsExtractor.FLAG_ENABLE_CONSTANT_BITRATE_SEEKING 1476 : 0; 1477 return new AdtsExtractor(flags); 1478 case PARSER_NAME_AC3: 1479 return new Ac3Extractor(); 1480 case PARSER_NAME_TS: 1481 flags |= 1482 getBooleanParameter(PARAMETER_TS_ALLOW_NON_IDR_AVC_KEYFRAMES) 1483 ? DefaultTsPayloadReaderFactory.FLAG_ALLOW_NON_IDR_KEYFRAMES 1484 : 0; 1485 flags |= 1486 getBooleanParameter(PARAMETER_TS_DETECT_ACCESS_UNITS) 1487 ? DefaultTsPayloadReaderFactory.FLAG_DETECT_ACCESS_UNITS 1488 : 0; 1489 flags |= 1490 getBooleanParameter(PARAMETER_TS_ENABLE_HDMV_DTS_AUDIO_STREAMS) 1491 ? DefaultTsPayloadReaderFactory.FLAG_ENABLE_HDMV_DTS_AUDIO_STREAMS 1492 : 0; 1493 flags |= 1494 getBooleanParameter(PARAMETER_TS_IGNORE_AAC_STREAM) 1495 ? DefaultTsPayloadReaderFactory.FLAG_IGNORE_AAC_STREAM 1496 : 0; 1497 flags |= 1498 getBooleanParameter(PARAMETER_TS_IGNORE_AVC_STREAM) 1499 ? DefaultTsPayloadReaderFactory.FLAG_IGNORE_H264_STREAM 1500 : 0; 1501 flags |= 1502 getBooleanParameter(PARAMETER_TS_IGNORE_SPLICE_INFO_STREAM) 1503 ? DefaultTsPayloadReaderFactory.FLAG_IGNORE_SPLICE_INFO_STREAM 1504 : 0; 1505 flags |= 1506 getBooleanParameter(PARAMETER_OVERRIDE_IN_BAND_CAPTION_DECLARATIONS) 1507 ? DefaultTsPayloadReaderFactory.FLAG_OVERRIDE_CAPTION_DESCRIPTORS 1508 : 0; 1509 String tsMode = getStringParameter(PARAMETER_TS_MODE, TS_MODE_SINGLE_PMT); 1510 int hlsMode = 1511 TS_MODE_SINGLE_PMT.equals(tsMode) 1512 ? TsExtractor.MODE_SINGLE_PMT 1513 : TS_MODE_HLS.equals(tsMode) 1514 ? TsExtractor.MODE_HLS 1515 : TsExtractor.MODE_MULTI_PMT; 1516 return new TsExtractor( 1517 hlsMode, 1518 timestampAdjuster != null 1519 ? timestampAdjuster 1520 : new TimestampAdjuster(/* firstSampleTimestampUs= */ 0), 1521 new DefaultTsPayloadReaderFactory(flags, mMuxedCaptionFormats)); 1522 case PARSER_NAME_FLV: 1523 return new FlvExtractor(); 1524 case PARSER_NAME_OGG: 1525 return new OggExtractor(); 1526 case PARSER_NAME_PS: 1527 return new PsExtractor(); 1528 case PARSER_NAME_WAV: 1529 return new WavExtractor(); 1530 case PARSER_NAME_AMR: 1531 flags |= 1532 getBooleanParameter(PARAMETER_AMR_ENABLE_CBR_SEEKING) 1533 ? AmrExtractor.FLAG_ENABLE_CONSTANT_BITRATE_SEEKING 1534 : 0; 1535 return new AmrExtractor(flags); 1536 case PARSER_NAME_AC4: 1537 return new Ac4Extractor(); 1538 case PARSER_NAME_FLAC: 1539 flags |= 1540 getBooleanParameter(PARAMETER_FLAC_DISABLE_ID3) 1541 ? FlacExtractor.FLAG_DISABLE_ID3_METADATA 1542 : 0; 1543 return new FlacExtractor(flags); 1544 default: 1545 // Should never happen. 1546 throw new IllegalStateException("Unexpected attempt to create: " + parserName); 1547 } 1548 } 1549 getBooleanParameter(String name)1550 private boolean getBooleanParameter(String name) { 1551 return (boolean) mParserParameters.getOrDefault(name, false); 1552 } 1553 getStringParameter(String name, String defaultValue)1554 private String getStringParameter(String name, String defaultValue) { 1555 return (String) mParserParameters.getOrDefault(name, defaultValue); 1556 } 1557 1558 @RequiresApi(31) getLogSessionIdStringV31()1559 private String getLogSessionIdStringV31() { 1560 return mLogSessionId != null ? mLogSessionId.getStringId() : ""; 1561 } 1562 1563 // Private classes. 1564 1565 private static final class InputReadingDataReader implements DataReader { 1566 1567 public InputReader mInputReader; 1568 1569 @Override read(byte[] buffer, int offset, int readLength)1570 public int read(byte[] buffer, int offset, int readLength) throws IOException { 1571 return mInputReader.read(buffer, offset, readLength); 1572 } 1573 } 1574 1575 private final class MediaParserDrmInitData extends DrmInitData { 1576 1577 private final SchemeInitData[] mSchemeDatas; 1578 MediaParserDrmInitData(com.google.android.exoplayer2.drm.DrmInitData exoDrmInitData)1579 private MediaParserDrmInitData(com.google.android.exoplayer2.drm.DrmInitData exoDrmInitData) 1580 throws IllegalAccessException, InstantiationException, InvocationTargetException { 1581 mSchemeDatas = new SchemeInitData[exoDrmInitData.schemeDataCount]; 1582 for (int i = 0; i < mSchemeDatas.length; i++) { 1583 mSchemeDatas[i] = toFrameworkSchemeInitData(exoDrmInitData.get(i)); 1584 } 1585 } 1586 1587 @Override 1588 @Nullable get(UUID schemeUuid)1589 public SchemeInitData get(UUID schemeUuid) { 1590 for (SchemeInitData schemeInitData : mSchemeDatas) { 1591 if (schemeInitData.uuid.equals(schemeUuid)) { 1592 return schemeInitData; 1593 } 1594 } 1595 return null; 1596 } 1597 1598 @Override getSchemeInitDataAt(int index)1599 public SchemeInitData getSchemeInitDataAt(int index) { 1600 return mSchemeDatas[index]; 1601 } 1602 1603 @Override getSchemeInitDataCount()1604 public int getSchemeInitDataCount() { 1605 return mSchemeDatas.length; 1606 } 1607 toFrameworkSchemeInitData(SchemeData exoSchemeData)1608 private DrmInitData.SchemeInitData toFrameworkSchemeInitData(SchemeData exoSchemeData) 1609 throws IllegalAccessException, InvocationTargetException, InstantiationException { 1610 return mSchemeInitDataConstructor.newInstance( 1611 exoSchemeData.uuid, exoSchemeData.mimeType, exoSchemeData.data); 1612 } 1613 } 1614 1615 private final class ExtractorOutputAdapter implements ExtractorOutput { 1616 1617 private final SparseArray<TrackOutput> mTrackOutputAdapters; 1618 private boolean mTracksEnded; 1619 ExtractorOutputAdapter()1620 private ExtractorOutputAdapter() { 1621 mTrackOutputAdapters = new SparseArray<>(); 1622 } 1623 1624 @Override track(int id, int type)1625 public TrackOutput track(int id, int type) { 1626 TrackOutput trackOutput = mTrackOutputAdapters.get(id); 1627 if (trackOutput == null) { 1628 int trackIndex = mTrackOutputAdapters.size(); 1629 trackOutput = new TrackOutputAdapter(trackIndex); 1630 mTrackOutputAdapters.put(id, trackOutput); 1631 if (mEagerlyExposeTrackType) { 1632 MediaFormat mediaFormat = new MediaFormat(); 1633 mediaFormat.setString("track-type-string", toTypeString(type)); 1634 mOutputConsumer.onTrackDataFound( 1635 trackIndex, new TrackData(mediaFormat, /* drmInitData= */ null)); 1636 } 1637 } 1638 return trackOutput; 1639 } 1640 1641 @Override endTracks()1642 public void endTracks() { 1643 mOutputConsumer.onTrackCountFound(mTrackOutputAdapters.size()); 1644 } 1645 1646 @Override seekMap(com.google.android.exoplayer2.extractor.SeekMap exoplayerSeekMap)1647 public void seekMap(com.google.android.exoplayer2.extractor.SeekMap exoplayerSeekMap) { 1648 long durationUs = exoplayerSeekMap.getDurationUs(); 1649 if (durationUs != C.TIME_UNSET) { 1650 mDurationMillis = C.usToMs(durationUs); 1651 } 1652 if (mExposeChunkIndexAsMediaFormat && exoplayerSeekMap instanceof ChunkIndex) { 1653 ChunkIndex chunkIndex = (ChunkIndex) exoplayerSeekMap; 1654 MediaFormat mediaFormat = new MediaFormat(); 1655 mediaFormat.setByteBuffer("chunk-index-int-sizes", toByteBuffer(chunkIndex.sizes)); 1656 mediaFormat.setByteBuffer( 1657 "chunk-index-long-offsets", toByteBuffer(chunkIndex.offsets)); 1658 mediaFormat.setByteBuffer( 1659 "chunk-index-long-us-durations", toByteBuffer(chunkIndex.durationsUs)); 1660 mediaFormat.setByteBuffer( 1661 "chunk-index-long-us-times", toByteBuffer(chunkIndex.timesUs)); 1662 mOutputConsumer.onTrackDataFound( 1663 /* trackIndex= */ 0, new TrackData(mediaFormat, /* drmInitData= */ null)); 1664 } 1665 mOutputConsumer.onSeekMapFound(new SeekMap(exoplayerSeekMap)); 1666 } 1667 } 1668 1669 private class TrackOutputAdapter implements TrackOutput { 1670 1671 private final int mTrackIndex; 1672 1673 private CryptoInfo mLastOutputCryptoInfo; 1674 private CryptoInfo.Pattern mLastOutputEncryptionPattern; 1675 private CryptoData mLastReceivedCryptoData; 1676 1677 @EncryptionDataReadState private int mEncryptionDataReadState; 1678 private int mEncryptionDataSizeToSubtractFromSampleDataSize; 1679 private int mEncryptionVectorSize; 1680 private byte[] mScratchIvSpace; 1681 private int mSubsampleEncryptionDataSize; 1682 private int[] mScratchSubsampleEncryptedBytesCount; 1683 private int[] mScratchSubsampleClearBytesCount; 1684 private boolean mHasSubsampleEncryptionData; 1685 private int mSkippedSupplementalDataBytes; 1686 TrackOutputAdapter(int trackIndex)1687 private TrackOutputAdapter(int trackIndex) { 1688 mTrackIndex = trackIndex; 1689 mScratchIvSpace = new byte[16]; // Size documented in CryptoInfo. 1690 mScratchSubsampleEncryptedBytesCount = new int[32]; 1691 mScratchSubsampleClearBytesCount = new int[32]; 1692 mEncryptionDataReadState = STATE_READING_SIGNAL_BYTE; 1693 mLastOutputEncryptionPattern = 1694 new CryptoInfo.Pattern(/* blocksToEncrypt= */ 0, /* blocksToSkip= */ 0); 1695 } 1696 1697 @Override format(Format format)1698 public void format(Format format) { 1699 mTrackFormats.put(mTrackIndex, format); 1700 mOutputConsumer.onTrackDataFound( 1701 mTrackIndex, 1702 new TrackData( 1703 toMediaFormat(format), toFrameworkDrmInitData(format.drmInitData))); 1704 } 1705 1706 @Override sampleData( DataReader input, int length, boolean allowEndOfInput, @SampleDataPart int sampleDataPart)1707 public int sampleData( 1708 DataReader input, 1709 int length, 1710 boolean allowEndOfInput, 1711 @SampleDataPart int sampleDataPart) 1712 throws IOException { 1713 mScratchDataReaderAdapter.setDataReader(input, length); 1714 long positionBeforeReading = mScratchDataReaderAdapter.getPosition(); 1715 mOutputConsumer.onSampleDataFound(mTrackIndex, mScratchDataReaderAdapter); 1716 return (int) (mScratchDataReaderAdapter.getPosition() - positionBeforeReading); 1717 } 1718 1719 @Override sampleData( ParsableByteArray data, int length, @SampleDataPart int sampleDataPart)1720 public void sampleData( 1721 ParsableByteArray data, int length, @SampleDataPart int sampleDataPart) { 1722 if (sampleDataPart == SAMPLE_DATA_PART_ENCRYPTION && !mInBandCryptoInfo) { 1723 while (length > 0) { 1724 switch (mEncryptionDataReadState) { 1725 case STATE_READING_SIGNAL_BYTE: 1726 int encryptionSignalByte = data.readUnsignedByte(); 1727 length--; 1728 mHasSubsampleEncryptionData = ((encryptionSignalByte >> 7) & 1) != 0; 1729 mEncryptionVectorSize = encryptionSignalByte & 0x7F; 1730 mEncryptionDataSizeToSubtractFromSampleDataSize = 1731 mEncryptionVectorSize + 1; // Signal byte. 1732 mEncryptionDataReadState = STATE_READING_INIT_VECTOR; 1733 break; 1734 case STATE_READING_INIT_VECTOR: 1735 Arrays.fill(mScratchIvSpace, (byte) 0); // Ensure 0-padding. 1736 data.readBytes(mScratchIvSpace, /* offset= */ 0, mEncryptionVectorSize); 1737 length -= mEncryptionVectorSize; 1738 if (mHasSubsampleEncryptionData) { 1739 mEncryptionDataReadState = STATE_READING_SUBSAMPLE_ENCRYPTION_SIZE; 1740 } else { 1741 mSubsampleEncryptionDataSize = 0; 1742 mEncryptionDataReadState = STATE_READING_SIGNAL_BYTE; 1743 } 1744 break; 1745 case STATE_READING_SUBSAMPLE_ENCRYPTION_SIZE: 1746 mSubsampleEncryptionDataSize = data.readUnsignedShort(); 1747 if (mScratchSubsampleClearBytesCount.length 1748 < mSubsampleEncryptionDataSize) { 1749 mScratchSubsampleClearBytesCount = 1750 new int[mSubsampleEncryptionDataSize]; 1751 mScratchSubsampleEncryptedBytesCount = 1752 new int[mSubsampleEncryptionDataSize]; 1753 } 1754 length -= 2; 1755 mEncryptionDataSizeToSubtractFromSampleDataSize += 1756 2 1757 + mSubsampleEncryptionDataSize 1758 * BYTES_PER_SUBSAMPLE_ENCRYPTION_ENTRY; 1759 mEncryptionDataReadState = STATE_READING_SUBSAMPLE_ENCRYPTION_DATA; 1760 break; 1761 case STATE_READING_SUBSAMPLE_ENCRYPTION_DATA: 1762 for (int i = 0; i < mSubsampleEncryptionDataSize; i++) { 1763 mScratchSubsampleClearBytesCount[i] = data.readUnsignedShort(); 1764 mScratchSubsampleEncryptedBytesCount[i] = data.readInt(); 1765 } 1766 length -= 1767 mSubsampleEncryptionDataSize 1768 * BYTES_PER_SUBSAMPLE_ENCRYPTION_ENTRY; 1769 mEncryptionDataReadState = STATE_READING_SIGNAL_BYTE; 1770 if (length != 0) { 1771 throw new IllegalStateException(); 1772 } 1773 break; 1774 default: 1775 // Never happens. 1776 throw new IllegalStateException(); 1777 } 1778 } 1779 } else if (sampleDataPart == SAMPLE_DATA_PART_SUPPLEMENTAL 1780 && !mIncludeSupplementalData) { 1781 mSkippedSupplementalDataBytes += length; 1782 data.skipBytes(length); 1783 } else { 1784 outputSampleData(data, length); 1785 } 1786 } 1787 1788 @Override sampleMetadata( long timeUs, int flags, int size, int offset, @Nullable CryptoData cryptoData)1789 public void sampleMetadata( 1790 long timeUs, int flags, int size, int offset, @Nullable CryptoData cryptoData) { 1791 size -= mSkippedSupplementalDataBytes; 1792 mSkippedSupplementalDataBytes = 0; 1793 mOutputConsumer.onSampleCompleted( 1794 mTrackIndex, 1795 timeUs, 1796 getMediaParserFlags(flags), 1797 size - mEncryptionDataSizeToSubtractFromSampleDataSize, 1798 offset, 1799 getPopulatedCryptoInfo(cryptoData)); 1800 mEncryptionDataReadState = STATE_READING_SIGNAL_BYTE; 1801 mEncryptionDataSizeToSubtractFromSampleDataSize = 0; 1802 } 1803 1804 @Nullable getPopulatedCryptoInfo(@ullable CryptoData cryptoData)1805 private CryptoInfo getPopulatedCryptoInfo(@Nullable CryptoData cryptoData) { 1806 if (cryptoData == null) { 1807 // The sample is not encrypted. 1808 return null; 1809 } else if (mInBandCryptoInfo) { 1810 if (cryptoData != mLastReceivedCryptoData) { 1811 mLastOutputCryptoInfo = 1812 createNewCryptoInfoAndPopulateWithCryptoData(cryptoData); 1813 // We are using in-band crypto info, so the IV will be ignored. But we prevent 1814 // it from being null because toString assumes it non-null. 1815 mLastOutputCryptoInfo.iv = EMPTY_BYTE_ARRAY; 1816 } 1817 } else /* We must populate the full CryptoInfo. */ { 1818 // CryptoInfo.pattern is not accessible to the user, so the user needs to feed 1819 // this CryptoInfo directly to MediaCodec. We need to create a new CryptoInfo per 1820 // sample because of per-sample initialization vector changes. 1821 CryptoInfo newCryptoInfo = createNewCryptoInfoAndPopulateWithCryptoData(cryptoData); 1822 newCryptoInfo.iv = Arrays.copyOf(mScratchIvSpace, mScratchIvSpace.length); 1823 boolean canReuseSubsampleInfo = 1824 mLastOutputCryptoInfo != null 1825 && mLastOutputCryptoInfo.numSubSamples 1826 == mSubsampleEncryptionDataSize; 1827 for (int i = 0; i < mSubsampleEncryptionDataSize && canReuseSubsampleInfo; i++) { 1828 canReuseSubsampleInfo = 1829 mLastOutputCryptoInfo.numBytesOfClearData[i] 1830 == mScratchSubsampleClearBytesCount[i] 1831 && mLastOutputCryptoInfo.numBytesOfEncryptedData[i] 1832 == mScratchSubsampleEncryptedBytesCount[i]; 1833 } 1834 newCryptoInfo.numSubSamples = mSubsampleEncryptionDataSize; 1835 if (canReuseSubsampleInfo) { 1836 newCryptoInfo.numBytesOfClearData = mLastOutputCryptoInfo.numBytesOfClearData; 1837 newCryptoInfo.numBytesOfEncryptedData = 1838 mLastOutputCryptoInfo.numBytesOfEncryptedData; 1839 } else { 1840 newCryptoInfo.numBytesOfClearData = 1841 Arrays.copyOf( 1842 mScratchSubsampleClearBytesCount, mSubsampleEncryptionDataSize); 1843 newCryptoInfo.numBytesOfEncryptedData = 1844 Arrays.copyOf( 1845 mScratchSubsampleEncryptedBytesCount, 1846 mSubsampleEncryptionDataSize); 1847 } 1848 mLastOutputCryptoInfo = newCryptoInfo; 1849 } 1850 mLastReceivedCryptoData = cryptoData; 1851 return mLastOutputCryptoInfo; 1852 } 1853 createNewCryptoInfoAndPopulateWithCryptoData(CryptoData cryptoData)1854 private CryptoInfo createNewCryptoInfoAndPopulateWithCryptoData(CryptoData cryptoData) { 1855 CryptoInfo cryptoInfo = new CryptoInfo(); 1856 cryptoInfo.key = cryptoData.encryptionKey; 1857 cryptoInfo.mode = cryptoData.cryptoMode; 1858 if (cryptoData.clearBlocks != mLastOutputEncryptionPattern.getSkipBlocks() 1859 || cryptoData.encryptedBlocks 1860 != mLastOutputEncryptionPattern.getEncryptBlocks()) { 1861 mLastOutputEncryptionPattern = 1862 new CryptoInfo.Pattern(cryptoData.encryptedBlocks, cryptoData.clearBlocks); 1863 } 1864 cryptoInfo.setPattern(mLastOutputEncryptionPattern); 1865 return cryptoInfo; 1866 } 1867 outputSampleData(ParsableByteArray data, int length)1868 private void outputSampleData(ParsableByteArray data, int length) { 1869 mScratchParsableByteArrayAdapter.resetWithByteArray(data, length); 1870 try { 1871 // Read all bytes from data. ExoPlayer extractors expect all sample data to be 1872 // consumed by TrackOutput implementations when passing a ParsableByteArray. 1873 while (mScratchParsableByteArrayAdapter.getLength() > 0) { 1874 mOutputConsumer.onSampleDataFound( 1875 mTrackIndex, mScratchParsableByteArrayAdapter); 1876 } 1877 } catch (IOException e) { 1878 // Unexpected. 1879 throw new RuntimeException(e); 1880 } 1881 } 1882 } 1883 1884 private static final class DataReaderAdapter implements InputReader { 1885 1886 private DataReader mDataReader; 1887 private int mCurrentPosition; 1888 private long mLength; 1889 setDataReader(DataReader dataReader, long length)1890 public void setDataReader(DataReader dataReader, long length) { 1891 mDataReader = dataReader; 1892 mCurrentPosition = 0; 1893 mLength = length; 1894 } 1895 1896 // Input implementation. 1897 1898 @Override read(byte[] buffer, int offset, int readLength)1899 public int read(byte[] buffer, int offset, int readLength) throws IOException { 1900 int readBytes = 0; 1901 readBytes = mDataReader.read(buffer, offset, readLength); 1902 mCurrentPosition += readBytes; 1903 return readBytes; 1904 } 1905 1906 @Override getPosition()1907 public long getPosition() { 1908 return mCurrentPosition; 1909 } 1910 1911 @Override getLength()1912 public long getLength() { 1913 return mLength - mCurrentPosition; 1914 } 1915 } 1916 1917 private static final class ParsableByteArrayAdapter implements InputReader { 1918 1919 private ParsableByteArray mByteArray; 1920 private long mLength; 1921 private int mCurrentPosition; 1922 resetWithByteArray(ParsableByteArray byteArray, long length)1923 public void resetWithByteArray(ParsableByteArray byteArray, long length) { 1924 mByteArray = byteArray; 1925 mCurrentPosition = 0; 1926 mLength = length; 1927 } 1928 1929 // Input implementation. 1930 1931 @Override read(byte[] buffer, int offset, int readLength)1932 public int read(byte[] buffer, int offset, int readLength) { 1933 mByteArray.readBytes(buffer, offset, readLength); 1934 mCurrentPosition += readLength; 1935 return readLength; 1936 } 1937 1938 @Override getPosition()1939 public long getPosition() { 1940 return mCurrentPosition; 1941 } 1942 1943 @Override getLength()1944 public long getLength() { 1945 return mLength - mCurrentPosition; 1946 } 1947 } 1948 1949 private static final class DummyExoPlayerSeekMap 1950 implements com.google.android.exoplayer2.extractor.SeekMap { 1951 1952 @Override isSeekable()1953 public boolean isSeekable() { 1954 return true; 1955 } 1956 1957 @Override getDurationUs()1958 public long getDurationUs() { 1959 return C.TIME_UNSET; 1960 } 1961 1962 @Override getSeekPoints(long timeUs)1963 public SeekPoints getSeekPoints(long timeUs) { 1964 com.google.android.exoplayer2.extractor.SeekPoint seekPoint = 1965 new com.google.android.exoplayer2.extractor.SeekPoint( 1966 timeUs, /* position= */ 0); 1967 return new SeekPoints(seekPoint, seekPoint); 1968 } 1969 } 1970 1971 /** Creates extractor instances. */ 1972 private interface ExtractorFactory { 1973 1974 /** Returns a new extractor instance. */ createInstance()1975 Extractor createInstance(); 1976 } 1977 1978 // Private static methods. 1979 toExoPlayerCaptionFormat(MediaFormat mediaFormat)1980 private static Format toExoPlayerCaptionFormat(MediaFormat mediaFormat) { 1981 Format.Builder formatBuilder = 1982 new Format.Builder().setSampleMimeType(mediaFormat.getString(MediaFormat.KEY_MIME)); 1983 if (mediaFormat.containsKey(MediaFormat.KEY_CAPTION_SERVICE_NUMBER)) { 1984 formatBuilder.setAccessibilityChannel( 1985 mediaFormat.getInteger(MediaFormat.KEY_CAPTION_SERVICE_NUMBER)); 1986 } 1987 return formatBuilder.build(); 1988 } 1989 toMediaFormat(Format format)1990 private static MediaFormat toMediaFormat(Format format) { 1991 MediaFormat result = new MediaFormat(); 1992 setOptionalMediaFormatInt(result, MediaFormat.KEY_BIT_RATE, format.bitrate); 1993 setOptionalMediaFormatInt(result, MediaFormat.KEY_CHANNEL_COUNT, format.channelCount); 1994 1995 ColorInfo colorInfo = format.colorInfo; 1996 if (colorInfo != null) { 1997 setOptionalMediaFormatInt( 1998 result, MediaFormat.KEY_COLOR_TRANSFER, colorInfo.colorTransfer); 1999 setOptionalMediaFormatInt(result, MediaFormat.KEY_COLOR_RANGE, colorInfo.colorRange); 2000 setOptionalMediaFormatInt(result, MediaFormat.KEY_COLOR_STANDARD, colorInfo.colorSpace); 2001 2002 if (format.colorInfo.hdrStaticInfo != null) { 2003 result.setByteBuffer( 2004 MediaFormat.KEY_HDR_STATIC_INFO, 2005 ByteBuffer.wrap(format.colorInfo.hdrStaticInfo)); 2006 } 2007 } 2008 2009 setOptionalMediaFormatString(result, MediaFormat.KEY_MIME, format.sampleMimeType); 2010 setOptionalMediaFormatString(result, MediaFormat.KEY_CODECS_STRING, format.codecs); 2011 if (format.frameRate != Format.NO_VALUE) { 2012 result.setFloat(MediaFormat.KEY_FRAME_RATE, format.frameRate); 2013 } 2014 setOptionalMediaFormatInt(result, MediaFormat.KEY_WIDTH, format.width); 2015 setOptionalMediaFormatInt(result, MediaFormat.KEY_HEIGHT, format.height); 2016 2017 List<byte[]> initData = format.initializationData; 2018 for (int i = 0; i < initData.size(); i++) { 2019 result.setByteBuffer("csd-" + i, ByteBuffer.wrap(initData.get(i))); 2020 } 2021 setPcmEncoding(format, result); 2022 setOptionalMediaFormatString(result, MediaFormat.KEY_LANGUAGE, format.language); 2023 setOptionalMediaFormatInt(result, MediaFormat.KEY_MAX_INPUT_SIZE, format.maxInputSize); 2024 setOptionalMediaFormatInt(result, MediaFormat.KEY_ROTATION, format.rotationDegrees); 2025 setOptionalMediaFormatInt(result, MediaFormat.KEY_SAMPLE_RATE, format.sampleRate); 2026 setOptionalMediaFormatInt( 2027 result, MediaFormat.KEY_CAPTION_SERVICE_NUMBER, format.accessibilityChannel); 2028 2029 int selectionFlags = format.selectionFlags; 2030 result.setInteger( 2031 MediaFormat.KEY_IS_AUTOSELECT, selectionFlags & C.SELECTION_FLAG_AUTOSELECT); 2032 result.setInteger(MediaFormat.KEY_IS_DEFAULT, selectionFlags & C.SELECTION_FLAG_DEFAULT); 2033 result.setInteger( 2034 MediaFormat.KEY_IS_FORCED_SUBTITLE, selectionFlags & C.SELECTION_FLAG_FORCED); 2035 2036 setOptionalMediaFormatInt(result, MediaFormat.KEY_ENCODER_DELAY, format.encoderDelay); 2037 setOptionalMediaFormatInt(result, MediaFormat.KEY_ENCODER_PADDING, format.encoderPadding); 2038 2039 if (format.pixelWidthHeightRatio != Format.NO_VALUE && format.pixelWidthHeightRatio != 0) { 2040 int parWidth = 1; 2041 int parHeight = 1; 2042 if (format.pixelWidthHeightRatio < 1.0f) { 2043 parHeight = 1 << 30; 2044 parWidth = (int) (format.pixelWidthHeightRatio * parHeight); 2045 } else if (format.pixelWidthHeightRatio > 1.0f) { 2046 parWidth = 1 << 30; 2047 parHeight = (int) (parWidth / format.pixelWidthHeightRatio); 2048 } 2049 result.setInteger(MediaFormat.KEY_PIXEL_ASPECT_RATIO_WIDTH, parWidth); 2050 result.setInteger(MediaFormat.KEY_PIXEL_ASPECT_RATIO_HEIGHT, parHeight); 2051 result.setFloat("pixel-width-height-ratio-float", format.pixelWidthHeightRatio); 2052 } 2053 if (format.drmInitData != null) { 2054 // The crypto mode is propagated along with sample metadata. We also include it in the 2055 // format for convenient use from ExoPlayer. 2056 result.setString("crypto-mode-fourcc", format.drmInitData.schemeType); 2057 } 2058 if (format.subsampleOffsetUs != Format.OFFSET_SAMPLE_RELATIVE) { 2059 result.setLong("subsample-offset-us-long", format.subsampleOffsetUs); 2060 } 2061 // LACK OF SUPPORT FOR: 2062 // format.id; 2063 // format.metadata; 2064 // format.stereoMode; 2065 return result; 2066 } 2067 toByteBuffer(long[] longArray)2068 private static ByteBuffer toByteBuffer(long[] longArray) { 2069 ByteBuffer byteBuffer = ByteBuffer.allocateDirect(longArray.length * Long.BYTES); 2070 for (long element : longArray) { 2071 byteBuffer.putLong(element); 2072 } 2073 byteBuffer.flip(); 2074 return byteBuffer; 2075 } 2076 toByteBuffer(int[] intArray)2077 private static ByteBuffer toByteBuffer(int[] intArray) { 2078 ByteBuffer byteBuffer = ByteBuffer.allocateDirect(intArray.length * Integer.BYTES); 2079 for (int element : intArray) { 2080 byteBuffer.putInt(element); 2081 } 2082 byteBuffer.flip(); 2083 return byteBuffer; 2084 } 2085 toTypeString(int type)2086 private static String toTypeString(int type) { 2087 switch (type) { 2088 case C.TRACK_TYPE_VIDEO: 2089 return "video"; 2090 case C.TRACK_TYPE_AUDIO: 2091 return "audio"; 2092 case C.TRACK_TYPE_TEXT: 2093 return "text"; 2094 case C.TRACK_TYPE_METADATA: 2095 return "metadata"; 2096 default: 2097 return "unknown"; 2098 } 2099 } 2100 setPcmEncoding(Format format, MediaFormat result)2101 private static void setPcmEncoding(Format format, MediaFormat result) { 2102 int exoPcmEncoding = format.pcmEncoding; 2103 setOptionalMediaFormatInt(result, "exo-pcm-encoding", format.pcmEncoding); 2104 int mediaFormatPcmEncoding; 2105 switch (exoPcmEncoding) { 2106 case C.ENCODING_PCM_8BIT: 2107 mediaFormatPcmEncoding = AudioFormat.ENCODING_PCM_8BIT; 2108 break; 2109 case C.ENCODING_PCM_16BIT: 2110 mediaFormatPcmEncoding = AudioFormat.ENCODING_PCM_16BIT; 2111 break; 2112 case C.ENCODING_PCM_FLOAT: 2113 mediaFormatPcmEncoding = AudioFormat.ENCODING_PCM_FLOAT; 2114 break; 2115 default: 2116 // No matching value. Do nothing. 2117 return; 2118 } 2119 result.setInteger(MediaFormat.KEY_PCM_ENCODING, mediaFormatPcmEncoding); 2120 } 2121 setOptionalMediaFormatInt(MediaFormat mediaFormat, String key, int value)2122 private static void setOptionalMediaFormatInt(MediaFormat mediaFormat, String key, int value) { 2123 if (value != Format.NO_VALUE) { 2124 mediaFormat.setInteger(key, value); 2125 } 2126 } 2127 setOptionalMediaFormatString( MediaFormat mediaFormat, String key, @Nullable String value)2128 private static void setOptionalMediaFormatString( 2129 MediaFormat mediaFormat, String key, @Nullable String value) { 2130 if (value != null) { 2131 mediaFormat.setString(key, value); 2132 } 2133 } 2134 toFrameworkDrmInitData( com.google.android.exoplayer2.drm.DrmInitData exoDrmInitData)2135 private DrmInitData toFrameworkDrmInitData( 2136 com.google.android.exoplayer2.drm.DrmInitData exoDrmInitData) { 2137 try { 2138 return exoDrmInitData != null && mSchemeInitDataConstructor != null 2139 ? new MediaParserDrmInitData(exoDrmInitData) 2140 : null; 2141 } catch (Throwable e) { 2142 if (!mLoggedSchemeInitDataCreationException) { 2143 mLoggedSchemeInitDataCreationException = true; 2144 Log.e(TAG, "Unable to create SchemeInitData instance."); 2145 } 2146 return null; 2147 } 2148 } 2149 2150 /** Returns a new {@link SeekPoint} equivalent to the given {@code exoPlayerSeekPoint}. */ toSeekPoint( com.google.android.exoplayer2.extractor.SeekPoint exoPlayerSeekPoint)2151 private static SeekPoint toSeekPoint( 2152 com.google.android.exoplayer2.extractor.SeekPoint exoPlayerSeekPoint) { 2153 return new SeekPoint(exoPlayerSeekPoint.timeUs, exoPlayerSeekPoint.position); 2154 } 2155 2156 /** 2157 * Introduces random error to the given metric value in order to prevent the identification of 2158 * the parsed media. 2159 */ addDither(long value)2160 private static long addDither(long value) { 2161 // Generate a random in [0, 1]. 2162 double randomDither = ThreadLocalRandom.current().nextFloat(); 2163 // Clamp the random number to [0, 2 * MEDIAMETRICS_DITHER]. 2164 randomDither *= 2 * MEDIAMETRICS_DITHER; 2165 // Translate the random number to [1 - MEDIAMETRICS_DITHER, 1 + MEDIAMETRICS_DITHER]. 2166 randomDither += 1 - MEDIAMETRICS_DITHER; 2167 return value != -1 ? (long) (value * randomDither) : -1; 2168 } 2169 assertValidNames(@onNull String[] names)2170 private static void assertValidNames(@NonNull String[] names) { 2171 for (String name : names) { 2172 if (!EXTRACTOR_FACTORIES_BY_NAME.containsKey(name)) { 2173 throw new IllegalArgumentException( 2174 "Invalid extractor name: " 2175 + name 2176 + ". Supported parsers are: " 2177 + TextUtils.join(", ", EXTRACTOR_FACTORIES_BY_NAME.keySet()) 2178 + "."); 2179 } 2180 } 2181 } 2182 getMediaParserFlags(int flags)2183 private int getMediaParserFlags(int flags) { 2184 @SampleFlags int result = 0; 2185 result |= (flags & C.BUFFER_FLAG_ENCRYPTED) != 0 ? SAMPLE_FLAG_ENCRYPTED : 0; 2186 result |= (flags & C.BUFFER_FLAG_KEY_FRAME) != 0 ? SAMPLE_FLAG_KEY_FRAME : 0; 2187 result |= (flags & C.BUFFER_FLAG_DECODE_ONLY) != 0 ? SAMPLE_FLAG_DECODE_ONLY : 0; 2188 result |= 2189 (flags & C.BUFFER_FLAG_HAS_SUPPLEMENTAL_DATA) != 0 && mIncludeSupplementalData 2190 ? SAMPLE_FLAG_HAS_SUPPLEMENTAL_DATA 2191 : 0; 2192 result |= (flags & C.BUFFER_FLAG_LAST_SAMPLE) != 0 ? SAMPLE_FLAG_LAST_SAMPLE : 0; 2193 return result; 2194 } 2195 2196 @Nullable getSchemeInitDataConstructor()2197 private static Constructor<DrmInitData.SchemeInitData> getSchemeInitDataConstructor() { 2198 // TODO: Use constructor statically when available. 2199 Constructor<DrmInitData.SchemeInitData> constructor; 2200 try { 2201 return DrmInitData.SchemeInitData.class.getConstructor( 2202 UUID.class, String.class, byte[].class); 2203 } catch (Throwable e) { 2204 Log.e(TAG, "Unable to get SchemeInitData constructor."); 2205 return null; 2206 } 2207 } 2208 2209 // Native methods. 2210 nativeSubmitMetrics( String logSessionId, String parserName, boolean createdByName, String parserPool, String lastObservedExceptionName, long resourceByteCount, long durationMillis, String trackMimeTypes, String trackCodecs, String alteredParameters, int videoWidth, int videoHeight)2211 private native void nativeSubmitMetrics( 2212 String logSessionId, 2213 String parserName, 2214 boolean createdByName, 2215 String parserPool, 2216 String lastObservedExceptionName, 2217 long resourceByteCount, 2218 long durationMillis, 2219 String trackMimeTypes, 2220 String trackCodecs, 2221 String alteredParameters, 2222 int videoWidth, 2223 int videoHeight); 2224 2225 // Static initialization. 2226 2227 static { 2228 System.loadLibrary(JNI_LIBRARY_NAME); 2229 2230 // Using a LinkedHashMap to keep the insertion order when iterating over the keys. 2231 LinkedHashMap<String, ExtractorFactory> extractorFactoriesByName = new LinkedHashMap<>(); 2232 // Parsers are ordered to match ExoPlayer's DefaultExtractorsFactory extractor ordering, 2233 // which in turn aims to minimize the chances of incorrect extractor selections. extractorFactoriesByName.put(PARSER_NAME_MATROSKA, MatroskaExtractor::new)2234 extractorFactoriesByName.put(PARSER_NAME_MATROSKA, MatroskaExtractor::new); extractorFactoriesByName.put(PARSER_NAME_FMP4, FragmentedMp4Extractor::new)2235 extractorFactoriesByName.put(PARSER_NAME_FMP4, FragmentedMp4Extractor::new); extractorFactoriesByName.put(PARSER_NAME_MP4, Mp4Extractor::new)2236 extractorFactoriesByName.put(PARSER_NAME_MP4, Mp4Extractor::new); extractorFactoriesByName.put(PARSER_NAME_MP3, Mp3Extractor::new)2237 extractorFactoriesByName.put(PARSER_NAME_MP3, Mp3Extractor::new); extractorFactoriesByName.put(PARSER_NAME_ADTS, AdtsExtractor::new)2238 extractorFactoriesByName.put(PARSER_NAME_ADTS, AdtsExtractor::new); extractorFactoriesByName.put(PARSER_NAME_AC3, Ac3Extractor::new)2239 extractorFactoriesByName.put(PARSER_NAME_AC3, Ac3Extractor::new); extractorFactoriesByName.put(PARSER_NAME_TS, TsExtractor::new)2240 extractorFactoriesByName.put(PARSER_NAME_TS, TsExtractor::new); extractorFactoriesByName.put(PARSER_NAME_FLV, FlvExtractor::new)2241 extractorFactoriesByName.put(PARSER_NAME_FLV, FlvExtractor::new); extractorFactoriesByName.put(PARSER_NAME_OGG, OggExtractor::new)2242 extractorFactoriesByName.put(PARSER_NAME_OGG, OggExtractor::new); extractorFactoriesByName.put(PARSER_NAME_PS, PsExtractor::new)2243 extractorFactoriesByName.put(PARSER_NAME_PS, PsExtractor::new); extractorFactoriesByName.put(PARSER_NAME_WAV, WavExtractor::new)2244 extractorFactoriesByName.put(PARSER_NAME_WAV, WavExtractor::new); extractorFactoriesByName.put(PARSER_NAME_AMR, AmrExtractor::new)2245 extractorFactoriesByName.put(PARSER_NAME_AMR, AmrExtractor::new); extractorFactoriesByName.put(PARSER_NAME_AC4, Ac4Extractor::new)2246 extractorFactoriesByName.put(PARSER_NAME_AC4, Ac4Extractor::new); extractorFactoriesByName.put(PARSER_NAME_FLAC, FlacExtractor::new)2247 extractorFactoriesByName.put(PARSER_NAME_FLAC, FlacExtractor::new); 2248 EXTRACTOR_FACTORIES_BY_NAME = Collections.unmodifiableMap(extractorFactoriesByName); 2249 2250 HashMap<String, Class> expectedTypeByParameterName = new HashMap<>(); expectedTypeByParameterName.put(PARAMETER_ADTS_ENABLE_CBR_SEEKING, Boolean.class)2251 expectedTypeByParameterName.put(PARAMETER_ADTS_ENABLE_CBR_SEEKING, Boolean.class); expectedTypeByParameterName.put(PARAMETER_AMR_ENABLE_CBR_SEEKING, Boolean.class)2252 expectedTypeByParameterName.put(PARAMETER_AMR_ENABLE_CBR_SEEKING, Boolean.class); expectedTypeByParameterName.put(PARAMETER_FLAC_DISABLE_ID3, Boolean.class)2253 expectedTypeByParameterName.put(PARAMETER_FLAC_DISABLE_ID3, Boolean.class); expectedTypeByParameterName.put(PARAMETER_MP4_IGNORE_EDIT_LISTS, Boolean.class)2254 expectedTypeByParameterName.put(PARAMETER_MP4_IGNORE_EDIT_LISTS, Boolean.class); expectedTypeByParameterName.put(PARAMETER_MP4_IGNORE_TFDT_BOX, Boolean.class)2255 expectedTypeByParameterName.put(PARAMETER_MP4_IGNORE_TFDT_BOX, Boolean.class); expectedTypeByParameterName.put( PARAMETER_MP4_TREAT_VIDEO_FRAMES_AS_KEYFRAMES, Boolean.class)2256 expectedTypeByParameterName.put( 2257 PARAMETER_MP4_TREAT_VIDEO_FRAMES_AS_KEYFRAMES, Boolean.class); expectedTypeByParameterName.put(PARAMETER_MATROSKA_DISABLE_CUES_SEEKING, Boolean.class)2258 expectedTypeByParameterName.put(PARAMETER_MATROSKA_DISABLE_CUES_SEEKING, Boolean.class); expectedTypeByParameterName.put(PARAMETER_MP3_DISABLE_ID3, Boolean.class)2259 expectedTypeByParameterName.put(PARAMETER_MP3_DISABLE_ID3, Boolean.class); expectedTypeByParameterName.put(PARAMETER_MP3_ENABLE_CBR_SEEKING, Boolean.class)2260 expectedTypeByParameterName.put(PARAMETER_MP3_ENABLE_CBR_SEEKING, Boolean.class); expectedTypeByParameterName.put(PARAMETER_MP3_ENABLE_INDEX_SEEKING, Boolean.class)2261 expectedTypeByParameterName.put(PARAMETER_MP3_ENABLE_INDEX_SEEKING, Boolean.class); expectedTypeByParameterName.put(PARAMETER_TS_MODE, String.class)2262 expectedTypeByParameterName.put(PARAMETER_TS_MODE, String.class); expectedTypeByParameterName.put(PARAMETER_TS_ALLOW_NON_IDR_AVC_KEYFRAMES, Boolean.class)2263 expectedTypeByParameterName.put(PARAMETER_TS_ALLOW_NON_IDR_AVC_KEYFRAMES, Boolean.class); expectedTypeByParameterName.put(PARAMETER_TS_IGNORE_AAC_STREAM, Boolean.class)2264 expectedTypeByParameterName.put(PARAMETER_TS_IGNORE_AAC_STREAM, Boolean.class); expectedTypeByParameterName.put(PARAMETER_TS_IGNORE_AVC_STREAM, Boolean.class)2265 expectedTypeByParameterName.put(PARAMETER_TS_IGNORE_AVC_STREAM, Boolean.class); expectedTypeByParameterName.put(PARAMETER_TS_IGNORE_SPLICE_INFO_STREAM, Boolean.class)2266 expectedTypeByParameterName.put(PARAMETER_TS_IGNORE_SPLICE_INFO_STREAM, Boolean.class); expectedTypeByParameterName.put(PARAMETER_TS_DETECT_ACCESS_UNITS, Boolean.class)2267 expectedTypeByParameterName.put(PARAMETER_TS_DETECT_ACCESS_UNITS, Boolean.class); expectedTypeByParameterName.put(PARAMETER_TS_ENABLE_HDMV_DTS_AUDIO_STREAMS, Boolean.class)2268 expectedTypeByParameterName.put(PARAMETER_TS_ENABLE_HDMV_DTS_AUDIO_STREAMS, Boolean.class); expectedTypeByParameterName.put(PARAMETER_IN_BAND_CRYPTO_INFO, Boolean.class)2269 expectedTypeByParameterName.put(PARAMETER_IN_BAND_CRYPTO_INFO, Boolean.class); expectedTypeByParameterName.put(PARAMETER_INCLUDE_SUPPLEMENTAL_DATA, Boolean.class)2270 expectedTypeByParameterName.put(PARAMETER_INCLUDE_SUPPLEMENTAL_DATA, Boolean.class); expectedTypeByParameterName.put(PARAMETER_IGNORE_TIMESTAMP_OFFSET, Boolean.class)2271 expectedTypeByParameterName.put(PARAMETER_IGNORE_TIMESTAMP_OFFSET, Boolean.class); expectedTypeByParameterName.put(PARAMETER_EAGERLY_EXPOSE_TRACKTYPE, Boolean.class)2272 expectedTypeByParameterName.put(PARAMETER_EAGERLY_EXPOSE_TRACKTYPE, Boolean.class); expectedTypeByParameterName.put(PARAMETER_EXPOSE_DUMMY_SEEKMAP, Boolean.class)2273 expectedTypeByParameterName.put(PARAMETER_EXPOSE_DUMMY_SEEKMAP, Boolean.class); expectedTypeByParameterName.put( PARAMETER_EXPOSE_CHUNK_INDEX_AS_MEDIA_FORMAT, Boolean.class)2274 expectedTypeByParameterName.put( 2275 PARAMETER_EXPOSE_CHUNK_INDEX_AS_MEDIA_FORMAT, Boolean.class); expectedTypeByParameterName.put( PARAMETER_OVERRIDE_IN_BAND_CAPTION_DECLARATIONS, Boolean.class)2276 expectedTypeByParameterName.put( 2277 PARAMETER_OVERRIDE_IN_BAND_CAPTION_DECLARATIONS, Boolean.class); expectedTypeByParameterName.put(PARAMETER_EXPOSE_EMSG_TRACK, Boolean.class)2278 expectedTypeByParameterName.put(PARAMETER_EXPOSE_EMSG_TRACK, Boolean.class); 2279 // We do not check PARAMETER_EXPOSE_CAPTION_FORMATS here, and we do it in setParameters 2280 // instead. Checking that the value is a List is insufficient to catch wrong parameter 2281 // value types. 2282 int sumOfParameterNameLengths = 2283 expectedTypeByParameterName.keySet().stream() 2284 .map(String::length) 2285 .reduce(0, Integer::sum); 2286 sumOfParameterNameLengths += PARAMETER_EXPOSE_CAPTION_FORMATS.length(); 2287 // Add space for any required separators. 2288 MEDIAMETRICS_PARAMETER_LIST_MAX_LENGTH = 2289 sumOfParameterNameLengths + expectedTypeByParameterName.size(); 2290 2291 EXPECTED_TYPE_BY_PARAMETER_NAME = Collections.unmodifiableMap(expectedTypeByParameterName); 2292 } 2293 } 2294