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
This commit is contained in:
olly 2016-03-15 04:02:33 -07:00 committed by Oliver Woodman
parent 9cfff0b028
commit d9d6b8540d
5 changed files with 91 additions and 71 deletions

View File

@ -636,9 +636,9 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
*/ */
private static String getSampleMimeType(String containerMimeType, String codecs) { private static String getSampleMimeType(String containerMimeType, String codecs) {
if (MimeTypes.isAudio(containerMimeType)) { if (MimeTypes.isAudio(containerMimeType)) {
return getAudioMediaMimeType(codecs); return MimeTypes.getAudioMediaMimeType(codecs);
} else if (MimeTypes.isVideo(containerMimeType)) { } else if (MimeTypes.isVideo(containerMimeType)) {
return getVideoMediaMimeType(codecs); return MimeTypes.getVideoMediaMimeType(codecs);
} else if (mimeTypeIsRawText(containerMimeType)) { } else if (mimeTypeIsRawText(containerMimeType)) {
return containerMimeType; return containerMimeType;
} else if (MimeTypes.APPLICATION_MP4.equals(containerMimeType)) { } else if (MimeTypes.APPLICATION_MP4.equals(containerMimeType)) {
@ -651,64 +651,6 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
return null; 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. * Returns whether a mimeType is a text sample mimeType.
* *

View File

@ -68,11 +68,11 @@ import java.util.List;
// Scratch variables to avoid allocations. // Scratch variables to avoid allocations.
private final ParsableByteArray seiWrapper; private final ParsableByteArray seiWrapper;
public H264Reader(TrackOutput output, SeiReader seiReader, boolean idrKeyframesOnly) { public H264Reader(TrackOutput output, SeiReader seiReader, boolean allowNonIdrKeyframes) {
super(output); super(output);
this.seiReader = seiReader; this.seiReader = seiReader;
prefixFlags = new boolean[3]; prefixFlags = new boolean[3];
ifrParserBuffer = (idrKeyframesOnly) ? null : new IfrParserBuffer(); ifrParserBuffer = allowNonIdrKeyframes ? new IfrParserBuffer() : null;
sps = new NalUnitTargetBuffer(NAL_UNIT_TYPE_SPS, 128); sps = new NalUnitTargetBuffer(NAL_UNIT_TYPE_SPS, 128);
pps = new NalUnitTargetBuffer(NAL_UNIT_TYPE_PPS, 128); pps = new NalUnitTargetBuffer(NAL_UNIT_TYPE_PPS, 128);
sei = new NalUnitTargetBuffer(NAL_UNIT_TYPE_SEI, 128); sei = new NalUnitTargetBuffer(NAL_UNIT_TYPE_SEI, 128);

View File

@ -37,6 +37,10 @@ import java.io.IOException;
*/ */
public final class TsExtractor implements Extractor { 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 String TAG = "TsExtractor";
private static final int TS_PACKET_SIZE = 188; 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 static final long HEVC_FORMAT_IDENTIFIER = Util.getIntegerCodeForString("HEVC");
private final PtsTimestampAdjuster ptsTimestampAdjuster; private final PtsTimestampAdjuster ptsTimestampAdjuster;
private final boolean idrKeyframesOnly; private final int workaroundFlags;
private final ParsableByteArray tsPacketBuffer; private final ParsableByteArray tsPacketBuffer;
private final ParsableBitArray tsScratch; private final ParsableBitArray tsScratch;
/* package */ final SparseArray<TsPayloadReader> tsPayloadReaders; // Indexed by pid /* package */ final SparseArray<TsPayloadReader> tsPayloadReaders; // Indexed by pid
@ -76,12 +80,12 @@ public final class TsExtractor implements Extractor {
} }
public TsExtractor(PtsTimestampAdjuster ptsTimestampAdjuster) { 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.ptsTimestampAdjuster = ptsTimestampAdjuster;
this.idrKeyframesOnly = idrKeyframesOnly; this.workaroundFlags = workaroundFlags;
tsPacketBuffer = new ParsableByteArray(TS_PACKET_SIZE); tsPacketBuffer = new ParsableByteArray(TS_PACKET_SIZE);
tsScratch = new ParsableBitArray(new byte[3]); tsScratch = new ParsableBitArray(new byte[3]);
tsPayloadReaders = new SparseArray<>(); tsPayloadReaders = new SparseArray<>();
@ -338,8 +342,8 @@ public final class TsExtractor implements Extractor {
pesPayloadReader = new MpegAudioReader(output.track(TS_STREAM_TYPE_MPA_LSF)); pesPayloadReader = new MpegAudioReader(output.track(TS_STREAM_TYPE_MPA_LSF));
break; break;
case TS_STREAM_TYPE_AAC: case TS_STREAM_TYPE_AAC:
pesPayloadReader = new AdtsReader(output.track(TS_STREAM_TYPE_AAC), pesPayloadReader = (workaroundFlags & WORKAROUND_IGNORE_AAC_STREAM) != 0 ? null
new DummyTrackOutput()); : new AdtsReader(output.track(TS_STREAM_TYPE_AAC), new DummyTrackOutput());
break; break;
case TS_STREAM_TYPE_AC3: case TS_STREAM_TYPE_AC3:
pesPayloadReader = new Ac3Reader(output.track(TS_STREAM_TYPE_AC3), false); 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)); pesPayloadReader = new H262Reader(output.track(TS_STREAM_TYPE_H262));
break; break;
case TS_STREAM_TYPE_H264: case TS_STREAM_TYPE_H264:
pesPayloadReader = new H264Reader(output.track(TS_STREAM_TYPE_H264), pesPayloadReader = (workaroundFlags & WORKAROUND_IGNORE_H264_STREAM) != 0 ? null
new SeiReader(output.track(TS_STREAM_TYPE_EIA608)), idrKeyframesOnly); : new H264Reader(output.track(TS_STREAM_TYPE_H264),
new SeiReader(output.track(TS_STREAM_TYPE_EIA608)),
(workaroundFlags & WORKAROUND_ALLOW_NON_IDR_KEYFRAMES) != 0);
break; break;
case TS_STREAM_TYPE_H265: case TS_STREAM_TYPE_H265:
pesPayloadReader = new H265Reader(output.track(TS_STREAM_TYPE_H265), pesPayloadReader = new H265Reader(output.track(TS_STREAM_TYPE_H265),

View File

@ -462,7 +462,20 @@ public class HlsChunkSource {
// The master source has yet to instantiate an adjuster for the discontinuity sequence. // The master source has yet to instantiate an adjuster for the discontinuity sequence.
return; 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, extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, extractor,
switchingVariantSpliced); switchingVariantSpliced);
} else { } else {

View File

@ -107,6 +107,65 @@ public final class MimeTypes {
return getTopLevelType(mimeType).equals(BASE_TYPE_APPLICATION); 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}. * Returns the top-level type of {@code mimeType}.
* *