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) {
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.
*

View File

@ -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);

View File

@ -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<TsPayloadReader> 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),

View File

@ -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 {

View File

@ -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}.
*