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  *     &#64;Override
128  *     public void onSeekMapFound(int i, &#64;NonNull MediaFormat mediaFormat) {
129  *       // Do nothing.
130  *     }
131  *
132  *     &#64;Override
133  *     public void onTrackDataFound(int i, &#64;NonNull TrackData trackData) {
134  *       MediaFormat mediaFormat = trackData.mediaFormat;
135  *       if (videoTrackIndex == -1 &amp;&amp;
136  *           mediaFormat
137  *               .getString(MediaFormat.KEY_MIME, &#47;* defaultValue= *&#47; "")
138  *               .startsWith("video/")) {
139  *         videoTrackIndex = i;
140  *       }
141  *     }
142  *
143  *     &#64;Override
144  *     public void onSampleDataFound(int trackIndex, &#64;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  *             &#47;* offset= *&#47; 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  *     &#64;Override
162  *     public void onSampleCompleted(
163  *         int trackIndex,
164  *         long timeMicros,
165  *         int flags,
166  *         int size,
167  *         int offset,
168  *         &#64;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  *           &#47;* destPos= *&#47; 0,
179  *           size);
180  *       // Place trailing bytes at the start of the buffer.
181  *       System.arraycopy(
182  *           sampleDataBuffer,
183  *           bytesWrittenCount - offset,
184  *           sampleDataBuffer,
185  *           &#47;* destPos= *&#47; 0,
186  *           &#47;* size= *&#47; 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 &gt; 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