use of com.google.android.exoplayer2.extractor.Extractor in project ExoPlayer by google.
the class ExtractorAsserts method assertBehavior.
/**
* Asserts that an extractor consumes valid input data successfully successfully under the
* conditions specified by {@code simulationConfig}.
*
* <p>The output of the extractor is compared against prerecorded dump files.
*
* @param assertionConfig Details of how to read and process the source and dump files.
* @param simulationConfig Details on the environment to simulate and behaviours to assert.
* @throws IOException If reading from the input fails.
*/
public static void assertBehavior(ExtractorFactory factory, String file, AssertionConfig assertionConfig, SimulationConfig simulationConfig) throws IOException {
// Check behavior prior to initialization.
Extractor extractor = factory.create();
extractor.seek(0, 0);
extractor.release();
// Assert output.
Context context = ApplicationProvider.getApplicationContext();
byte[] fileData = TestUtil.getByteArray(context, file);
String dumpFilesPrefix;
if (assertionConfig.dumpFilesPrefix != null) {
dumpFilesPrefix = assertionConfig.dumpFilesPrefix;
} else {
String[] path = file.split("/");
checkState(path.length > 0 && path[0].equals("media"), "AssertionConfig.dumpFilesPrefix == null but file isn't in a media/ sub-directory.\n" + "Expected : 'media/<path-to-file>'\n" + "Found : '" + file + "'\n" + "You need to set AssertionConfig.dumpFilesPrefix explicitly if your media and dump" + " file aren't located in the expected structure (see docs on" + " AssertionConfig.dumpFilesPrefix)");
path[0] = "extractordumps";
dumpFilesPrefix = Joiner.on('/').join(path);
}
assertOutput(factory.create(), dumpFilesPrefix, fileData, context, assertionConfig.deduplicateConsecutiveFormats, simulationConfig.sniffFirst, simulationConfig.simulateIOErrors, simulationConfig.simulateUnknownLength, simulationConfig.simulatePartialReads);
}
use of com.google.android.exoplayer2.extractor.Extractor in project ExoPlayer by google.
the class ExtractorAsserts method assertOutput.
/**
* Asserts that an extractor consumes valid input data successfully under the specified
* conditions.
*
* @param extractor The {@link Extractor} to be tested.
* @param dumpFilesPrefix The dump files prefix prepended to the dump files path.
* @param data Content of the input file.
* @param context To be used to load the sample file.
* @param sniffFirst Whether to sniff the data by calling {@link Extractor#sniff(ExtractorInput)}
* prior to consuming it.
* @param simulateIOErrors Whether to simulate IO errors.
* @param simulateUnknownLength Whether to simulate unknown input length.
* @param simulatePartialReads Whether to simulate partial reads.
* @throws IOException If reading from the input fails.
*/
private static void assertOutput(Extractor extractor, String dumpFilesPrefix, byte[] data, Context context, boolean deduplicateConsecutiveFormats, boolean sniffFirst, boolean simulateIOErrors, boolean simulateUnknownLength, boolean simulatePartialReads) throws IOException {
FakeExtractorInput input = new FakeExtractorInput.Builder().setData(data).setSimulateIOErrors(simulateIOErrors).setSimulateUnknownLength(simulateUnknownLength).setSimulatePartialReads(simulatePartialReads).build();
if (sniffFirst) {
assertSniff(extractor, input, /* expectedResult= */
true);
input.resetPeekPosition();
}
FakeExtractorOutput extractorOutput = consumeTestData(extractor, input, 0, true, deduplicateConsecutiveFormats);
if (simulateUnknownLength) {
DumpFileAsserts.assertOutput(context, extractorOutput, dumpFilesPrefix + UNKNOWN_LENGTH_EXTENSION);
} else {
DumpFileAsserts.assertOutput(context, extractorOutput, dumpFilesPrefix + ".0" + DUMP_EXTENSION);
}
// Seeking to (timeUs=0, position=0) should always work, and cause the same data to be output.
extractorOutput.clearTrackOutputs();
input.reset();
consumeTestData(extractor, input, /* timeUs= */
0, extractorOutput, false);
if (simulateUnknownLength) {
DumpFileAsserts.assertOutput(context, extractorOutput, dumpFilesPrefix + UNKNOWN_LENGTH_EXTENSION);
} else {
DumpFileAsserts.assertOutput(context, extractorOutput, dumpFilesPrefix + ".0" + DUMP_EXTENSION);
}
SeekMap seekMap = Assertions.checkNotNull(extractorOutput.seekMap);
long durationUs = seekMap.getDurationUs();
// Only seek to the timeUs=0 if the SeekMap is unseekable or the duration is unknown.
int numberSeekTests = seekMap.isSeekable() && durationUs != C.TIME_UNSET ? 4 : 1;
for (int j = 0; j < numberSeekTests; j++) {
long timeUs = durationUs * j / 3;
long position = seekMap.getSeekPoints(timeUs).first.position;
if (timeUs == 0 && position == 0) {
// Already tested.
continue;
}
input.reset();
input.setPosition((int) position);
extractorOutput.clearTrackOutputs();
consumeTestData(extractor, input, timeUs, extractorOutput, false);
if (simulateUnknownLength && timeUs == 0) {
DumpFileAsserts.assertOutput(context, extractorOutput, dumpFilesPrefix + UNKNOWN_LENGTH_EXTENSION);
} else {
DumpFileAsserts.assertOutput(context, extractorOutput, dumpFilesPrefix + '.' + j + DUMP_EXTENSION);
}
}
}
use of com.google.android.exoplayer2.extractor.Extractor in project ExoPlayer by google.
the class TestUtil method extractSeekMap.
/**
* Reads from the given input using the given {@link Extractor}, until it can produce the {@link
* SeekMap} and all of the track formats have been identified, or until the extractor encounters
* EOF.
*
* @param extractor The {@link Extractor} to extractor from input.
* @param output The {@link FakeTrackOutput} to store the extracted {@link SeekMap} and track.
* @param dataSource The {@link DataSource} that will be used to read from the input.
* @param uri The Uri of the input.
* @return The extracted {@link SeekMap}.
* @throws IOException If an error occurred reading from the input, or if the extractor finishes
* reading from input without extracting any {@link SeekMap}.
*/
public static SeekMap extractSeekMap(Extractor extractor, FakeExtractorOutput output, DataSource dataSource, Uri uri) throws IOException {
ExtractorInput input = getExtractorInputFromPosition(dataSource, /* position= */
0, uri);
extractor.init(output);
PositionHolder positionHolder = new PositionHolder();
int readResult = Extractor.RESULT_CONTINUE;
while (true) {
try {
// Keep reading until we get the seek map and the track information.
while (readResult == Extractor.RESULT_CONTINUE && (output.seekMap == null || !output.tracksEnded)) {
readResult = extractor.read(input, positionHolder);
}
for (int i = 0; i < output.trackOutputs.size(); i++) {
int trackId = output.trackOutputs.keyAt(i);
while (readResult == Extractor.RESULT_CONTINUE && output.trackOutputs.get(trackId).lastFormat == null) {
readResult = extractor.read(input, positionHolder);
}
}
} finally {
DataSourceUtil.closeQuietly(dataSource);
}
if (readResult == Extractor.RESULT_SEEK) {
input = getExtractorInputFromPosition(dataSource, positionHolder.position, uri);
readResult = Extractor.RESULT_CONTINUE;
} else if (readResult == Extractor.RESULT_END_OF_INPUT) {
throw new IOException("EOF encountered without seekmap");
}
if (output.seekMap != null) {
return output.seekMap;
}
}
}
use of com.google.android.exoplayer2.extractor.Extractor in project ExoPlayer by google.
the class HlsSampleStreamWrapper method buildTracksFromSampleStreams.
/**
* Builds tracks that are exposed by this {@link HlsSampleStreamWrapper} instance, as well as
* internal data-structures required for operation.
*
* <p>Tracks in HLS are complicated. A HLS multivariant playlist contains a number of "variants".
* Each variant stream typically contains muxed video, audio and (possibly) additional audio,
* metadata and caption tracks. We wish to allow the user to select between an adaptive track that
* spans all variants, as well as each individual variant. If multiple audio tracks are present
* within each variant then we wish to allow the user to select between those also.
*
* <p>To do this, tracks are constructed as follows. The {@link HlsChunkSource} exposes (N+1)
* tracks, where N is the number of variants defined in the HLS multivariant playlist. These
* consist of one adaptive track defined to span all variants and a track for each individual
* variant. The adaptive track is initially selected. The extractor is then prepared to discover
* the tracks inside of each variant stream. The two sets of tracks are then combined by this
* method to create a third set, which is the set exposed by this {@link HlsSampleStreamWrapper}:
*
* <ul>
* <li>The extractor tracks are inspected to infer a "primary" track type. If a video track is
* present then it is always the primary type. If not, audio is the primary type if present.
* Else text is the primary type if present. Else there is no primary type.
* <li>If there is exactly one extractor track of the primary type, it's expanded into (N+1)
* exposed tracks, all of which correspond to the primary extractor track and each of which
* corresponds to a different chunk source track. Selecting one of these tracks has the
* effect of switching the selected track on the chunk source.
* <li>All other extractor tracks are exposed directly. Selecting one of these tracks has the
* effect of selecting an extractor track, leaving the selected track on the chunk source
* unchanged.
* </ul>
*/
@EnsuresNonNull({ "trackGroups", "optionalTrackGroups", "trackGroupToSampleQueueIndex" })
private void buildTracksFromSampleStreams() {
// Iterate through the extractor tracks to discover the "primary" track type, and the index
// of the single track of this type.
int primaryExtractorTrackType = C.TRACK_TYPE_NONE;
int primaryExtractorTrackIndex = C.INDEX_UNSET;
int extractorTrackCount = sampleQueues.length;
for (int i = 0; i < extractorTrackCount; i++) {
@Nullable String sampleMimeType = Assertions.checkStateNotNull(sampleQueues[i].getUpstreamFormat()).sampleMimeType;
int trackType;
if (MimeTypes.isVideo(sampleMimeType)) {
trackType = C.TRACK_TYPE_VIDEO;
} else if (MimeTypes.isAudio(sampleMimeType)) {
trackType = C.TRACK_TYPE_AUDIO;
} else if (MimeTypes.isText(sampleMimeType)) {
trackType = C.TRACK_TYPE_TEXT;
} else {
trackType = C.TRACK_TYPE_NONE;
}
if (getTrackTypeScore(trackType) > getTrackTypeScore(primaryExtractorTrackType)) {
primaryExtractorTrackType = trackType;
primaryExtractorTrackIndex = i;
} else if (trackType == primaryExtractorTrackType && primaryExtractorTrackIndex != C.INDEX_UNSET) {
// We have multiple tracks of the primary type. We only want an index if there only exists a
// single track of the primary type, so unset the index again.
primaryExtractorTrackIndex = C.INDEX_UNSET;
}
}
TrackGroup chunkSourceTrackGroup = chunkSource.getTrackGroup();
int chunkSourceTrackCount = chunkSourceTrackGroup.length;
// Instantiate the necessary internal data-structures.
primaryTrackGroupIndex = C.INDEX_UNSET;
trackGroupToSampleQueueIndex = new int[extractorTrackCount];
for (int i = 0; i < extractorTrackCount; i++) {
trackGroupToSampleQueueIndex[i] = i;
}
// Construct the set of exposed track groups.
TrackGroup[] trackGroups = new TrackGroup[extractorTrackCount];
for (int i = 0; i < extractorTrackCount; i++) {
Format sampleFormat = Assertions.checkStateNotNull(sampleQueues[i].getUpstreamFormat());
if (i == primaryExtractorTrackIndex) {
Format[] formats = new Format[chunkSourceTrackCount];
for (int j = 0; j < chunkSourceTrackCount; j++) {
Format playlistFormat = chunkSourceTrackGroup.getFormat(j);
if (primaryExtractorTrackType == C.TRACK_TYPE_AUDIO && muxedAudioFormat != null) {
playlistFormat = playlistFormat.withManifestFormatInfo(muxedAudioFormat);
}
// If there's only a single variant (chunkSourceTrackCount == 1) then we can safely
// retain all fields from sampleFormat. Else we need to use deriveFormat to retain only
// the fields that will be the same for all variants.
formats[j] = chunkSourceTrackCount == 1 ? sampleFormat.withManifestFormatInfo(playlistFormat) : deriveFormat(playlistFormat, sampleFormat, /* propagateBitrates= */
true);
}
trackGroups[i] = new TrackGroup(uid, formats);
primaryTrackGroupIndex = i;
} else {
@Nullable Format playlistFormat = primaryExtractorTrackType == C.TRACK_TYPE_VIDEO && MimeTypes.isAudio(sampleFormat.sampleMimeType) ? muxedAudioFormat : null;
String muxedTrackGroupId = uid + ":muxed:" + (i < primaryExtractorTrackIndex ? i : i - 1);
trackGroups[i] = new TrackGroup(muxedTrackGroupId, deriveFormat(playlistFormat, sampleFormat, /* propagateBitrates= */
false));
}
}
this.trackGroups = createTrackGroupArrayWithDrmInfo(trackGroups);
Assertions.checkState(optionalTrackGroups == null);
optionalTrackGroups = Collections.emptySet();
}
use of com.google.android.exoplayer2.extractor.Extractor in project ExoPlayer by google.
the class DefaultHlsExtractorFactory method createExtractor.
@Override
public BundledHlsMediaChunkExtractor createExtractor(Uri uri, Format format, @Nullable List<Format> muxedCaptionFormats, TimestampAdjuster timestampAdjuster, Map<String, List<String>> responseHeaders, ExtractorInput sniffingExtractorInput, PlayerId playerId) throws IOException {
@FileTypes.Type int formatInferredFileType = FileTypes.inferFileTypeFromMimeType(format.sampleMimeType);
@FileTypes.Type int responseHeadersInferredFileType = FileTypes.inferFileTypeFromResponseHeaders(responseHeaders);
@FileTypes.Type int uriInferredFileType = FileTypes.inferFileTypeFromUri(uri);
// Defines the order in which to try the extractors.
List<Integer> fileTypeOrder = new ArrayList<>(/* initialCapacity= */
DEFAULT_EXTRACTOR_ORDER.length);
addFileTypeIfValidAndNotPresent(formatInferredFileType, fileTypeOrder);
addFileTypeIfValidAndNotPresent(responseHeadersInferredFileType, fileTypeOrder);
addFileTypeIfValidAndNotPresent(uriInferredFileType, fileTypeOrder);
for (int fileType : DEFAULT_EXTRACTOR_ORDER) {
addFileTypeIfValidAndNotPresent(fileType, fileTypeOrder);
}
// Extractor to be used if the type is not recognized.
@Nullable Extractor fallBackExtractor = null;
sniffingExtractorInput.resetPeekPosition();
for (int i = 0; i < fileTypeOrder.size(); i++) {
int fileType = fileTypeOrder.get(i);
Extractor extractor = checkNotNull(createExtractorByFileType(fileType, format, muxedCaptionFormats, timestampAdjuster));
if (sniffQuietly(extractor, sniffingExtractorInput)) {
return new BundledHlsMediaChunkExtractor(extractor, format, timestampAdjuster);
}
if (fallBackExtractor == null && (fileType == formatInferredFileType || fileType == responseHeadersInferredFileType || fileType == uriInferredFileType || fileType == FileTypes.TS)) {
// If sniffing fails, fallback to the file types inferred from context. If all else fails,
// fallback to Transport Stream. See https://github.com/google/ExoPlayer/issues/8219.
fallBackExtractor = extractor;
}
}
return new BundledHlsMediaChunkExtractor(checkNotNull(fallBackExtractor), format, timestampAdjuster);
}
Aggregations