diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/DefaultTrackOutput.java b/library/src/main/java/com/google/android/exoplayer2/extractor/DefaultTrackOutput.java index 08ec103e40..461da41373 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/DefaultTrackOutput.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/DefaultTrackOutput.java @@ -514,14 +514,10 @@ public final class DefaultTrackOutput implements TrackOutput { if ((flags & C.BUFFER_FLAG_KEY_FRAME) == 0 || !infoQueue.attemptSplice(timeUs)) { return; } - // TODO - We should be able to actually remove the data from the rolling buffer after a - // splice succeeds, but doing so is a little bit tricky; it requires moving data written - // after the last committed sample. pendingSplice = false; } if (needKeyframe) { if ((flags & C.BUFFER_FLAG_KEY_FRAME) == 0) { - // TODO - As above, although this case is probably less worthwhile. return; } needKeyframe = false; diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/flv/VideoTagPayloadReader.java b/library/src/main/java/com/google/android/exoplayer2/extractor/flv/VideoTagPayloadReader.java index 4c31d233a2..3ff7dd608e 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/flv/VideoTagPayloadReader.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/flv/VideoTagPayloadReader.java @@ -19,12 +19,10 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.extractor.TrackOutput; -import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.NalUnitUtil; import com.google.android.exoplayer2.util.ParsableByteArray; -import java.util.ArrayList; -import java.util.List; +import com.google.android.exoplayer2.video.AvcConfig; /** * Parses video tags from an FLV stream and extracts H.264 nal units. @@ -87,15 +85,12 @@ import java.util.List; if (packetType == AVC_PACKET_TYPE_SEQUENCE_HEADER && !hasOutputFormat) { ParsableByteArray videoSequence = new ParsableByteArray(new byte[data.bytesLeft()]); data.readBytes(videoSequence.data, 0, data.bytesLeft()); - - AvcSequenceHeaderData avcData = parseAvcCodecPrivate(videoSequence); - nalUnitLengthFieldLength = avcData.nalUnitLengthFieldLength; - + AvcConfig avcConfig = AvcConfig.parse(videoSequence); + nalUnitLengthFieldLength = avcConfig.nalUnitLengthFieldLength; // Construct and output the format. Format format = Format.createVideoSampleFormat(null, MimeTypes.VIDEO_H264, null, - Format.NO_VALUE, Format.NO_VALUE, avcData.width, avcData.height, - Format.NO_VALUE, avcData.initializationData, Format.NO_VALUE, - avcData.pixelWidthAspectRatio, null); + Format.NO_VALUE, Format.NO_VALUE, avcConfig.width, avcConfig.height, Format.NO_VALUE, + avcConfig.initializationData, Format.NO_VALUE, avcConfig.pixelWidthAspectRatio, null); output.format(format); hasOutputFormat = true; } else if (packetType == AVC_PACKET_TYPE_AVC_NALU) { @@ -132,62 +127,4 @@ import java.util.List; } } - /** - * Builds initialization data for a {@link Format} from H.264 (AVC) codec private data. - * - * @return The AvcSequenceHeader data needed to initialize the video codec. - */ - private AvcSequenceHeaderData parseAvcCodecPrivate(ParsableByteArray buffer) { - // TODO: Deduplicate with AtomParsers.parseAvcCFromParent. - buffer.setPosition(4); - int nalUnitLengthFieldLength = (buffer.readUnsignedByte() & 0x03) + 1; - Assertions.checkState(nalUnitLengthFieldLength != 3); - List initializationData = new ArrayList<>(); - int numSequenceParameterSets = buffer.readUnsignedByte() & 0x1F; - for (int i = 0; i < numSequenceParameterSets; i++) { - initializationData.add(NalUnitUtil.parseChildNalUnit(buffer)); - } - int numPictureParameterSets = buffer.readUnsignedByte(); - for (int j = 0; j < numPictureParameterSets; j++) { - initializationData.add(NalUnitUtil.parseChildNalUnit(buffer)); - } - - float pixelWidthAspectRatio = 1; - int width = Format.NO_VALUE; - int height = Format.NO_VALUE; - if (numSequenceParameterSets > 0) { - byte[] sps = initializationData.get(0); - NalUnitUtil.SpsData spsData = - NalUnitUtil.parseSpsNalUnit(sps, nalUnitLengthFieldLength, sps.length); - width = spsData.width; - height = spsData.height; - pixelWidthAspectRatio = spsData.pixelWidthAspectRatio; - } - - return new AvcSequenceHeaderData(initializationData, nalUnitLengthFieldLength, - width, height, pixelWidthAspectRatio); - } - - /** - * Holds data parsed from an Sequence Header video tag atom. - */ - private static final class AvcSequenceHeaderData { - - public final List initializationData; - public final int nalUnitLengthFieldLength; - public final float pixelWidthAspectRatio; - public final int width; - public final int height; - - public AvcSequenceHeaderData(List initializationData, int nalUnitLengthFieldLength, - int width, int height, float pixelWidthAspectRatio) { - this.initializationData = initializationData; - this.nalUnitLengthFieldLength = nalUnitLengthFieldLength; - this.pixelWidthAspectRatio = pixelWidthAspectRatio; - this.width = width; - this.height = height; - } - - } - } diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java b/library/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java index 592ad0a66d..614dd60acd 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java @@ -15,7 +15,6 @@ */ package com.google.android.exoplayer2.extractor.mkv; -import android.util.Pair; import android.util.SparseArray; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; @@ -35,6 +34,8 @@ import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.NalUnitUtil; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.Util; +import com.google.android.exoplayer2.video.AvcConfig; +import com.google.android.exoplayer2.video.HevcConfig; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -1251,17 +1252,15 @@ public final class MatroskaExtractor implements Extractor { break; case CODEC_ID_H264: mimeType = MimeTypes.VIDEO_H264; - Pair, Integer> h264Data = parseAvcCodecPrivate( - new ParsableByteArray(codecPrivate)); - initializationData = h264Data.first; - nalUnitLengthFieldLength = h264Data.second; + AvcConfig avcConfig = AvcConfig.parse(new ParsableByteArray(codecPrivate)); + initializationData = avcConfig.initializationData; + nalUnitLengthFieldLength = avcConfig.nalUnitLengthFieldLength; break; case CODEC_ID_H265: mimeType = MimeTypes.VIDEO_H265; - Pair, Integer> hevcData = parseHevcCodecPrivate( - new ParsableByteArray(codecPrivate)); - initializationData = hevcData.first; - nalUnitLengthFieldLength = hevcData.second; + HevcConfig hevcConfig = HevcConfig.parse(new ParsableByteArray(codecPrivate)); + initializationData = hevcConfig.initializationData; + nalUnitLengthFieldLength = hevcConfig.nalUnitLengthFieldLength; break; case CODEC_ID_FOURCC: mimeType = MimeTypes.VIDEO_VC1; @@ -1414,89 +1413,6 @@ public final class MatroskaExtractor implements Extractor { } } - /** - * Builds initialization data for a {@link Format} from H.264 (AVC) codec private data. - * - * @return The initialization data for the {@link Format}. - * @throws ParserException If the initialization data could not be built. - */ - private static Pair, Integer> parseAvcCodecPrivate(ParsableByteArray buffer) - throws ParserException { - try { - // TODO: Deduplicate with AtomParsers.parseAvcCFromParent. - buffer.setPosition(4); - int nalUnitLengthFieldLength = (buffer.readUnsignedByte() & 0x03) + 1; - if (nalUnitLengthFieldLength == 3) { - throw new ParserException(); - } - List initializationData = new ArrayList<>(); - int numSequenceParameterSets = buffer.readUnsignedByte() & 0x1F; - for (int i = 0; i < numSequenceParameterSets; i++) { - initializationData.add(NalUnitUtil.parseChildNalUnit(buffer)); - } - int numPictureParameterSets = buffer.readUnsignedByte(); - for (int j = 0; j < numPictureParameterSets; j++) { - initializationData.add(NalUnitUtil.parseChildNalUnit(buffer)); - } - return Pair.create(initializationData, nalUnitLengthFieldLength); - } catch (ArrayIndexOutOfBoundsException e) { - throw new ParserException("Error parsing AVC codec private"); - } - } - - /** - * Builds initialization data for a {@link Format} from H.265 (HEVC) codec private data. - * - * @return The initialization data for the {@link Format}. - * @throws ParserException If the initialization data could not be built. - */ - private static Pair, Integer> parseHevcCodecPrivate(ParsableByteArray parent) - throws ParserException { - try { - // TODO: Deduplicate with AtomParsers.parseHvcCFromParent. - parent.setPosition(21); - int lengthSizeMinusOne = parent.readUnsignedByte() & 0x03; - - // Calculate the combined size of all VPS/SPS/PPS bitstreams. - int numberOfArrays = parent.readUnsignedByte(); - int csdLength = 0; - int csdStartPosition = parent.getPosition(); - for (int i = 0; i < numberOfArrays; i++) { - parent.skipBytes(1); // completeness (1), nal_unit_type (7) - int numberOfNalUnits = parent.readUnsignedShort(); - for (int j = 0; j < numberOfNalUnits; j++) { - int nalUnitLength = parent.readUnsignedShort(); - csdLength += 4 + nalUnitLength; // Start code and NAL unit. - parent.skipBytes(nalUnitLength); - } - } - - // Concatenate the codec-specific data into a single buffer. - parent.setPosition(csdStartPosition); - byte[] buffer = new byte[csdLength]; - int bufferPosition = 0; - for (int i = 0; i < numberOfArrays; i++) { - parent.skipBytes(1); // completeness (1), nal_unit_type (7) - int numberOfNalUnits = parent.readUnsignedShort(); - for (int j = 0; j < numberOfNalUnits; j++) { - int nalUnitLength = parent.readUnsignedShort(); - System.arraycopy(NalUnitUtil.NAL_START_CODE, 0, buffer, bufferPosition, - NalUnitUtil.NAL_START_CODE.length); - bufferPosition += NalUnitUtil.NAL_START_CODE.length; - System.arraycopy(parent.data, parent.getPosition(), buffer, bufferPosition, - nalUnitLength); - bufferPosition += nalUnitLength; - parent.skipBytes(nalUnitLength); - } - } - - List initializationData = csdLength == 0 ? null : Collections.singletonList(buffer); - return Pair.create(initializationData, lengthSizeMinusOne + 1); - } catch (ArrayIndexOutOfBoundsException e) { - throw new ParserException("Error parsing HEVC codec private"); - } - } - /** * Builds initialization data for a {@link Format} from Vorbis codec private data. * diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java b/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java index f6c19f6d29..a5d7fb24f4 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java @@ -25,10 +25,10 @@ import com.google.android.exoplayer2.extractor.GaplessInfoHolder; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.CodecSpecificDataUtil; import com.google.android.exoplayer2.util.MimeTypes; -import com.google.android.exoplayer2.util.NalUnitUtil; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.Util; -import java.util.ArrayList; +import com.google.android.exoplayer2.video.AvcConfig; +import com.google.android.exoplayer2.video.HevcConfig; import java.util.Collections; import java.util.List; @@ -56,7 +56,7 @@ import java.util.List; * @return A {@link Track} instance, or {@code null} if the track's type isn't supported. */ public static Track parseTrak(Atom.ContainerAtom trak, Atom.LeafAtom mvhd, long duration, - DrmInitData drmInitData, boolean isQuickTime) { + DrmInitData drmInitData, boolean isQuickTime) throws ParserException { Atom.ContainerAtom mdia = trak.getContainerAtomOfType(Atom.TYPE_mdia); int trackType = parseHdlr(mdia.getLeafAtomOfType(Atom.TYPE_hdlr).data); if (trackType == C.TRACK_TYPE_UNKNOWN) { @@ -589,7 +589,7 @@ import java.util.List; * @return An object containing the parsed data. */ private static StsdData parseStsd(ParsableByteArray stsd, int trackId, int rotationDegrees, - String language, DrmInitData drmInitData, boolean isQuickTime) { + String language, DrmInitData drmInitData, boolean isQuickTime) throws ParserException { stsd.setPosition(Atom.FULL_HEADER_SIZE); int numberOfEntries = stsd.readInt(); StsdData out = new StsdData(numberOfEntries); @@ -638,7 +638,7 @@ import java.util.List; private static void parseVideoSampleEntry(ParsableByteArray parent, int atomType, int position, int size, int trackId, int rotationDegrees, DrmInitData drmInitData, StsdData out, - int entryIndex) { + int entryIndex) throws ParserException { parent.setPosition(position + Atom.HEADER_SIZE); parent.skipBytes(24); @@ -669,18 +669,20 @@ import java.util.List; if (childAtomType == Atom.TYPE_avcC) { Assertions.checkState(mimeType == null); mimeType = MimeTypes.VIDEO_H264; - AvcCData avcCData = parseAvcCFromParent(parent, childStartPosition); - initializationData = avcCData.initializationData; - out.nalUnitLengthFieldLength = avcCData.nalUnitLengthFieldLength; + parent.setPosition(childStartPosition + Atom.HEADER_SIZE); + AvcConfig avcConfig = AvcConfig.parse(parent); + initializationData = avcConfig.initializationData; + out.nalUnitLengthFieldLength = avcConfig.nalUnitLengthFieldLength; if (!pixelWidthHeightRatioFromPasp) { - pixelWidthHeightRatio = avcCData.pixelWidthAspectRatio; + pixelWidthHeightRatio = avcConfig.pixelWidthAspectRatio; } } else if (childAtomType == Atom.TYPE_hvcC) { Assertions.checkState(mimeType == null); mimeType = MimeTypes.VIDEO_H265; - Pair, Integer> hvcCData = parseHvcCFromParent(parent, childStartPosition); - initializationData = hvcCData.first; - out.nalUnitLengthFieldLength = hvcCData.second; + parent.setPosition(childStartPosition + Atom.HEADER_SIZE); + HevcConfig hevcConfig = HevcConfig.parse(parent); + initializationData = hevcConfig.initializationData; + out.nalUnitLengthFieldLength = hevcConfig.nalUnitLengthFieldLength; } else if (childAtomType == Atom.TYPE_vpcC) { Assertions.checkState(mimeType == null); mimeType = (atomType == Atom.TYPE_vp08) ? MimeTypes.VIDEO_VP8 : MimeTypes.VIDEO_VP9; @@ -710,76 +712,6 @@ import java.util.List; rotationDegrees, pixelWidthHeightRatio, drmInitData); } - private static AvcCData parseAvcCFromParent(ParsableByteArray parent, int position) { - parent.setPosition(position + Atom.HEADER_SIZE + 4); - // Start of the AVCDecoderConfigurationRecord (defined in 14496-15) - int nalUnitLengthFieldLength = (parent.readUnsignedByte() & 0x3) + 1; - if (nalUnitLengthFieldLength == 3) { - throw new IllegalStateException(); - } - List initializationData = new ArrayList<>(); - float pixelWidthAspectRatio = 1; - int numSequenceParameterSets = parent.readUnsignedByte() & 0x1F; - for (int j = 0; j < numSequenceParameterSets; j++) { - initializationData.add(NalUnitUtil.parseChildNalUnit(parent)); - } - int numPictureParameterSets = parent.readUnsignedByte(); - for (int j = 0; j < numPictureParameterSets; j++) { - initializationData.add(NalUnitUtil.parseChildNalUnit(parent)); - } - - if (numSequenceParameterSets > 0) { - // Parse the first sequence parameter set to obtain pixelWidthAspectRatio. - byte[] sps = initializationData.get(0); - pixelWidthAspectRatio = NalUnitUtil - .parseSpsNalUnit(sps, nalUnitLengthFieldLength, sps.length).pixelWidthAspectRatio; - } - - return new AvcCData(initializationData, nalUnitLengthFieldLength, pixelWidthAspectRatio); - } - - private static Pair, Integer> parseHvcCFromParent(ParsableByteArray parent, - int position) { - // Skip to the NAL unit length size field. - parent.setPosition(position + Atom.HEADER_SIZE + 21); - int lengthSizeMinusOne = parent.readUnsignedByte() & 0x03; - - // Calculate the combined size of all VPS/SPS/PPS bitstreams. - int numberOfArrays = parent.readUnsignedByte(); - int csdLength = 0; - int csdStartPosition = parent.getPosition(); - for (int i = 0; i < numberOfArrays; i++) { - parent.skipBytes(1); // completeness (1), nal_unit_type (7) - int numberOfNalUnits = parent.readUnsignedShort(); - for (int j = 0; j < numberOfNalUnits; j++) { - int nalUnitLength = parent.readUnsignedShort(); - csdLength += 4 + nalUnitLength; // Start code and NAL unit. - parent.skipBytes(nalUnitLength); - } - } - - // Concatenate the codec-specific data into a single buffer. - parent.setPosition(csdStartPosition); - byte[] buffer = new byte[csdLength]; - int bufferPosition = 0; - for (int i = 0; i < numberOfArrays; i++) { - parent.skipBytes(1); // completeness (1), nal_unit_type (7) - int numberOfNalUnits = parent.readUnsignedShort(); - for (int j = 0; j < numberOfNalUnits; j++) { - int nalUnitLength = parent.readUnsignedShort(); - System.arraycopy(NalUnitUtil.NAL_START_CODE, 0, buffer, bufferPosition, - NalUnitUtil.NAL_START_CODE.length); - bufferPosition += NalUnitUtil.NAL_START_CODE.length; - System.arraycopy(parent.data, parent.getPosition(), buffer, bufferPosition, nalUnitLength); - bufferPosition += nalUnitLength; - parent.skipBytes(nalUnitLength); - } - } - - List initializationData = csdLength == 0 ? null : Collections.singletonList(buffer); - return Pair.create(initializationData, lengthSizeMinusOne + 1); - } - /** * Parses the edts atom (defined in 14496-12 subsection 8.6.5). * @@ -1192,22 +1124,4 @@ import java.util.List; } - /** - * Holds data parsed from an AvcC atom. - */ - private static final class AvcCData { - - public final List initializationData; - public final int nalUnitLengthFieldLength; - public final float pixelWidthAspectRatio; - - public AvcCData(List initializationData, int nalUnitLengthFieldLength, - float pixelWidthAspectRatio) { - this.initializationData = initializationData; - this.nalUnitLengthFieldLength = nalUnitLengthFieldLength; - this.pixelWidthAspectRatio = pixelWidthAspectRatio; - } - - } - } diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java b/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java index efd262ca69..8945539cc0 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java @@ -330,7 +330,7 @@ public final class FragmentedMp4Extractor implements Extractor { } } - private void onMoovContainerAtomRead(ContainerAtom moov) { + private void onMoovContainerAtomRead(ContainerAtom moov) throws ParserException { Assertions.checkState(sideloadedTrack == null, "Unexpected moov box."); List moovLeafChildren = moov.leafChildren; int moovLeafChildrenSize = moovLeafChildren.size(); diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ogg/VorbisUtil.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ogg/VorbisUtil.java index 043f99d693..79767a00d8 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ogg/VorbisUtil.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ogg/VorbisUtil.java @@ -389,11 +389,9 @@ import java.util.Arrays; if (dimensions != 0) { lookupValuesCount = mapType1QuantValues(entries, dimensions); } else { - // TODO no sample file found yet lookupValuesCount = 0; } } else { - // TODO no sample file found yet lookupValuesCount = entries * dimensions; } // discard (no decoding required yet) diff --git a/library/src/main/java/com/google/android/exoplayer2/video/AvcConfig.java b/library/src/main/java/com/google/android/exoplayer2/video/AvcConfig.java new file mode 100644 index 0000000000..9091ee2966 --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer2/video/AvcConfig.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.video; + +import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.ParserException; +import com.google.android.exoplayer2.util.NalUnitUtil; +import com.google.android.exoplayer2.util.NalUnitUtil.SpsData; +import com.google.android.exoplayer2.util.ParsableByteArray; +import java.util.ArrayList; +import java.util.List; + +/** + * AVC configuration data. + */ +public final class AvcConfig { + + public final List initializationData; + public final int nalUnitLengthFieldLength; + public final int width; + public final int height; + public final float pixelWidthAspectRatio; + + /** + * Parses AVC configuration data. + * + * @param data A {@link ParsableByteArray}, whose position is set to the start of the AVC + * configuration data to parse. + * @return A parsed representation of the HEVC configuration data. + * @throws ParserException If an error occurred parsing the data. + */ + public static AvcConfig parse(ParsableByteArray data) throws ParserException { + try { + data.skipBytes(4); // Skip to the AVCDecoderConfigurationRecord (defined in 14496-15) + int nalUnitLengthFieldLength = (data.readUnsignedByte() & 0x3) + 1; + if (nalUnitLengthFieldLength == 3) { + throw new IllegalStateException(); + } + List initializationData = new ArrayList<>(); + int numSequenceParameterSets = data.readUnsignedByte() & 0x1F; + for (int j = 0; j < numSequenceParameterSets; j++) { + initializationData.add(NalUnitUtil.parseChildNalUnit(data)); + } + int numPictureParameterSets = data.readUnsignedByte(); + for (int j = 0; j < numPictureParameterSets; j++) { + initializationData.add(NalUnitUtil.parseChildNalUnit(data)); + } + + int width = Format.NO_VALUE; + int height = Format.NO_VALUE; + float pixelWidthAspectRatio = 1; + if (numSequenceParameterSets > 0) { + byte[] sps = initializationData.get(0); + SpsData spsData = NalUnitUtil.parseSpsNalUnit(initializationData.get(0), + nalUnitLengthFieldLength, sps.length); + width = spsData.width; + height = spsData.height; + pixelWidthAspectRatio = spsData.pixelWidthAspectRatio; + } + return new AvcConfig(initializationData, nalUnitLengthFieldLength, width, height, + pixelWidthAspectRatio); + } catch (ArrayIndexOutOfBoundsException e) { + throw new ParserException("Error parsing AVC config", e); + } + } + + private AvcConfig(List initializationData, int nalUnitLengthFieldLength, + int width, int height, float pixelWidthAspectRatio) { + this.initializationData = initializationData; + this.nalUnitLengthFieldLength = nalUnitLengthFieldLength; + this.width = width; + this.height = height; + this.pixelWidthAspectRatio = pixelWidthAspectRatio; + } + +} diff --git a/library/src/main/java/com/google/android/exoplayer2/video/HevcConfig.java b/library/src/main/java/com/google/android/exoplayer2/video/HevcConfig.java new file mode 100644 index 0000000000..0982589866 --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer2/video/HevcConfig.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.video; + +import com.google.android.exoplayer2.ParserException; +import com.google.android.exoplayer2.util.NalUnitUtil; +import com.google.android.exoplayer2.util.ParsableByteArray; +import java.util.Collections; +import java.util.List; + +/** + * HEVC configuration data. + */ +public final class HevcConfig { + + public final List initializationData; + public final int nalUnitLengthFieldLength; + + /** + * Parses HEVC configuration data. + * + * @param data A {@link ParsableByteArray}, whose position is set to the start of the HEVC + * configuration data to parse. + * @return A parsed representation of the HEVC configuration data. + * @throws ParserException If an error occurred parsing the data. + */ + public static HevcConfig parse(ParsableByteArray data) throws ParserException { + try { + data.skipBytes(21); // Skip to the NAL unit length size field. + int lengthSizeMinusOne = data.readUnsignedByte() & 0x03; + + // Calculate the combined size of all VPS/SPS/PPS bitstreams. + int numberOfArrays = data.readUnsignedByte(); + int csdLength = 0; + int csdStartPosition = data.getPosition(); + for (int i = 0; i < numberOfArrays; i++) { + data.skipBytes(1); // completeness (1), nal_unit_type (7) + int numberOfNalUnits = data.readUnsignedShort(); + for (int j = 0; j < numberOfNalUnits; j++) { + int nalUnitLength = data.readUnsignedShort(); + csdLength += 4 + nalUnitLength; // Start code and NAL unit. + data.skipBytes(nalUnitLength); + } + } + + // Concatenate the codec-specific data into a single buffer. + data.setPosition(csdStartPosition); + byte[] buffer = new byte[csdLength]; + int bufferPosition = 0; + for (int i = 0; i < numberOfArrays; i++) { + data.skipBytes(1); // completeness (1), nal_unit_type (7) + int numberOfNalUnits = data.readUnsignedShort(); + for (int j = 0; j < numberOfNalUnits; j++) { + int nalUnitLength = data.readUnsignedShort(); + System.arraycopy(NalUnitUtil.NAL_START_CODE, 0, buffer, bufferPosition, + NalUnitUtil.NAL_START_CODE.length); + bufferPosition += NalUnitUtil.NAL_START_CODE.length; + System + .arraycopy(data.data, data.getPosition(), buffer, bufferPosition, nalUnitLength); + bufferPosition += nalUnitLength; + data.skipBytes(nalUnitLength); + } + } + + List initializationData = csdLength == 0 ? null : Collections.singletonList(buffer); + return new HevcConfig(initializationData, lengthSizeMinusOne + 1); + } catch (ArrayIndexOutOfBoundsException e) { + throw new ParserException("Error parsing HEVC config", e); + } + } + + private HevcConfig(List initializationData, int nalUnitLengthFieldLength) { + this.initializationData = initializationData; + this.nalUnitLengthFieldLength = nalUnitLengthFieldLength; + } + +}