@Override protected void parsePayload(ParsableByteArray data, long timeUs) { int packetType = data.readUnsignedByte(); // Parse sequence header just in case it was not done before. if (packetType == AAC_PACKET_TYPE_SEQUENCE_HEADER && !hasOutputFormat) { byte[] audioSpecifiConfig = new byte[data.bytesLeft()]; data.readBytes(audioSpecifiConfig, 0, audioSpecifiConfig.length); Pair<Integer, Integer> audioParams = CodecSpecificDataUtil.parseAacAudioSpecificConfig( audioSpecifiConfig); MediaFormat mediaFormat = MediaFormat.createAudioFormat(null, MimeTypes.AUDIO_AAC, MediaFormat.NO_VALUE, MediaFormat.NO_VALUE, getDurationUs(), audioParams.second, audioParams.first, Collections.singletonList(audioSpecifiConfig), null); output.format(mediaFormat); hasOutputFormat = true; } else if (packetType == AAC_PACKET_TYPE_AAC_RAW) { // Sample audio AAC frames int bytesToWrite = data.bytesLeft(); output.sampleData(data, bytesToWrite); output.sampleMetadata(timeUs, C.SAMPLE_FLAG_SYNC, bytesToWrite, 0, null); } }
/** * Parses the sample header. */ private void parseHeader() { adtsScratch.setPosition(0); if (!hasOutputFormat) { int audioObjectType = adtsScratch.readBits(2) + 1; int sampleRateIndex = adtsScratch.readBits(4); adtsScratch.skipBits(1); int channelConfig = adtsScratch.readBits(3); byte[] audioSpecificConfig = CodecSpecificDataUtil.buildAacAudioSpecificConfig( audioObjectType, sampleRateIndex, channelConfig); Pair<Integer, Integer> audioParams = CodecSpecificDataUtil.parseAacAudioSpecificConfig( audioSpecificConfig); MediaFormat mediaFormat = MediaFormat.createAudioFormat(MimeTypes.AUDIO_AAC, MediaFormat.NO_VALUE, audioParams.second, audioParams.first, Collections.singletonList(audioSpecificConfig)); frameDurationUs = (C.MICROS_PER_SECOND * 1024L) / mediaFormat.sampleRate; output.format(mediaFormat); hasOutputFormat = true; } else { adtsScratch.skipBits(10); } adtsScratch.skipBits(4); sampleSize = adtsScratch.readBits(13) - 2 /* the sync word */ - HEADER_SIZE; if (hasCrc) { sampleSize -= CRC_SIZE; } }
/** * Parses the sample header. */ private void parseHeader() { adtsScratch.setPosition(0); if (!hasMediaFormat()) { int audioObjectType = adtsScratch.readBits(2) + 1; int sampleRateIndex = adtsScratch.readBits(4); adtsScratch.skipBits(1); int channelConfig = adtsScratch.readBits(3); byte[] audioSpecificConfig = CodecSpecificDataUtil.buildAudioSpecificConfig( audioObjectType, sampleRateIndex, channelConfig); Pair<Integer, Integer> audioParams = CodecSpecificDataUtil.parseAudioSpecificConfig( audioSpecificConfig); MediaFormat mediaFormat = MediaFormat.createAudioFormat(MimeTypes.AUDIO_AAC, MediaFormat.NO_VALUE, audioParams.second, audioParams.first, Collections.singletonList(audioSpecificConfig)); frameDurationUs = (C.MICROS_PER_SECOND * 1024L) / mediaFormat.sampleRate; setMediaFormat(mediaFormat); } else { adtsScratch.skipBits(10); } adtsScratch.skipBits(4); sampleSize = adtsScratch.readBits(13) - 2 /* the sync word */ - HEADER_SIZE; if (hasCrc) { sampleSize -= CRC_SIZE; } }
/** Constructs and returns a NAL unit with a start code followed by the data in {@code atom}. */ public static byte[] parseChildNalUnit(ParsableByteArray atom) { int length = atom.readUnsignedShort(); int offset = atom.getPosition(); atom.skip(length); return CodecSpecificDataUtil.buildNalUnit(atom.data, offset, length); }
private static Pair<MediaFormat, TrackEncryptionBox> parseMp4aFromParent(ParsableByteArray parent, int position, int size) { parent.setPosition(position + ATOM_HEADER_SIZE); // Start of the mp4a atom (defined in 14496-14) parent.skip(16); int channelCount = parent.readUnsignedShort(); int sampleSize = parent.readUnsignedShort(); parent.skip(4); int sampleRate = parent.readUnsignedFixedPoint1616(); byte[] initializationData = null; TrackEncryptionBox trackEncryptionBox = null; int childPosition = parent.getPosition(); while (childPosition - position < size) { parent.setPosition(childPosition); int childStartPosition = parent.getPosition(); int childAtomSize = parent.readInt(); int childAtomType = parent.readInt(); if (childAtomType == Atom.TYPE_esds) { initializationData = parseEsdsFromParent(parent, childStartPosition); // TODO: Do we really need to do this? See [redacted] // Update sampleRate and channelCount from the AudioSpecificConfig initialization data. Pair<Integer, Integer> audioSpecificConfig = CodecSpecificDataUtil.parseAudioSpecificConfig(initializationData); sampleRate = audioSpecificConfig.first; channelCount = audioSpecificConfig.second; } else if (childAtomType == Atom.TYPE_sinf) { trackEncryptionBox = parseSinfFromParent(parent, childStartPosition, childAtomSize); } childPosition += childAtomSize; } MediaFormat format = MediaFormat.createAudioFormat("audio/mp4a-latm", sampleSize, channelCount, sampleRate, Collections.singletonList(initializationData)); return Pair.create(format, trackEncryptionBox); }
private static void parseAudioSampleEntry(ParsableByteArray parent, int atomType, int position, int size, long durationUs, StsdDataHolder out, int entryIndex) { parent.setPosition(position + Atom.HEADER_SIZE); parent.skipBytes(16); int channelCount = parent.readUnsignedShort(); int sampleSize = parent.readUnsignedShort(); 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) { parent.setPosition(childPosition); int childStartPosition = parent.getPosition(); int childAtomSize = parent.readInt(); Assertions.checkArgument(childAtomSize > 0, "childAtomSize should be positive"); int childAtomType = parent.readInt(); if (atomType == Atom.TYPE_mp4a || atomType == Atom.TYPE_enca) { if (childAtomType == Atom.TYPE_esds) { Pair<String, byte[]> 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<Integer, Integer> audioSpecificConfig = CodecSpecificDataUtil.parseAacAudioSpecificConfig(initializationData); sampleRate = audioSpecificConfig.first; channelCount = audioSpecificConfig.second; } } else if (childAtomType == Atom.TYPE_sinf) { out.trackEncryptionBoxes[entryIndex] = parseSinfFromParent(parent, childStartPosition, childAtomSize); } } else if (atomType == Atom.TYPE_ac_3 && childAtomType == Atom.TYPE_dac3) { // TODO: Choose the right AC-3 track based on the contents of dac3/dec3. // TODO: Add support for encryption (by setting out.trackEncryptionBoxes). parent.setPosition(Atom.HEADER_SIZE + childStartPosition); out.mediaFormat = Ac3Util.parseAnnexFAc3Format(parent); return; } else if (atomType == Atom.TYPE_ec_3 && childAtomType == Atom.TYPE_dec3) { parent.setPosition(Atom.HEADER_SIZE + childStartPosition); out.mediaFormat = Ac3Util.parseAnnexFEAc3Format(parent); return; } childPosition += childAtomSize; } // If the media type was not recognized, ignore the track. if (mimeType == null) { return; } out.mediaFormat = MediaFormat.createAudioFormat(mimeType, sampleSize, durationUs, channelCount, sampleRate, initializationData == null ? null : Collections.singletonList(initializationData)); }
@Override public void parseStartTag(XmlPullParser parser) throws ParserException { int type = (Integer) getNormalizedAttribute(KEY_TYPE); String value; index = parseInt(parser, KEY_INDEX, -1); bitrate = parseRequiredInt(parser, KEY_BITRATE); if (type == StreamElement.TYPE_VIDEO) { maxHeight = parseRequiredInt(parser, KEY_MAX_HEIGHT); maxWidth = parseRequiredInt(parser, KEY_MAX_WIDTH); mimeType = fourCCToMimeType(parseRequiredString(parser, KEY_FOUR_CC)); } else { maxHeight = -1; maxWidth = -1; String fourCC = parser.getAttributeValue(null, KEY_FOUR_CC); // If fourCC is missing and the stream type is audio, we assume AAC. mimeType = fourCC != null ? fourCCToMimeType(fourCC) : type == StreamElement.TYPE_AUDIO ? MimeTypes.AUDIO_AAC : null; } if (type == StreamElement.TYPE_AUDIO) { samplingRate = parseRequiredInt(parser, KEY_SAMPLING_RATE); channels = parseRequiredInt(parser, KEY_CHANNELS); } else { samplingRate = -1; channels = -1; } value = parser.getAttributeValue(null, KEY_CODEC_PRIVATE_DATA); if (value != null && value.length() > 0) { byte[] codecPrivateData = hexStringToByteArray(value); byte[][] split = CodecSpecificDataUtil.splitNalUnits(codecPrivateData); if (split == null) { csd.add(codecPrivateData); } else { for (int i = 0; i < split.length; i++) { csd.add(split[i]); } } } }
/** * Parses the sample header. */ private void parseAdtsHeader() { adtsScratch.setPosition(0); if (!hasOutputFormat) { int audioObjectType = adtsScratch.readBits(2) + 1; if (audioObjectType != 2) { // The stream indicates AAC-Main (1), AAC-SSR (3) or AAC-LTP (4). When the stream indicates // AAC-Main it's more likely that the stream contains HE-AAC (5), which cannot be // represented correctly in the 2 bit audio_object_type field in the ADTS header. In // practice when the stream indicates AAC-SSR or AAC-LTP it more commonly contains AAC-LC or // HE-AAC. Since most Android devices don't support AAC-Main, AAC-SSR or AAC-LTP, and since // indicating AAC-LC works for HE-AAC streams, we pretend that we're dealing with AAC-LC and // hope for the best. In practice this often works. // See: https://github.com/google/ExoPlayer/issues/774 // See: https://github.com/google/ExoPlayer/issues/1383 Log.w(TAG, "Detected audio object type: " + audioObjectType + ", but assuming AAC LC."); audioObjectType = 2; } int sampleRateIndex = adtsScratch.readBits(4); adtsScratch.skipBits(1); int channelConfig = adtsScratch.readBits(3); byte[] audioSpecificConfig = CodecSpecificDataUtil.buildAacAudioSpecificConfig( audioObjectType, sampleRateIndex, channelConfig); Pair<Integer, Integer> audioParams = CodecSpecificDataUtil.parseAacAudioSpecificConfig( audioSpecificConfig); MediaFormat mediaFormat = MediaFormat.createAudioFormat(null, MimeTypes.AUDIO_AAC, MediaFormat.NO_VALUE, MediaFormat.NO_VALUE, C.UNKNOWN_TIME_US, audioParams.second, audioParams.first, Collections.singletonList(audioSpecificConfig), null); // In this class a sample is an access unit, but the MediaFormat sample rate specifies the // number of PCM audio samples per second. sampleDurationUs = (C.MICROS_PER_SECOND * 1024) / mediaFormat.sampleRate; output.format(mediaFormat); hasOutputFormat = true; } else { adtsScratch.skipBits(10); } adtsScratch.skipBits(4); int sampleSize = adtsScratch.readBits(13) - 2 /* the sync word */ - HEADER_SIZE; if (hasCrc) { sampleSize -= CRC_SIZE; } setReadingSampleState(output, sampleDurationUs, 0, sampleSize); }
@Override public void parseStartTag(XmlPullParser parser) throws ParserException { int type = (Integer) getNormalizedAttribute(KEY_TYPE); String value; index = parseInt(parser, KEY_INDEX, -1); bitrate = parseRequiredInt(parser, KEY_BITRATE); language = (String) getNormalizedAttribute(KEY_LANGUAGE); if (type == StreamElement.TYPE_VIDEO) { maxHeight = parseRequiredInt(parser, KEY_MAX_HEIGHT); maxWidth = parseRequiredInt(parser, KEY_MAX_WIDTH); mimeType = fourCCToMimeType(parseRequiredString(parser, KEY_FOUR_CC)); } else { maxHeight = -1; maxWidth = -1; String fourCC = parser.getAttributeValue(null, KEY_FOUR_CC); // If fourCC is missing and the stream type is audio, we assume AAC. mimeType = fourCC != null ? fourCCToMimeType(fourCC) : type == StreamElement.TYPE_AUDIO ? MimeTypes.AUDIO_AAC : null; } if (type == StreamElement.TYPE_AUDIO) { samplingRate = parseRequiredInt(parser, KEY_SAMPLING_RATE); channels = parseRequiredInt(parser, KEY_CHANNELS); } else { samplingRate = -1; channels = -1; } value = parser.getAttributeValue(null, KEY_CODEC_PRIVATE_DATA); if (value != null && value.length() > 0) { byte[] codecPrivateData = Util.getBytesFromHexString(value); byte[][] split = CodecSpecificDataUtil.splitNalUnits(codecPrivateData); if (split == null) { csd.add(codecPrivateData); } else { for (int i = 0; i < split.length; i++) { csd.add(split[i]); } } } }
private static Pair<MediaFormat, TrackEncryptionBox> parseAudioSampleEntry( ParsableByteArray parent, int atomType, int position, int size) { parent.setPosition(position + ATOM_HEADER_SIZE); parent.skip(16); int channelCount = parent.readUnsignedShort(); int sampleSize = parent.readUnsignedShort(); parent.skip(4); int sampleRate = parent.readUnsignedFixedPoint1616(); int bitrate = MediaFormat.NO_VALUE; byte[] initializationData = null; TrackEncryptionBox trackEncryptionBox = null; int childPosition = parent.getPosition(); while (childPosition - position < size) { parent.setPosition(childPosition); int childStartPosition = parent.getPosition(); int childAtomSize = parent.readInt(); 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<Integer, Integer> audioSpecificConfig = CodecSpecificDataUtil.parseAudioSpecificConfig(initializationData); sampleRate = audioSpecificConfig.first; channelCount = audioSpecificConfig.second; } else if (childAtomType == Atom.TYPE_sinf) { trackEncryptionBox = parseSinfFromParent(parent, childStartPosition, childAtomSize); } } else if (atomType == Atom.TYPE_ac_3 && childAtomType == Atom.TYPE_dac3) { // TODO: Choose the right AC-3 track based on the contents of dac3/dec3. Ac3Format ac3Format = parseAc3SpecificBoxFromParent(parent, childStartPosition); if (ac3Format != null) { sampleRate = ac3Format.sampleRate; channelCount = ac3Format.channelCount; bitrate = ac3Format.bitrate; } // TODO: Add support for encrypted AC-3. trackEncryptionBox = null; } else if (atomType == Atom.TYPE_ec_3 && childAtomType == Atom.TYPE_dec3) { sampleRate = parseEc3SpecificBoxFromParent(parent, childStartPosition); trackEncryptionBox = null; } childPosition += childAtomSize; } 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; } MediaFormat format = MediaFormat.createAudioFormat( mimeType, sampleSize, channelCount, sampleRate, bitrate, initializationData == null ? null : Collections.singletonList(initializationData)); return Pair.create(format, trackEncryptionBox); }
private static byte[] parseChildNalUnit(ParsableByteArray atom) { int length = atom.readUnsignedShort(); int offset = atom.getPosition(); atom.skip(length); return CodecSpecificDataUtil.buildNalUnit(atom.data, offset, length); }
@Override public void parseStartTag(XmlPullParser parser) throws ParserException { int type = (Integer) getNormalizedAttribute(KEY_TYPE); content = null; String value; index = parseInt(parser, KEY_INDEX, -1); bitrate = parseRequiredInt(parser, KEY_BITRATE); nalUnitLengthField = parseInt(parser, KEY_NAL_UNIT_LENGTH_FIELD, 4); if (type == StreamElement.TYPE_VIDEO) { maxHeight = parseRequiredInt(parser, KEY_MAX_HEIGHT); maxWidth = parseRequiredInt(parser, KEY_MAX_WIDTH); mimeType = fourCCToMimeType(parseRequiredString(parser, KEY_FOUR_CC)); } else { maxHeight = -1; maxWidth = -1; String fourCC = parser.getAttributeValue(null, KEY_FOUR_CC); // If fourCC is missing and the stream type is audio, we assume AAC. mimeType = fourCC != null ? fourCCToMimeType(fourCC) : type == StreamElement.TYPE_AUDIO ? MimeTypes.AUDIO_AAC : null; } if (type == StreamElement.TYPE_AUDIO) { samplingRate = parseRequiredInt(parser, KEY_SAMPLING_RATE); channels = parseRequiredInt(parser, KEY_CHANNELS); bitPerSample = parseRequiredInt(parser, KEY_BITS_PER_SAMPLE); packetSize = parseRequiredInt(parser, KEY_PACKET_SIZE); audioTag = parseRequiredInt(parser, KEY_AUDIO_TAG); } else { samplingRate = -1; channels = -1; bitPerSample = -1; packetSize = -1; audioTag = -1; } value = parser.getAttributeValue(null, KEY_CODEC_PRIVATE_DATA); if (value != null && value.length() > 0) { byte[] codecPrivateData = hexStringToByteArray(value); byte[][] split = CodecSpecificDataUtil.splitNalUnits(codecPrivateData); if (split == null) { csd.add(codecPrivateData); } else { for (int i = 0; i < split.length; i++) { Pair<Integer, Integer> spsParameters = CodecSpecificDataUtil.parseSpsNalUnit(split[i]); if (spsParameters != null) { profile = spsParameters.first; level = spsParameters.second; } csd.add(split[i]); } } } }
private static Pair<MediaFormat, TrackEncryptionBox> parseAudioSampleEntry( ParsableByteArray parent, int atomType, int position, int size) { parent.setPosition(position + Mp4Util.ATOM_HEADER_SIZE); parent.skip(16); int channelCount = parent.readUnsignedShort(); int sampleSize = parent.readUnsignedShort(); parent.skip(4); int sampleRate = parent.readUnsignedFixedPoint1616(); int bitrate = MediaFormat.NO_VALUE; byte[] initializationData = null; TrackEncryptionBox trackEncryptionBox = null; int childPosition = parent.getPosition(); while (childPosition - position < size) { parent.setPosition(childPosition); int childStartPosition = parent.getPosition(); int childAtomSize = parent.readInt(); Assertions.checkArgument(childAtomSize > 0, "childAtomSize should be positive"); 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<Integer, Integer> audioSpecificConfig = CodecSpecificDataUtil.parseAudioSpecificConfig(initializationData); sampleRate = audioSpecificConfig.first; channelCount = audioSpecificConfig.second; } else if (childAtomType == Atom.TYPE_sinf) { trackEncryptionBox = parseSinfFromParent(parent, childStartPosition, childAtomSize); } } else if (atomType == Atom.TYPE_ac_3 && childAtomType == Atom.TYPE_dac3) { // TODO: Choose the right AC-3 track based on the contents of dac3/dec3. Ac3Format ac3Format = parseAc3SpecificBoxFromParent(parent, childStartPosition); if (ac3Format != null) { sampleRate = ac3Format.sampleRate; channelCount = ac3Format.channelCount; bitrate = ac3Format.bitrate; } // TODO: Add support for encrypted AC-3. trackEncryptionBox = null; } else if (atomType == Atom.TYPE_ec_3 && childAtomType == Atom.TYPE_dec3) { sampleRate = parseEc3SpecificBoxFromParent(parent, childStartPosition); trackEncryptionBox = null; } childPosition += childAtomSize; } 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; } MediaFormat format = MediaFormat.createAudioFormat( mimeType, sampleSize, channelCount, sampleRate, bitrate, initializationData == null ? null : Collections.singletonList(initializationData)); return Pair.create(format, trackEncryptionBox); }
@Override public void parseStartTag(XmlPullParser parser) throws ParserException { int type = (Integer) getNormalizedAttribute(KEY_TYPE); content = null; String value; index = parseInt(parser, KEY_INDEX, -1); bitrate = parseRequiredInt(parser, KEY_BITRATE); nalUnitLengthField = parseInt(parser, KEY_NAL_UNIT_LENGTH_FIELD, 4); if (type == StreamElement.TYPE_VIDEO) { maxHeight = parseRequiredInt(parser, KEY_MAX_HEIGHT); maxWidth = parseRequiredInt(parser, KEY_MAX_WIDTH); fourCC = parseRequiredString(parser, KEY_FOUR_CC); } else { maxHeight = -1; maxWidth = -1; fourCC = parser.getAttributeValue(null, KEY_FOUR_CC); } if (type == StreamElement.TYPE_AUDIO) { samplingRate = parseRequiredInt(parser, KEY_SAMPLING_RATE); channels = parseRequiredInt(parser, KEY_CHANNELS); bitPerSample = parseRequiredInt(parser, KEY_BITS_PER_SAMPLE); packetSize = parseRequiredInt(parser, KEY_PACKET_SIZE); audioTag = parseRequiredInt(parser, KEY_AUDIO_TAG); } else { samplingRate = -1; channels = -1; bitPerSample = -1; packetSize = -1; audioTag = -1; } value = parser.getAttributeValue(null, KEY_CODEC_PRIVATE_DATA); if (value != null && value.length() > 0) { byte[] codecPrivateData = hexStringToByteArray(value); byte[][] split = CodecSpecificDataUtil.splitNalUnits(codecPrivateData); if (split == null) { csd.add(codecPrivateData); } else { for (int i = 0; i < split.length; i++) { Pair<Integer, Integer> spsParameters = CodecSpecificDataUtil.parseSpsNalUnit(split[i]); if (spsParameters != null) { profile = spsParameters.first; level = spsParameters.second; } csd.add(split[i]); } } } }