From d9d6b8540d143b2d9705cb1c64de7165618201c8 Mon Sep 17 00:00:00 2001 From: olly Date: Tue, 15 Mar 2016 04:02:33 -0700 Subject: [PATCH] HLS: Ignore AAC/H264 streams if we know they don't exist. This is needed to support fully demuxed audio in HLS. For the sample I have the video (only) variant still declares an AAC stream. I suspect there's at least one toolchain out there that hardcodes H264 and AAC streams in TS output. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=117224224 --- .../MediaPresentationDescriptionParser.java | 62 +------------------ .../exoplayer/extractor/ts/H264Reader.java | 4 +- .../exoplayer/extractor/ts/TsExtractor.java | 22 ++++--- .../android/exoplayer/hls/HlsChunkSource.java | 15 ++++- .../android/exoplayer/util/MimeTypes.java | 59 ++++++++++++++++++ 5 files changed, 91 insertions(+), 71 deletions(-) diff --git a/library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java b/library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java index c73d9983ac..6e5f5fab34 100644 --- a/library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java +++ b/library/src/main/java/com/google/android/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java @@ -636,9 +636,9 @@ public class MediaPresentationDescriptionParser extends DefaultHandler */ private static String getSampleMimeType(String containerMimeType, String codecs) { if (MimeTypes.isAudio(containerMimeType)) { - return getAudioMediaMimeType(codecs); + return MimeTypes.getAudioMediaMimeType(codecs); } else if (MimeTypes.isVideo(containerMimeType)) { - return getVideoMediaMimeType(codecs); + return MimeTypes.getVideoMediaMimeType(codecs); } else if (mimeTypeIsRawText(containerMimeType)) { return containerMimeType; } else if (MimeTypes.APPLICATION_MP4.equals(containerMimeType)) { @@ -651,64 +651,6 @@ public class MediaPresentationDescriptionParser extends DefaultHandler return null; } - /** - * Derives a video sample mimeType from a codecs attribute. - * - * @param codecs The codecs attribute. - * @return The derived video mimeType, or null if it could not be derived. - */ - private static String getVideoMediaMimeType(String codecs) { - if (codecs == null) { - return null; - } - String[] codecList = codecs.split(","); - for (String codec : codecList) { - codec = codec.trim(); - if (codec.startsWith("avc1") || codec.startsWith("avc3")) { - return MimeTypes.VIDEO_H264; - } else if (codec.startsWith("hev1") || codec.startsWith("hvc1")) { - return MimeTypes.VIDEO_H265; - } else if (codec.startsWith("vp9")) { - return MimeTypes.VIDEO_VP9; - } else if (codec.startsWith("vp8")) { - return MimeTypes.VIDEO_VP8; - } - } - return null; - } - - /** - * Derives a audio sample mimeType from a codecs attribute. - * - * @param codecs The codecs attribute. - * @return The derived audio mimeType, or null if it could not be derived. - */ - private static String getAudioMediaMimeType(String codecs) { - if (codecs == null) { - return null; - } - String[] codecList = codecs.split(","); - for (String codec : codecList) { - codec = codec.trim(); - if (codec.startsWith("mp4a")) { - return MimeTypes.AUDIO_AAC; - } else if (codec.startsWith("ac-3") || codec.startsWith("dac3")) { - return MimeTypes.AUDIO_AC3; - } else if (codec.startsWith("ec-3") || codec.startsWith("dec3")) { - return MimeTypes.AUDIO_E_AC3; - } else if (codec.startsWith("dtsc") || codec.startsWith("dtse")) { - return MimeTypes.AUDIO_DTS; - } else if (codec.startsWith("dtsh") || codec.startsWith("dtsl")) { - return MimeTypes.AUDIO_DTS_HD; - } else if (codec.startsWith("opus")) { - return MimeTypes.AUDIO_OPUS; - } else if (codec.startsWith("vorbis")) { - return MimeTypes.AUDIO_VORBIS; - } - } - return null; - } - /** * Returns whether a mimeType is a text sample mimeType. * diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ts/H264Reader.java b/library/src/main/java/com/google/android/exoplayer/extractor/ts/H264Reader.java index 1261bf70b2..70cc328fef 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ts/H264Reader.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ts/H264Reader.java @@ -68,11 +68,11 @@ import java.util.List; // Scratch variables to avoid allocations. private final ParsableByteArray seiWrapper; - public H264Reader(TrackOutput output, SeiReader seiReader, boolean idrKeyframesOnly) { + public H264Reader(TrackOutput output, SeiReader seiReader, boolean allowNonIdrKeyframes) { super(output); this.seiReader = seiReader; prefixFlags = new boolean[3]; - ifrParserBuffer = (idrKeyframesOnly) ? null : new IfrParserBuffer(); + ifrParserBuffer = allowNonIdrKeyframes ? new IfrParserBuffer() : null; sps = new NalUnitTargetBuffer(NAL_UNIT_TYPE_SPS, 128); pps = new NalUnitTargetBuffer(NAL_UNIT_TYPE_PPS, 128); sei = new NalUnitTargetBuffer(NAL_UNIT_TYPE_SEI, 128); diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ts/TsExtractor.java b/library/src/main/java/com/google/android/exoplayer/extractor/ts/TsExtractor.java index a6d4ef55d5..2962477c2a 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ts/TsExtractor.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ts/TsExtractor.java @@ -37,6 +37,10 @@ import java.io.IOException; */ public final class TsExtractor implements Extractor { + public static final int WORKAROUND_ALLOW_NON_IDR_KEYFRAMES = 1; + public static final int WORKAROUND_IGNORE_AAC_STREAM = 2; + public static final int WORKAROUND_IGNORE_H264_STREAM = 4; + private static final String TAG = "TsExtractor"; private static final int TS_PACKET_SIZE = 188; @@ -61,7 +65,7 @@ public final class TsExtractor implements Extractor { private static final long HEVC_FORMAT_IDENTIFIER = Util.getIntegerCodeForString("HEVC"); private final PtsTimestampAdjuster ptsTimestampAdjuster; - private final boolean idrKeyframesOnly; + private final int workaroundFlags; private final ParsableByteArray tsPacketBuffer; private final ParsableBitArray tsScratch; /* package */ final SparseArray tsPayloadReaders; // Indexed by pid @@ -76,12 +80,12 @@ public final class TsExtractor implements Extractor { } public TsExtractor(PtsTimestampAdjuster ptsTimestampAdjuster) { - this(ptsTimestampAdjuster, true); + this(ptsTimestampAdjuster, 0); } - public TsExtractor(PtsTimestampAdjuster ptsTimestampAdjuster, boolean idrKeyframesOnly) { + public TsExtractor(PtsTimestampAdjuster ptsTimestampAdjuster, int workaroundFlags) { this.ptsTimestampAdjuster = ptsTimestampAdjuster; - this.idrKeyframesOnly = idrKeyframesOnly; + this.workaroundFlags = workaroundFlags; tsPacketBuffer = new ParsableByteArray(TS_PACKET_SIZE); tsScratch = new ParsableBitArray(new byte[3]); tsPayloadReaders = new SparseArray<>(); @@ -338,8 +342,8 @@ public final class TsExtractor implements Extractor { pesPayloadReader = new MpegAudioReader(output.track(TS_STREAM_TYPE_MPA_LSF)); break; case TS_STREAM_TYPE_AAC: - pesPayloadReader = new AdtsReader(output.track(TS_STREAM_TYPE_AAC), - new DummyTrackOutput()); + pesPayloadReader = (workaroundFlags & WORKAROUND_IGNORE_AAC_STREAM) != 0 ? null + : new AdtsReader(output.track(TS_STREAM_TYPE_AAC), new DummyTrackOutput()); break; case TS_STREAM_TYPE_AC3: pesPayloadReader = new Ac3Reader(output.track(TS_STREAM_TYPE_AC3), false); @@ -355,8 +359,10 @@ public final class TsExtractor implements Extractor { pesPayloadReader = new H262Reader(output.track(TS_STREAM_TYPE_H262)); break; case TS_STREAM_TYPE_H264: - pesPayloadReader = new H264Reader(output.track(TS_STREAM_TYPE_H264), - new SeiReader(output.track(TS_STREAM_TYPE_EIA608)), idrKeyframesOnly); + pesPayloadReader = (workaroundFlags & WORKAROUND_IGNORE_H264_STREAM) != 0 ? null + : new H264Reader(output.track(TS_STREAM_TYPE_H264), + new SeiReader(output.track(TS_STREAM_TYPE_EIA608)), + (workaroundFlags & WORKAROUND_ALLOW_NON_IDR_KEYFRAMES) != 0); break; case TS_STREAM_TYPE_H265: pesPayloadReader = new H265Reader(output.track(TS_STREAM_TYPE_H265), diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java index 0f3c11c156..7250410622 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java @@ -462,7 +462,20 @@ public class HlsChunkSource { // The master source has yet to instantiate an adjuster for the discontinuity sequence. return; } - Extractor extractor = new TsExtractor(timestampAdjuster); + int workaroundFlags = 0; + String codecs = enabledVariants[selectedVariantIndex].codecs; + if (!TextUtils.isEmpty(codecs)) { + // Sometimes AAC and H264 streams are declared in TS chunks even though they don't really + // exist. If we know from the codec attribute that they don't exist, then we can explicitly + // ignore them even if they're declared. + if (MimeTypes.getAudioMediaMimeType(codecs) != MimeTypes.AUDIO_AAC) { + workaroundFlags |= TsExtractor.WORKAROUND_IGNORE_AAC_STREAM; + } + if (MimeTypes.getVideoMediaMimeType(codecs) != MimeTypes.VIDEO_H264) { + workaroundFlags |= TsExtractor.WORKAROUND_IGNORE_H264_STREAM; + } + } + Extractor extractor = new TsExtractor(timestampAdjuster, workaroundFlags); extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, extractor, switchingVariantSpliced); } else { diff --git a/library/src/main/java/com/google/android/exoplayer/util/MimeTypes.java b/library/src/main/java/com/google/android/exoplayer/util/MimeTypes.java index f7d67d77f5..ec7da5663e 100644 --- a/library/src/main/java/com/google/android/exoplayer/util/MimeTypes.java +++ b/library/src/main/java/com/google/android/exoplayer/util/MimeTypes.java @@ -107,6 +107,65 @@ public final class MimeTypes { return getTopLevelType(mimeType).equals(BASE_TYPE_APPLICATION); } + + /** + * Derives a video sample mimeType from a codecs attribute. + * + * @param codecs The codecs attribute. + * @return The derived video mimeType, or null if it could not be derived. + */ + public static String getVideoMediaMimeType(String codecs) { + if (codecs == null) { + return null; + } + String[] codecList = codecs.split(","); + for (String codec : codecList) { + codec = codec.trim(); + if (codec.startsWith("avc1") || codec.startsWith("avc3")) { + return MimeTypes.VIDEO_H264; + } else if (codec.startsWith("hev1") || codec.startsWith("hvc1")) { + return MimeTypes.VIDEO_H265; + } else if (codec.startsWith("vp9")) { + return MimeTypes.VIDEO_VP9; + } else if (codec.startsWith("vp8")) { + return MimeTypes.VIDEO_VP8; + } + } + return null; + } + + /** + * Derives a audio sample mimeType from a codecs attribute. + * + * @param codecs The codecs attribute. + * @return The derived audio mimeType, or null if it could not be derived. + */ + public static String getAudioMediaMimeType(String codecs) { + if (codecs == null) { + return null; + } + String[] codecList = codecs.split(","); + for (String codec : codecList) { + codec = codec.trim(); + if (codec.startsWith("mp4a")) { + return MimeTypes.AUDIO_AAC; + } else if (codec.startsWith("ac-3") || codec.startsWith("dac3")) { + return MimeTypes.AUDIO_AC3; + } else if (codec.startsWith("ec-3") || codec.startsWith("dec3")) { + return MimeTypes.AUDIO_E_AC3; + } else if (codec.startsWith("dtsc") || codec.startsWith("dtse")) { + return MimeTypes.AUDIO_DTS; + } else if (codec.startsWith("dtsh") || codec.startsWith("dtsl")) { + return MimeTypes.AUDIO_DTS_HD; + } else if (codec.startsWith("opus")) { + return MimeTypes.AUDIO_OPUS; + } else if (codec.startsWith("vorbis")) { + return MimeTypes.AUDIO_VORBIS; + } + } + return null; + } + /** * Returns the top-level type of {@code mimeType}. *