Optimize extractors order using file extension

PiperOrigin-RevId: 314508481
This commit is contained in:
kimvde 2020-06-03 12:46:26 +01:00 committed by Oliver Woodman
parent 3904f6778a
commit 7df99381c1
5 changed files with 209 additions and 72 deletions

View File

@ -156,12 +156,17 @@
* HLS:
* Add support for upstream discard including cancelation of ongoing load
([#6322](https://github.com/google/ExoPlayer/issues/6322)).
* MP3:
* Add `IndexSeeker` for accurate seeks in VBR streams
* Extractors:
* Add `IndexSeeker` for accurate seeks in VBR MP3 streams
([#6787](https://github.com/google/ExoPlayer/issues/6787)). This seeker
is enabled by passing `FLAG_ENABLE_INDEX_SEEKING` to the `Mp3Extractor`.
It may require to scan a significant portion of the file for seeking,
which may be costly on large files.
* Change the order of extractors for sniffing to reduce start-up latency
in `DefaultExtractorsFactory` and `DefaultHlsExtractorsFactory`
([#6410](https://github.com/google/ExoPlayer/issues/6410)).
* Select first extractors based on the filename extension in
`DefaultExtractorsFactory`.
* Testing
* Add `TestExoPlayer`, a utility class with APIs to create
`SimpleExoPlayer` instances with fake components for testing.
@ -179,9 +184,6 @@
* Cast extension: Implement playlist API and deprecate the old queue
manipulation API.
* Demo app: Retain previous position in list of samples.
* Change the order of extractors for sniffing to reduce start-up latency in
`DefaultExtractorsFactory` and `DefaultHlsExtractorsFactory`
([#6410](https://github.com/google/ExoPlayer/issues/6410)).
* Add Guava dependency.
### 2.11.5 (2020-06-03) ###

View File

@ -276,7 +276,7 @@ public final class ProgressiveMediaSource extends BaseMediaSource
return new ProgressiveMediaPeriod(
playbackProperties.uri,
dataSource,
extractorsFactory.createExtractors(),
extractorsFactory.createExtractors(playbackProperties.uri),
drmSessionManager,
loadableLoadErrorHandlingPolicy,
createEventDispatcher(id),

View File

@ -15,6 +15,23 @@
*/
package com.google.android.exoplayer2.extractor;
import static com.google.android.exoplayer2.util.FilenameUtil.FILE_FORMAT_AC3;
import static com.google.android.exoplayer2.util.FilenameUtil.FILE_FORMAT_AC4;
import static com.google.android.exoplayer2.util.FilenameUtil.FILE_FORMAT_ADTS;
import static com.google.android.exoplayer2.util.FilenameUtil.FILE_FORMAT_AMR;
import static com.google.android.exoplayer2.util.FilenameUtil.FILE_FORMAT_FLAC;
import static com.google.android.exoplayer2.util.FilenameUtil.FILE_FORMAT_FLV;
import static com.google.android.exoplayer2.util.FilenameUtil.FILE_FORMAT_MATROSKA;
import static com.google.android.exoplayer2.util.FilenameUtil.FILE_FORMAT_MP3;
import static com.google.android.exoplayer2.util.FilenameUtil.FILE_FORMAT_MP4;
import static com.google.android.exoplayer2.util.FilenameUtil.FILE_FORMAT_OGG;
import static com.google.android.exoplayer2.util.FilenameUtil.FILE_FORMAT_PS;
import static com.google.android.exoplayer2.util.FilenameUtil.FILE_FORMAT_TS;
import static com.google.android.exoplayer2.util.FilenameUtil.FILE_FORMAT_UNKNOWN;
import static com.google.android.exoplayer2.util.FilenameUtil.FILE_FORMAT_WAV;
import static com.google.android.exoplayer2.util.FilenameUtil.getFormatFromExtension;
import android.net.Uri;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.extractor.amr.AmrExtractor;
import com.google.android.exoplayer2.extractor.flac.FlacExtractor;
@ -32,8 +49,11 @@ import com.google.android.exoplayer2.extractor.ts.PsExtractor;
import com.google.android.exoplayer2.extractor.ts.TsExtractor;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader;
import com.google.android.exoplayer2.extractor.wav.WavExtractor;
import com.google.android.exoplayer2.util.FilenameUtil;
import com.google.android.exoplayer2.util.TimestampAdjuster;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.List;
/**
* An {@link ExtractorsFactory} that provides an array of extractors for the following formats:
@ -63,6 +83,25 @@ import java.lang.reflect.Constructor;
*/
public final class DefaultExtractorsFactory implements ExtractorsFactory {
// Extractors order is optimized according to
// https://docs.google.com/document/d/1w2mKaWMxfz2Ei8-LdxqbPs1VLe_oudB-eryXXw9OvQQ.
private static final int[] DEFAULT_EXTRACTOR_ORDER =
new int[] {
FILE_FORMAT_FLV,
FILE_FORMAT_FLAC,
FILE_FORMAT_WAV,
FILE_FORMAT_MP4,
FILE_FORMAT_AMR,
FILE_FORMAT_PS,
FILE_FORMAT_OGG,
FILE_FORMAT_TS,
FILE_FORMAT_MATROSKA,
FILE_FORMAT_ADTS,
FILE_FORMAT_AC3,
FILE_FORMAT_AC4,
FILE_FORMAT_MP3,
};
@Nullable
private static final Constructor<? extends Extractor> FLAC_EXTENSION_EXTRACTOR_CONSTRUCTOR;
@ -240,48 +279,96 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
@Override
public synchronized Extractor[] createExtractors() {
Extractor[] extractors = new Extractor[14];
// Extractors order is optimized according to
// https://docs.google.com/document/d/1w2mKaWMxfz2Ei8-LdxqbPs1VLe_oudB-eryXXw9OvQQ.
extractors[0] = new FlvExtractor();
return createExtractors(Uri.EMPTY);
}
@Override
public synchronized Extractor[] createExtractors(Uri uri) {
List<Extractor> extractors = new ArrayList<>(/* initialCapacity= */ 14);
String filename = uri.getLastPathSegment();
@FilenameUtil.FileFormat
int extensionFormat = filename == null ? FILE_FORMAT_UNKNOWN : getFormatFromExtension(filename);
addExtractorsForFormat(extensionFormat, extractors);
for (int format : DEFAULT_EXTRACTOR_ORDER) {
if (format != extensionFormat) {
addExtractorsForFormat(format, extractors);
}
}
return extractors.toArray(new Extractor[extractors.size()]);
}
private void addExtractorsForFormat(
@FilenameUtil.FileFormat int fileFormat, List<Extractor> extractors) {
switch (fileFormat) {
case FILE_FORMAT_AC3:
extractors.add(new Ac3Extractor());
break;
case FILE_FORMAT_AC4:
extractors.add(new Ac4Extractor());
break;
case FILE_FORMAT_ADTS:
extractors.add(
new AdtsExtractor(
adtsFlags
| (constantBitrateSeekingEnabled
? AdtsExtractor.FLAG_ENABLE_CONSTANT_BITRATE_SEEKING
: 0)));
break;
case FILE_FORMAT_AMR:
extractors.add(
new AmrExtractor(
amrFlags
| (constantBitrateSeekingEnabled
? AmrExtractor.FLAG_ENABLE_CONSTANT_BITRATE_SEEKING
: 0)));
break;
case FILE_FORMAT_FLAC:
if (FLAC_EXTENSION_EXTRACTOR_CONSTRUCTOR != null) {
try {
extractors[1] = FLAC_EXTENSION_EXTRACTOR_CONSTRUCTOR.newInstance();
extractors.add(FLAC_EXTENSION_EXTRACTOR_CONSTRUCTOR.newInstance());
} catch (Exception e) {
// Should never happen.
throw new IllegalStateException("Unexpected error creating FLAC extractor", e);
}
} else {
extractors[1] = new FlacExtractor(coreFlacFlags);
extractors.add(new FlacExtractor(coreFlacFlags));
}
extractors[2] = new WavExtractor();
extractors[3] = new FragmentedMp4Extractor(fragmentedMp4Flags);
extractors[4] = new Mp4Extractor(mp4Flags);
extractors[5] =
new AmrExtractor(
amrFlags
| (constantBitrateSeekingEnabled
? AmrExtractor.FLAG_ENABLE_CONSTANT_BITRATE_SEEKING
: 0));
extractors[6] = new PsExtractor();
extractors[7] = new OggExtractor();
extractors[8] = new TsExtractor(tsMode, tsFlags);
extractors[9] = new MatroskaExtractor(matroskaFlags);
extractors[10] =
new AdtsExtractor(
adtsFlags
| (constantBitrateSeekingEnabled
? AdtsExtractor.FLAG_ENABLE_CONSTANT_BITRATE_SEEKING
: 0));
extractors[11] = new Ac3Extractor();
extractors[12] = new Ac4Extractor();
extractors[13] =
break;
case FILE_FORMAT_FLV:
extractors.add(new FlvExtractor());
break;
case FILE_FORMAT_MATROSKA:
extractors.add(new MatroskaExtractor(matroskaFlags));
break;
case FILE_FORMAT_MP3:
extractors.add(
new Mp3Extractor(
mp3Flags
| (constantBitrateSeekingEnabled
? Mp3Extractor.FLAG_ENABLE_CONSTANT_BITRATE_SEEKING
: 0));
return extractors;
: 0)));
break;
case FILE_FORMAT_MP4:
extractors.add(new FragmentedMp4Extractor(fragmentedMp4Flags));
extractors.add(new Mp4Extractor(mp4Flags));
break;
case FILE_FORMAT_OGG:
extractors.add(new OggExtractor());
break;
case FILE_FORMAT_PS:
extractors.add(new PsExtractor());
break;
case FILE_FORMAT_TS:
extractors.add(new TsExtractor(tsMode, tsFlags));
break;
case FILE_FORMAT_WAV:
extractors.add(new WavExtractor());
break;
default:
break;
}
}
}

View File

@ -15,9 +15,19 @@
*/
package com.google.android.exoplayer2.extractor;
import android.net.Uri;
/** Factory for arrays of {@link Extractor} instances. */
public interface ExtractorsFactory {
/** Returns an array of new {@link Extractor} instances. */
Extractor[] createExtractors();
/**
* Returns an array of new {@link Extractor} instances to extract the stream corresponding to the
* provided {@link Uri}.
*/
default Extractor[] createExtractors(Uri uri) {
return createExtractors();
}
}

View File

@ -17,6 +17,7 @@ package com.google.android.exoplayer2.extractor;
import static com.google.common.truth.Truth.assertThat;
import android.net.Uri;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.extractor.amr.AmrExtractor;
import com.google.android.exoplayer2.extractor.flac.FlacExtractor;
@ -42,34 +43,71 @@ import org.junit.runner.RunWith;
public final class DefaultExtractorsFactoryTest {
@Test
public void createExtractors_returnExpectedClasses() {
public void createExtractors_withoutUri_optimizesSniffingOrder() {
DefaultExtractorsFactory defaultExtractorsFactory = new DefaultExtractorsFactory();
Extractor[] extractors = defaultExtractorsFactory.createExtractors();
List<Class<?>> listCreatedExtractorClasses = new ArrayList<>();
for (Extractor extractor : extractors) {
listCreatedExtractorClasses.add(extractor.getClass());
}
Class<?>[] expectedExtractorClassses =
new Class<?>[] {
List<Class<? extends Extractor>> extractorClasses = getExtractorClasses(extractors);
assertThat(extractorClasses.subList(0, 3))
.containsExactly(FlvExtractor.class, FlacExtractor.class, WavExtractor.class)
.inOrder();
assertThat(extractorClasses.subList(3, 5))
.containsExactly(Mp4Extractor.class, FragmentedMp4Extractor.class);
assertThat(extractorClasses.subList(5, extractors.length))
.containsExactly(
AmrExtractor.class,
PsExtractor.class,
OggExtractor.class,
TsExtractor.class,
MatroskaExtractor.class,
FragmentedMp4Extractor.class,
Mp4Extractor.class,
Mp3Extractor.class,
AdtsExtractor.class,
Ac3Extractor.class,
TsExtractor.class,
Ac4Extractor.class,
Mp3Extractor.class)
.inOrder();
}
@Test
public void createExtractors_withUri_startsWithExtractorsMatchingExtension() {
DefaultExtractorsFactory defaultExtractorsFactory = new DefaultExtractorsFactory();
Extractor[] extractors = defaultExtractorsFactory.createExtractors(Uri.parse("test.mp4"));
List<Class<? extends Extractor>> extractorClasses = getExtractorClasses(extractors);
assertThat(extractorClasses.subList(0, 2))
.containsExactly(Mp4Extractor.class, FragmentedMp4Extractor.class);
}
@Test
public void createExtractors_withUri_optimizesSniffingOrder() {
DefaultExtractorsFactory defaultExtractorsFactory = new DefaultExtractorsFactory();
Extractor[] extractors = defaultExtractorsFactory.createExtractors(Uri.parse("test.mp4"));
List<Class<? extends Extractor>> extractorClasses = getExtractorClasses(extractors);
assertThat(extractorClasses.subList(2, extractors.length))
.containsExactly(
FlvExtractor.class,
OggExtractor.class,
PsExtractor.class,
FlacExtractor.class,
WavExtractor.class,
AmrExtractor.class,
PsExtractor.class,
OggExtractor.class,
TsExtractor.class,
MatroskaExtractor.class,
AdtsExtractor.class,
Ac3Extractor.class,
Ac4Extractor.class,
FlacExtractor.class
};
Mp3Extractor.class)
.inOrder();
}
assertThat(listCreatedExtractorClasses).containsNoDuplicates();
assertThat(listCreatedExtractorClasses).containsExactlyElementsIn(expectedExtractorClassses);
private static List<Class<? extends Extractor>> getExtractorClasses(Extractor[] extractors) {
List<Class<? extends Extractor>> extractorClasses = new ArrayList<>();
for (Extractor extractor : extractors) {
extractorClasses.add(extractor.getClass());
}
return extractorClasses;
}
}