diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/ParsableByteArray.java b/library/common/src/main/java/com/google/android/exoplayer2/util/ParsableByteArray.java index 79913d2aa9..6c989c5639 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/util/ParsableByteArray.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/util/ParsableByteArray.java @@ -565,4 +565,22 @@ public final class ParsableByteArray { position += length; return value; } + + /** + * The data from the end of the buffer is copied to the front + * The limit() because the bytesLeft() and position is zero + */ + public void compact() { + if (bytesLeft() == 0) { + limit = 0; + } else { + final ByteBuffer byteBuffer = ByteBuffer.wrap(data); + byteBuffer.limit(limit); + byteBuffer.position(position); + byteBuffer.compact(); + byteBuffer.flip(); + limit = byteBuffer.limit(); + } + position = 0; + } } diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/AvcChunkPeeker.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/AvcChunkHandler.java similarity index 86% rename from library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/AvcChunkPeeker.java rename to library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/AvcChunkHandler.java index b3ecf270d2..90bc6bb351 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/AvcChunkPeeker.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/AvcChunkHandler.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.extractor.avi; +import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.extractor.ExtractorInput; @@ -27,7 +28,7 @@ import java.io.IOException; * Corrects the time and PAR for H264 streams * AVC is very rare in AVI due to the rise of the mp4 container */ -public class AvcChunkPeeker extends NalChunkPeeker { +public class AvcChunkHandler extends NalChunkPeeker { private static final int NAL_TYPE_MASK = 0x1f; private static final int NAL_TYPE_IDR = 5; //I Frame private static final int NAL_TYPE_SEI = 6; @@ -35,22 +36,21 @@ public class AvcChunkPeeker extends NalChunkPeeker { private static final int NAL_TYPE_PPS = 8; private static final int NAL_TYPE_AUD = 9; - private final PicCountClock picCountClock; private final Format.Builder formatBuilder; - private final TrackOutput trackOutput; private float pixelWidthHeightRatio = 1f; private NalUnitUtil.SpsData spsData; - public AvcChunkPeeker(Format.Builder formatBuilder, TrackOutput trackOutput, LinearClock clock) { - super(16); + public AvcChunkHandler(int id, @NonNull TrackOutput trackOutput, + @NonNull ChunkClock clock, Format.Builder formatBuilder) { + super(id, trackOutput, new PicCountClock(clock.durationUs, clock.chunks), 16); this.formatBuilder = formatBuilder; - this.trackOutput = trackOutput; - picCountClock = new PicCountClock(clock.durationUs, clock.length); } + @NonNull + @Override public PicCountClock getClock() { - return picCountClock; + return (PicCountClock) clock; } @Override @@ -84,13 +84,13 @@ public class AvcChunkPeeker extends NalChunkPeeker { if (spsData.picOrderCountType == 0) { int picOrderCountLsb = in.readBits(spsData.picOrderCntLsbLength); //Log.d("Test", "FrameNum: " + frame + " cnt=" + picOrderCountLsb); - picCountClock.setPicCount(picOrderCountLsb); + getClock().setPicCount(picOrderCountLsb); return; } else if (spsData.picOrderCountType == 2) { - picCountClock.setPicCount(frameNum); + getClock().setPicCount(frameNum); return; } - picCountClock.setIndex(picCountClock.getIndex()); + clock.setIndex(clock.getIndex()); } @VisibleForTesting @@ -99,10 +99,10 @@ public class AvcChunkPeeker extends NalChunkPeeker { nalTypeOffset = seekNextNal(input, spsStart); spsData = NalUnitUtil.parseSpsNalUnitPayload(buffer, spsStart, pos); if (spsData.picOrderCountType == 0) { - picCountClock.setMaxPicCount(1 << spsData.picOrderCntLsbLength, 2); + getClock().setMaxPicCount(1 << spsData.picOrderCntLsbLength, 2); } else if (spsData.picOrderCountType == 2) { //Plus one because we double the frame number - picCountClock.setMaxPicCount(1 << spsData.frameNumLength, 1); + getClock().setMaxPicCount(1 << spsData.frameNumLength, 1); } if (spsData.pixelWidthHeightRatio != pixelWidthHeightRatio) { pixelWidthHeightRatio = spsData.pixelWidthHeightRatio; @@ -124,7 +124,7 @@ public class AvcChunkPeeker extends NalChunkPeeker { updatePicCountClock(nalTypeOffset); return; case NAL_TYPE_IDR: - picCountClock.syncIndexes(); + getClock().syncIndexes(); return; case NAL_TYPE_AUD: case NAL_TYPE_SEI: diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/AviExtractor.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/AviExtractor.java index b0c65bd8ac..3831210a15 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/AviExtractor.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/AviExtractor.java @@ -103,7 +103,7 @@ public class AviExtractor implements Extractor { @VisibleForTesting static final int STATE_READ_IDX1 = 2; @VisibleForTesting - static final int STATE_READ_SAMPLES = 3; + static final int STATE_READ_CHUNKS = 3; @VisibleForTesting static final int STATE_SEEK_START = 4; @@ -127,9 +127,9 @@ public class AviExtractor implements Extractor { private AviHeaderBox aviHeader; private long durationUs = C.TIME_UNSET; /** - * AviTracks by StreamId + * ChunkHandlers by StreamId */ - private AviTrack[] aviTracks = new AviTrack[0]; + private ChunkHandler[] chunkHandlers = new ChunkHandler[0]; //At the start of the movi tag private long moviOffset; private long moviEnd; @@ -137,7 +137,7 @@ public class AviExtractor implements Extractor { AviSeekMap aviSeekMap; //Set if a chunk is only partially read - private transient AviTrack chunkHandler; + private transient ChunkHandler chunkHandler; /** * @@ -218,7 +218,7 @@ public class AviExtractor implements Extractor { } @VisibleForTesting - AviTrack parseStream(final ListBox streamList, int streamId) { + ChunkHandler parseStream(final ListBox streamList, int streamId) { final StreamHeaderBox streamHeader = streamList.getChild(StreamHeaderBox.class); final StreamFormatBox streamFormat = streamList.getChild(StreamFormatBox.class); if (streamHeader == null) { @@ -243,7 +243,8 @@ public class AviExtractor implements Extractor { if (streamName != null) { builder.setLabel(streamName.getName()); } - final AviTrack aviTrack; + final ChunkClock clock = new ChunkClock(durationUs, length); + final ChunkHandler chunkHandler; if (streamHeader.isVideo()) { final VideoFormat videoFormat = streamFormat.getVideoFormat(); final String mimeType = videoFormat.getMimeType(); @@ -257,17 +258,12 @@ public class AviExtractor implements Extractor { builder.setFrameRate(streamHeader.getFrameRate()); builder.setSampleMimeType(mimeType); - final LinearClock clock = new LinearClock(durationUs, length); - aviTrack = new AviTrack(streamId, AviTrack.CHUNK_TYPE_VIDEO, clock, trackOutput); - if (MimeTypes.VIDEO_H264.equals(mimeType)) { - final AvcChunkPeeker avcChunkPeeker = new AvcChunkPeeker(builder, trackOutput, clock); - aviTrack.setClock(avcChunkPeeker.getClock()); - aviTrack.setChunkPeeker(avcChunkPeeker); + chunkHandler = new AvcChunkHandler(streamId, trackOutput, clock, builder); + } else if (MimeTypes.VIDEO_MP4V.equals(mimeType)) { + chunkHandler = new Mp4vChunkHandler(streamId, trackOutput, clock, builder); } else { - if (MimeTypes.VIDEO_MP4V.equals(mimeType)) { - aviTrack.setChunkPeeker(new Mp4vChunkPeeker(builder, trackOutput)); - } + chunkHandler = new ChunkHandler(streamId, ChunkHandler.TYPE_VIDEO, trackOutput, clock); } trackOutput.format(builder.build()); this.durationUs = durationUs; @@ -294,13 +290,18 @@ public class AviExtractor implements Extractor { builder.setInitializationData(Collections.singletonList(audioFormat.getCodecData())); } trackOutput.format(builder.build()); - aviTrack = new AviTrack(streamId, AviTrack.CHUNK_TYPE_AUDIO, - new LinearClock(durationUs, length), trackOutput); - aviTrack.setKeyFrames(AviTrack.ALL_KEY_FRAMES); + if (MimeTypes.AUDIO_MPEG.equals(mimeType)) { + chunkHandler = new Mp3ChunkHandler(streamId, trackOutput, clock, + audioFormat.getSamplesPerSecond()); + } else { + chunkHandler = new ChunkHandler(streamId, ChunkHandler.TYPE_AUDIO, + trackOutput, clock); + } + chunkHandler.setKeyFrames(ChunkHandler.ALL_KEY_FRAMES); }else { - aviTrack = null; + chunkHandler = null; } - return aviTrack; + return chunkHandler; } private int readTracks(ExtractorInput input) throws IOException { @@ -312,7 +313,7 @@ public class AviExtractor implements Extractor { if (aviHeader == null) { throw new IOException("AviHeader not found"); } - aviTracks = new AviTrack[aviHeader.getStreams()]; + chunkHandlers = new ChunkHandler[aviHeader.getStreams()]; //This is usually wrong, so it will be overwritten by video if present durationUs = aviHeader.getTotalFrames() * (long)aviHeader.getMicroSecPerFrame(); @@ -320,7 +321,7 @@ public class AviExtractor implements Extractor { for (Box box : headerList.getChildren()) { if (box instanceof ListBox && ((ListBox) box).getListType() == ListBox.TYPE_STRL) { final ListBox streamList = (ListBox) box; - aviTracks[streamId] = parseStream(streamList, streamId); + chunkHandlers[streamId] = parseStream(streamList, streamId); streamId++; } } @@ -356,32 +357,32 @@ public class AviExtractor implements Extractor { } @VisibleForTesting - AviTrack getVideoTrack() { - for (@Nullable AviTrack aviTrack : aviTracks) { - if (aviTrack != null && aviTrack.isVideo()) { - return aviTrack; + ChunkHandler getVideoTrack() { + for (@Nullable ChunkHandler chunkHandler : chunkHandlers) { + if (chunkHandler != null && chunkHandler.isVideo()) { + return chunkHandler; } } return null; } void fixTimings(final int[] keyFrameCounts, final long videoDuration) { - for (final AviTrack aviTrack : aviTracks) { - if (aviTrack != null) { - if (aviTrack.isAudio()) { - final long durationUs = aviTrack.getClock().durationUs; - i("Audio #" + aviTrack.getId() + " chunks: " + aviTrack.chunks + " us=" + durationUs + - " size=" + aviTrack.size); - final LinearClock linearClock = aviTrack.getClock(); + for (final ChunkHandler chunkHandler : chunkHandlers) { + if (chunkHandler != null) { + if (chunkHandler.isAudio()) { + final long durationUs = chunkHandler.getClock().durationUs; + i("Audio #" + chunkHandler.getId() + " chunks: " + chunkHandler.chunks + " us=" + durationUs + + " size=" + chunkHandler.size); + final ChunkClock linearClock = chunkHandler.getClock(); //If the audio track duration is off from the video by >5 % recalc using video if ((durationUs - videoDuration) / (float)videoDuration > .05f) { - w("Audio #" + aviTrack.getId() + " duration is off using videoDuration"); + w("Audio #" + chunkHandler.getId() + " duration is off using videoDuration"); linearClock.setDuration(videoDuration); } - linearClock.setLength(aviTrack.chunks); - if (aviTrack.chunks != keyFrameCounts[aviTrack.getId()]) { - w("Audio is not all key frames chunks=" + aviTrack.chunks + " keyFrames=" + - keyFrameCounts[aviTrack.getId()]); + linearClock.setChunks(chunkHandler.chunks); + if (chunkHandler.chunks != keyFrameCounts[chunkHandler.getId()]) { + w("Audio is not all key frames chunks=" + chunkHandler.chunks + " keyFrames=" + + keyFrameCounts[chunkHandler.getId()]); } } } @@ -392,7 +393,7 @@ public class AviExtractor implements Extractor { * Reads the index and sets the keyFrames and creates the SeekMap */ void readIdx1(ExtractorInput input, int remaining) throws IOException { - final AviTrack videoTrack = getVideoTrack(); + final ChunkHandler videoTrack = getVideoTrack(); if (videoTrack == null) { output.seekMap(new SeekMap.Unseekable(getDuration())); w("No video track found"); @@ -412,8 +413,8 @@ public class AviExtractor implements Extractor { //These are ints/2 final UnboundedIntArray keyFrameOffsetsDiv2 = new UnboundedIntArray(); - final int[] keyFrameCounts = new int[aviTracks.length]; - final UnboundedIntArray[] seekIndexes = new UnboundedIntArray[aviTracks.length]; + final int[] keyFrameCounts = new int[chunkHandlers.length]; + final UnboundedIntArray[] seekIndexes = new UnboundedIntArray[chunkHandlers.length]; for (int i=0;i= chunksPerKeyFrame) { + if (indexSize == 0 || chunkHandler.chunks - seekIndexes[videoId].get(indexSize - 1) >= chunksPerKeyFrame) { keyFrameOffsetsDiv2.add(offset / 2); - for (AviTrack seekTrack : aviTracks) { + for (ChunkHandler seekTrack : chunkHandlers) { if (seekTrack != null) { seekIndexes[seekTrack.getId()].add(seekTrack.chunks); } } } } - keyFrameCounts[aviTrack.getId()]++; + keyFrameCounts[chunkHandler.getId()]++; } - aviTrack.chunks++; - aviTrack.size+=size; + chunkHandler.chunks++; + chunkHandler.size+=size; } indexByteBuffer.compact(); } if (videoTrack.chunks == keyFrameCounts[videoTrack.getId()]) { - videoTrack.setKeyFrames(AviTrack.ALL_KEY_FRAMES); + videoTrack.setKeyFrames(ChunkHandler.ALL_KEY_FRAMES); } else { videoTrack.setKeyFrames(seekIndexes[videoId].getArray()); } @@ -485,16 +486,16 @@ public class AviExtractor implements Extractor { @Nullable @VisibleForTesting - AviTrack getAviTrack(int chunkId) { - for (AviTrack aviTrack : aviTracks) { - if (aviTrack != null && aviTrack.handlesChunkId(chunkId)) { - return aviTrack; + ChunkHandler getChunkHandler(int chunkId) { + for (ChunkHandler chunkHandler : chunkHandlers) { + if (chunkHandler != null && chunkHandler.handlesChunkId(chunkId)) { + return chunkHandler; } } return null; } - int readSamples(@NonNull ExtractorInput input) throws IOException { + int readChunks(@NonNull ExtractorInput input) throws IOException { if (chunkHandler != null) { if (chunkHandler.resume(input)) { chunkHandler = null; @@ -527,21 +528,21 @@ public class AviExtractor implements Extractor { alignInput(input); return RESULT_CONTINUE; } - final AviTrack aviTrack = getAviTrack(chunkId); - if (aviTrack == null) { + final ChunkHandler chunkHandler = getChunkHandler(chunkId); + if (chunkHandler == null) { input.skipFully(size); alignInput(input); w("Unknown tag=" + toString(chunkId) + " pos=" + (input.getPosition() - 8) + " size=" + size + " moviEnd=" + moviEnd); return RESULT_CONTINUE; } - if (aviTrack.newChunk(chunkId, size, input)) { + if (chunkHandler.newChunk(chunkId, size, input)) { alignInput(input); } else { - chunkHandler = aviTrack; + this.chunkHandler = chunkHandler; } } - if (input.getPosition() == input.getLength()) { + if (input.getPosition() >= moviEnd) { return C.RESULT_END_OF_INPUT; } return RESULT_CONTINUE; @@ -550,10 +551,10 @@ public class AviExtractor implements Extractor { @Override public int read(@NonNull ExtractorInput input, @NonNull PositionHolder seekPosition) throws IOException { switch (state) { - case STATE_READ_SAMPLES: - return readSamples(input); + case STATE_READ_CHUNKS: + return readChunks(input); case STATE_SEEK_START: - state = STATE_READ_SAMPLES; + state = STATE_READ_CHUNKS; seekPosition.position = moviOffset + 4; return RESULT_SEEK; case STATE_READ_TRACKS: @@ -573,7 +574,7 @@ public class AviExtractor implements Extractor { output.seekMap(new SeekMap.Unseekable(getDuration())); } seekPosition.position = moviOffset + 4; - state = STATE_READ_SAMPLES; + state = STATE_READ_CHUNKS; return RESULT_SEEK; } } @@ -591,15 +592,15 @@ public class AviExtractor implements Extractor { } } else { if (aviSeekMap != null) { - aviSeekMap.setFrames(position, timeUs, aviTracks); + aviSeekMap.setFrames(position, timeUs, chunkHandlers); } } } void resetClocks() { - for (@Nullable AviTrack aviTrack : aviTracks) { - if (aviTrack != null) { - aviTrack.getClock().setIndex(0); + for (@Nullable ChunkHandler chunkHandler : chunkHandlers) { + if (chunkHandler != null) { + chunkHandler.getClock().setIndex(0); } } } @@ -610,8 +611,8 @@ public class AviExtractor implements Extractor { } @VisibleForTesting - void setAviTracks(AviTrack[] aviTracks) { - this.aviTracks = aviTracks; + void setChunkHandlers(ChunkHandler[] chunkHandlers) { + this.chunkHandlers = chunkHandlers; } @VisibleForTesting(otherwise = VisibleForTesting.NONE) @@ -626,13 +627,13 @@ public class AviExtractor implements Extractor { } @VisibleForTesting(otherwise = VisibleForTesting.NONE) - AviTrack getChunkHandler() { + ChunkHandler getChunkHandler() { return chunkHandler; } @VisibleForTesting(otherwise = VisibleForTesting.NONE) - void setChunkHandler(final AviTrack aviTrack) { - chunkHandler = aviTrack; + void setChunkHandler(final ChunkHandler chunkHandler) { + this.chunkHandler = chunkHandler; } @VisibleForTesting(otherwise = VisibleForTesting.NONE) diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/AviSeekMap.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/AviSeekMap.java index fe662ca615..edd8f87683 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/AviSeekMap.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/AviSeekMap.java @@ -99,17 +99,18 @@ public class AviSeekMap implements SeekMap { //Log.d(AviExtractor.TAG, "SeekPoint: us=" + outUs + " pos=" + position); } - public void setFrames(final long position, final long timeUs, final AviTrack[] aviTracks) { + public void setFrames(final long position, final long timeUs, final ChunkHandler[] chunkHandlers) { final int index = Arrays.binarySearch(keyFrameOffsetsDiv2, (int)((position - seekOffset) / 2)); if (index < 0) { throw new IllegalArgumentException("Position: " + position); } - for (int i=0;i