From a4ff13d7da0878ed07030db1691d5c2169d90dfa Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Fri, 26 Jun 2015 14:37:48 +0100 Subject: [PATCH] Set a MIME type based on the esds ObjectTypeIndication. Issue: #576 --- .../exoplayer/extractor/mp4/AtomParsers.java | 95 ++++++++++++++----- .../exoplayer/extractor/mp4/Mp4Extractor.java | 4 +- .../exoplayer/extractor/ts/AdtsReader.java | 4 +- .../SmoothStreamingChunkSource.java | 2 +- .../exoplayer/util/CodecSpecificDataUtil.java | 6 +- 5 files changed, 79 insertions(+), 32 deletions(-) diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/AtomParsers.java b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/AtomParsers.java index 9ae31e4a26..4b5f2ce7f3 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/AtomParsers.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/AtomParsers.java @@ -64,8 +64,9 @@ import java.util.List; long mediaTimescale = parseMdhd(mdia.getLeafAtomOfType(Atom.TYPE_mdhd).data); StsdDataHolder stsdData = parseStsd(stbl.getLeafAtomOfType(Atom.TYPE_stsd).data, durationUs); - return new Track(id, trackType, mediaTimescale, durationUs, stsdData.mediaFormat, - stsdData.trackEncryptionBoxes, stsdData.nalUnitLengthFieldLength); + return stsdData.mediaFormat == null ? null + : new Track(id, trackType, mediaTimescale, durationUs, stsdData.mediaFormat, + stsdData.trackEncryptionBoxes, stsdData.nalUnitLengthFieldLength); } /** @@ -388,9 +389,10 @@ import java.util.List; out.nalUnitLengthFieldLength = hvcCData.second; } else if (childAtomType == Atom.TYPE_esds) { Assertions.checkState(mimeType == null); - mimeType = MimeTypes.VIDEO_MP4V; - initializationData = - Collections.singletonList(parseEsdsFromParent(parent, childStartPosition)); + Pair mimeTypeAndInitializationData = + parseEsdsFromParent(parent, childStartPosition); + mimeType = mimeTypeAndInitializationData.first; + initializationData = Collections.singletonList(mimeTypeAndInitializationData.second); } else if (childAtomType == Atom.TYPE_sinf) { out.trackEncryptionBoxes[entryIndex] = parseSinfFromParent(parent, childStartPosition, childAtomSize); @@ -399,6 +401,12 @@ import java.util.List; } childPosition += childAtomSize; } + + // If the media type was not recognized, ignore the track. + if (mimeType == null) { + return; + } + out.mediaFormat = MediaFormat.createVideoFormat(mimeType, MediaFormat.NO_VALUE, durationUs, width, height, pixelWidthHeightRatio, initializationData); } @@ -529,6 +537,14 @@ import java.util.List; parent.skipBytes(4); int sampleRate = parent.readUnsignedFixedPoint1616(); + // If the atom type determines a MIME type, set it immediately. + String mimeType = null; + if (atomType == Atom.TYPE_ac_3) { + mimeType = MimeTypes.AUDIO_AC3; + } else if (atomType == Atom.TYPE_ec_3) { + mimeType = MimeTypes.AUDIO_EC3; + } + byte[] initializationData = null; int childPosition = parent.getPosition(); while (childPosition - position < size) { @@ -539,13 +555,18 @@ import java.util.List; int childAtomType = parent.readInt(); if (atomType == Atom.TYPE_mp4a || atomType == Atom.TYPE_enca) { if (childAtomType == Atom.TYPE_esds) { - initializationData = parseEsdsFromParent(parent, childStartPosition); - // TODO: Do we really need to do this? See [Internal: b/10903778] - // Update sampleRate and channelCount from the AudioSpecificConfig initialization data. - Pair audioSpecificConfig = - CodecSpecificDataUtil.parseAudioSpecificConfig(initializationData); - sampleRate = audioSpecificConfig.first; - channelCount = audioSpecificConfig.second; + Pair mimeTypeAndInitializationData = + parseEsdsFromParent(parent, childStartPosition); + mimeType = mimeTypeAndInitializationData.first; + initializationData = mimeTypeAndInitializationData.second; + if (MimeTypes.AUDIO_AAC.equals(mimeType)) { + // TODO: Do we really need to do this? See [Internal: b/10903778] + // Update sampleRate and channelCount from the AudioSpecificConfig initialization data. + Pair audioSpecificConfig = + CodecSpecificDataUtil.parseAacAudioSpecificConfig(initializationData); + sampleRate = audioSpecificConfig.first; + channelCount = audioSpecificConfig.second; + } } else if (childAtomType == Atom.TYPE_sinf) { out.trackEncryptionBoxes[entryIndex] = parseSinfFromParent(parent, childStartPosition, childAtomSize); @@ -564,14 +585,9 @@ import java.util.List; childPosition += childAtomSize; } - // Set the MIME type for ac-3/ec-3 atoms even if the dac3/dec3 child atom is missing. - String mimeType; - if (atomType == Atom.TYPE_ac_3) { - mimeType = MimeTypes.AUDIO_AC3; - } else if (atomType == Atom.TYPE_ec_3) { - mimeType = MimeTypes.AUDIO_EC3; - } else { - mimeType = MimeTypes.AUDIO_AAC; + // If the media type was not recognized, ignore the track. + if (mimeType == null) { + return; } out.mediaFormat = MediaFormat.createAudioFormat(mimeType, sampleSize, durationUs, channelCount, @@ -580,7 +596,7 @@ import java.util.List; } /** Returns codec-specific initialization data contained in an esds box. */ - private static byte[] parseEsdsFromParent(ParsableByteArray parent, int position) { + private static Pair parseEsdsFromParent(ParsableByteArray parent, int position) { parent.setPosition(position + Atom.HEADER_SIZE + 4); // Start of the ES_Descriptor (defined in 14496-1) parent.skipBytes(1); // ES_Descriptor tag @@ -607,9 +623,40 @@ import java.util.List; while (varIntByte > 127) { varIntByte = parent.readUnsignedByte(); } - parent.skipBytes(13); - // Start of AudioSpecificConfig (defined in 14496-3) + // Set the MIME type based on the object type indication (14496-1 table 5). + int objectTypeIndication = parent.readUnsignedByte(); + String mimeType; + switch (objectTypeIndication) { + case 0x20: + mimeType = MimeTypes.VIDEO_MP4V; + break; + case 0x21: + mimeType = MimeTypes.VIDEO_H264; + break; + case 0x23: + mimeType = MimeTypes.VIDEO_H265; + break; + case 0x40: + mimeType = MimeTypes.AUDIO_AAC; + break; + case 0x6B: + mimeType = MimeTypes.AUDIO_MPEG; + break; + case 0xA5: + mimeType = MimeTypes.AUDIO_AC3; + break; + case 0xA6: + mimeType = MimeTypes.AUDIO_EC3; + break; + default: + mimeType = null; + break; + } + + parent.skipBytes(12); + + // Start of the AudioSpecificConfig. parent.skipBytes(1); // AudioSpecificConfig tag varIntByte = parent.readUnsignedByte(); int varInt = varIntByte & 0x7F; @@ -620,7 +667,7 @@ import java.util.List; } byte[] initializationData = new byte[varInt]; parent.readBytes(initializationData, 0, varInt); - return initializationData; + return Pair.create(mimeType, initializationData); } private AtomParsers() { diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/Mp4Extractor.java b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/Mp4Extractor.java index 9840d4cbb1..9ea68fa2ec 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/Mp4Extractor.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/Mp4Extractor.java @@ -225,8 +225,8 @@ public final class Mp4Extractor implements Extractor, SeekMap { } Track track = AtomParsers.parseTrak(atom, moov.getLeafAtomOfType(Atom.TYPE_mvhd)); - if (track == null || track.mediaFormat == null || (track.type != Track.TYPE_AUDIO - && track.type != Track.TYPE_VIDEO && track.type != Track.TYPE_TEXT)) { + if (track == null || (track.type != Track.TYPE_AUDIO && track.type != Track.TYPE_VIDEO + && track.type != Track.TYPE_TEXT)) { continue; } diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ts/AdtsReader.java b/library/src/main/java/com/google/android/exoplayer/extractor/ts/AdtsReader.java index fa4fd51dd2..8121c54901 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ts/AdtsReader.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ts/AdtsReader.java @@ -165,9 +165,9 @@ import java.util.Collections; adtsScratch.skipBits(1); int channelConfig = adtsScratch.readBits(3); - byte[] audioSpecificConfig = CodecSpecificDataUtil.buildAudioSpecificConfig( + byte[] audioSpecificConfig = CodecSpecificDataUtil.buildAacAudioSpecificConfig( audioObjectType, sampleRateIndex, channelConfig); - Pair audioParams = CodecSpecificDataUtil.parseAudioSpecificConfig( + Pair audioParams = CodecSpecificDataUtil.parseAacAudioSpecificConfig( audioSpecificConfig); MediaFormat mediaFormat = MediaFormat.createAudioFormat(MimeTypes.AUDIO_AAC, diff --git a/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java b/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java index ad8a2b9836..91121c59fd 100644 --- a/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java @@ -388,7 +388,7 @@ public class SmoothStreamingChunkSource implements ChunkSource { if (trackElement.csd != null) { csd = Arrays.asList(trackElement.csd); } else { - csd = Collections.singletonList(CodecSpecificDataUtil.buildAudioSpecificConfig( + csd = Collections.singletonList(CodecSpecificDataUtil.buildAacAudioSpecificConfig( trackFormat.audioSamplingRate, trackFormat.numChannels)); } MediaFormat format = MediaFormat.createAudioFormat(mimeType, MediaFormat.NO_VALUE, diff --git a/library/src/main/java/com/google/android/exoplayer/util/CodecSpecificDataUtil.java b/library/src/main/java/com/google/android/exoplayer/util/CodecSpecificDataUtil.java index 8a04f8fd27..fa212d2a19 100644 --- a/library/src/main/java/com/google/android/exoplayer/util/CodecSpecificDataUtil.java +++ b/library/src/main/java/com/google/android/exoplayer/util/CodecSpecificDataUtil.java @@ -47,7 +47,7 @@ public final class CodecSpecificDataUtil { * @param audioSpecificConfig The AudioSpecificConfig to parse. * @return A pair consisting of the sample rate in Hz and the channel count. */ - public static Pair parseAudioSpecificConfig(byte[] audioSpecificConfig) { + public static Pair parseAacAudioSpecificConfig(byte[] audioSpecificConfig) { int audioObjectType = (audioSpecificConfig[0] >> 3) & 0x1F; int byteOffset = audioObjectType == 5 || audioObjectType == 29 ? 1 : 0; int frequencyIndex = (audioSpecificConfig[byteOffset] & 0x7) << 1 @@ -66,7 +66,7 @@ public final class CodecSpecificDataUtil { * @param channelConfig The channel configuration. * @return The AudioSpecificConfig. */ - public static byte[] buildAudioSpecificConfig(int audioObjectType, int sampleRateIndex, + public static byte[] buildAacAudioSpecificConfig(int audioObjectType, int sampleRateIndex, int channelConfig) { byte[] audioSpecificConfig = new byte[2]; audioSpecificConfig[0] = (byte) ((audioObjectType << 3) & 0xF8 | (sampleRateIndex >> 1) & 0x07); @@ -81,7 +81,7 @@ public final class CodecSpecificDataUtil { * @param numChannels The number of channels. * @return The AudioSpecificConfig. */ - public static byte[] buildAudioSpecificConfig(int sampleRate, int numChannels) { + public static byte[] buildAacAudioSpecificConfig(int sampleRate, int numChannels) { int sampleRateIndex = -1; for (int i = 0; i < AUDIO_SPECIFIC_CONFIG_SAMPLING_RATE_TABLE.length; ++i) { if (sampleRate == AUDIO_SPECIFIC_CONFIG_SAMPLING_RATE_TABLE[i]) {