Commit 3a9f1f9a by Dustin

Improved comments, improved naming consistency.

parent 14c842e5
Showing with 147 additions and 124 deletions
...@@ -29,7 +29,7 @@ import java.io.IOException; ...@@ -29,7 +29,7 @@ import java.io.IOException;
* Corrects the time and PAR for H264 streams * Corrects the time and PAR for H264 streams
* AVC is very rare in AVI due to the rise of the mp4 container * AVC is very rare in AVI due to the rise of the mp4 container
*/ */
public class AvcChunkHandler extends NalChunkPeeker { public class AvcChunkHandler extends NalChunkHandler {
private static final int NAL_TYPE_MASK = 0x1f; private static final int NAL_TYPE_MASK = 0x1f;
private static final int NAL_TYPE_IDR = 5; //I Frame private static final int NAL_TYPE_IDR = 5; //I Frame
private static final int NAL_TYPE_SEI = 6; private static final int NAL_TYPE_SEI = 6;
...@@ -63,7 +63,7 @@ public class AvcChunkHandler extends NalChunkPeeker { ...@@ -63,7 +63,7 @@ public class AvcChunkHandler extends NalChunkPeeker {
if (clock instanceof PicCountClock) { if (clock instanceof PicCountClock) {
return false; return false;
} else { } else {
//If the clock is regular clock, skip "normal" frames //If the clock is ChunkClock, skip "normal" frames
return nalType >= 0 && nalType <= NAL_TYPE_IDR; return nalType >= 0 && nalType <= NAL_TYPE_IDR;
} }
} }
...@@ -107,10 +107,12 @@ public class AvcChunkHandler extends NalChunkPeeker { ...@@ -107,10 +107,12 @@ public class AvcChunkHandler extends NalChunkPeeker {
final int spsStart = nalTypeOffset + 1; final int spsStart = nalTypeOffset + 1;
nalTypeOffset = seekNextNal(input, spsStart); nalTypeOffset = seekNextNal(input, spsStart);
spsData = NalUnitUtil.parseSpsNalUnitPayload(buffer, spsStart, pos); spsData = NalUnitUtil.parseSpsNalUnitPayload(buffer, spsStart, pos);
//If we have B Frames, upgrade to PicCountClock //If we can have B Frames, upgrade to PicCountClock
final PicCountClock picCountClock; final PicCountClock picCountClock;
if (spsData.maxNumRefFrames > 1 && !(clock instanceof PicCountClock)) { if (spsData.maxNumRefFrames > 1 && !(clock instanceof PicCountClock)) {
clock = picCountClock = new PicCountClock(clock.durationUs, clock.chunks); picCountClock = new PicCountClock(clock.durationUs, clock.chunks);
picCountClock.setIndex(clock.getIndex());
clock = picCountClock;
} else { } else {
picCountClock = getPicCountClock(); picCountClock = getPicCountClock();
} }
......
...@@ -291,7 +291,7 @@ public class AviExtractor implements Extractor { ...@@ -291,7 +291,7 @@ public class AviExtractor implements Extractor {
} }
trackOutput.format(builder.build()); trackOutput.format(builder.build());
if (MimeTypes.AUDIO_MPEG.equals(mimeType)) { if (MimeTypes.AUDIO_MPEG.equals(mimeType)) {
chunkHandler = new Mp3ChunkHandler(streamId, trackOutput, clock, chunkHandler = new MpegAudioChunkHandler(streamId, trackOutput, clock,
audioFormat.getSamplesPerSecond()); audioFormat.getSamplesPerSecond());
} else { } else {
chunkHandler = new ChunkHandler(streamId, ChunkHandler.TYPE_AUDIO, chunkHandler = new ChunkHandler(streamId, ChunkHandler.TYPE_AUDIO,
...@@ -367,7 +367,7 @@ public class AviExtractor implements Extractor { ...@@ -367,7 +367,7 @@ public class AviExtractor implements Extractor {
} }
void fixTimings(final int[] keyFrameCounts, final long videoDuration) { void fixTimings(final int[] keyFrameCounts, final long videoDuration) {
for (final ChunkHandler chunkHandler : chunkHandlers) { for (@Nullable final ChunkHandler chunkHandler : chunkHandlers) {
if (chunkHandler != null) { if (chunkHandler != null) {
if (chunkHandler.isAudio()) { if (chunkHandler.isAudio()) {
final long durationUs = chunkHandler.getClock().durationUs; final long durationUs = chunkHandler.getClock().durationUs;
...@@ -452,7 +452,7 @@ public class AviExtractor implements Extractor { ...@@ -452,7 +452,7 @@ public class AviExtractor implements Extractor {
int indexSize = seekIndexes[videoId].getSize(); int indexSize = seekIndexes[videoId].getSize();
if (indexSize == 0 || chunkHandler.chunks - seekIndexes[videoId].get(indexSize - 1) >= chunksPerKeyFrame) { if (indexSize == 0 || chunkHandler.chunks - seekIndexes[videoId].get(indexSize - 1) >= chunksPerKeyFrame) {
keyFrameOffsetsDiv2.add(offset / 2); keyFrameOffsetsDiv2.add(offset / 2);
for (ChunkHandler seekTrack : chunkHandlers) { for (@Nullable ChunkHandler seekTrack : chunkHandlers) {
if (seekTrack != null) { if (seekTrack != null) {
seekIndexes[seekTrack.getId()].add(seekTrack.chunks); seekIndexes[seekTrack.getId()].add(seekTrack.chunks);
} }
...@@ -487,7 +487,7 @@ public class AviExtractor implements Extractor { ...@@ -487,7 +487,7 @@ public class AviExtractor implements Extractor {
@Nullable @Nullable
@VisibleForTesting @VisibleForTesting
ChunkHandler getChunkHandler(int chunkId) { ChunkHandler getChunkHandler(int chunkId) {
for (ChunkHandler chunkHandler : chunkHandlers) { for (@Nullable ChunkHandler chunkHandler : chunkHandlers) {
if (chunkHandler != null && chunkHandler.handlesChunkId(chunkId)) { if (chunkHandler != null && chunkHandler.handlesChunkId(chunkId)) {
return chunkHandler; return chunkHandler;
} }
...@@ -536,7 +536,7 @@ public class AviExtractor implements Extractor { ...@@ -536,7 +536,7 @@ public class AviExtractor implements Extractor {
+ " size=" + size + " moviEnd=" + moviEnd); + " size=" + size + " moviEnd=" + moviEnd);
return RESULT_CONTINUE; return RESULT_CONTINUE;
} }
if (chunkHandler.newChunk(chunkId, size, input)) { if (chunkHandler.newChunk(size, input)) {
alignInput(input); alignInput(input);
} else { } else {
this.chunkHandler = chunkHandler; this.chunkHandler = chunkHandler;
...@@ -587,20 +587,20 @@ public class AviExtractor implements Extractor { ...@@ -587,20 +587,20 @@ public class AviExtractor implements Extractor {
chunkHandler = null; chunkHandler = null;
if (position <= 0) { if (position <= 0) {
if (moviOffset != 0) { if (moviOffset != 0) {
resetClocks(); setIndexes(new int[chunkHandlers.length]);
state = STATE_SEEK_START; state = STATE_SEEK_START;
} }
} else { } else {
if (aviSeekMap != null) { if (aviSeekMap != null) {
aviSeekMap.setFrames(position, timeUs, chunkHandlers); setIndexes(aviSeekMap.getIndexes(position));
} }
} }
} }
void resetClocks() { private void setIndexes(@NonNull int[] indexes) {
for (@Nullable ChunkHandler chunkHandler : chunkHandlers) { for (@Nullable ChunkHandler chunkHandler : chunkHandlers) {
if (chunkHandler != null) { if (chunkHandler != null) {
chunkHandler.getClock().setIndex(0); chunkHandler.setIndex(indexes[chunkHandler.getId()]);
} }
} }
} }
......
...@@ -99,18 +99,23 @@ public class AviSeekMap implements SeekMap { ...@@ -99,18 +99,23 @@ public class AviSeekMap implements SeekMap {
//Log.d(AviExtractor.TAG, "SeekPoint: us=" + outUs + " pos=" + position); //Log.d(AviExtractor.TAG, "SeekPoint: us=" + outUs + " pos=" + position);
} }
public void setFrames(final long position, final long timeUs, final ChunkHandler[] chunkHandlers) { /**
* Get the ChunkClock indexes by stream id
* @param position seek position in the file
*/
@NonNull
public int[] getIndexes(final long position) {
final int index = Arrays.binarySearch(keyFrameOffsetsDiv2, (int)((position - seekOffset) / 2)); final int index = Arrays.binarySearch(keyFrameOffsetsDiv2, (int)((position - seekOffset) / 2));
if (index < 0) { if (index < 0) {
throw new IllegalArgumentException("Position: " + position); throw new IllegalArgumentException("Position: " + position);
} }
for (int i=0;i<chunkHandlers.length;i++) { final int[] indexes = new int[seekIndexes.length];
final ChunkHandler chunkHandler = chunkHandlers[i]; for (int i=0;i<indexes.length;i++) {
if (chunkHandler != null) { if (seekIndexes[i].length > index) {
// Log.d(AviExtractor.TAG, "Frame: " + (chunkHandler.isVideo()? 'V' : 'A') + " us=" + clock.getUs() + " frame=" + clock.getIndex() + " key=" + chunkHandler.isKeyFrame()); indexes[i] = seekIndexes[i][index];
chunkHandler.setIndex(seekIndexes[i][index]);
} }
} }
return indexes;
} }
} }
...@@ -25,11 +25,16 @@ import java.io.IOException; ...@@ -25,11 +25,16 @@ import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
/** /**
* Collection of info about a track. * Handles chunk data from a given stream.
* This acts a bridge between AVI and ExoPlayer structures * This acts a bridge between AVI and ExoPlayer
*/ */
public class ChunkHandler { public class ChunkHandler {
/**
* Constant meaning all frames are considered key frames
*/
public static final int[] ALL_KEY_FRAMES = new int[0]; public static final int[] ALL_KEY_FRAMES = new int[0];
public static int TYPE_VIDEO = ('d' << 16) | ('c' << 24); public static int TYPE_VIDEO = ('d' << 16) | ('c' << 24);
public static int TYPE_AUDIO = ('w' << 16) | ('b' << 24); public static int TYPE_AUDIO = ('w' << 16) | ('b' << 24);
...@@ -39,10 +44,24 @@ public class ChunkHandler { ...@@ -39,10 +44,24 @@ public class ChunkHandler {
@NonNull @NonNull
final TrackOutput trackOutput; final TrackOutput trackOutput;
/**
* The chunk id as it appears in the index and the movi
*/
final int chunkId; final int chunkId;
/**
* Secondary chunk id. Bad muxers sometimes use uncompressed for key frames
*/
final int chunkIdAlt; final int chunkIdAlt;
/**
* Number of chunks as calculated by the index
*/
int chunks; int chunks;
/**
* Size total size of the stream in bytes calculated by the index
*/
int size; int size;
/** /**
...@@ -50,9 +69,18 @@ public class ChunkHandler { ...@@ -50,9 +69,18 @@ public class ChunkHandler {
*/ */
int[] keyFrames = new int[0]; int[] keyFrames = new int[0];
/**
* Size of the current chunk in bytes
*/
transient int chunkSize; transient int chunkSize;
/**
* Bytes remaining in the chunk to be processed
*/
transient int chunkRemaining; transient int chunkRemaining;
/**
* Get stream id in ASCII
*/
@VisibleForTesting @VisibleForTesting
static int getChunkIdLower(int id) { static int getChunkIdLower(int id) {
int tens = id / 10; int tens = id / 10;
...@@ -71,6 +99,10 @@ public class ChunkHandler { ...@@ -71,6 +99,10 @@ public class ChunkHandler {
} }
} }
/**
*
* @return true if this can handle the chunkId
*/
public boolean handlesChunkId(int chunkId) { public boolean handlesChunkId(int chunkId) {
return this.chunkId == chunkId || chunkIdAlt == chunkId; return this.chunkId == chunkId || chunkIdAlt == chunkId;
} }
...@@ -80,13 +112,9 @@ public class ChunkHandler { ...@@ -80,13 +112,9 @@ public class ChunkHandler {
return clock; return clock;
} }
public void setClock(@NonNull ChunkClock clock) {
this.clock = clock;
}
/** /**
* * Sets the list of key frames
* @param keyFrames null means all key frames * @param keyFrames list of frame indexes or {@link #ALL_KEY_FRAMES}
*/ */
void setKeyFrames(@NonNull final int[] keyFrames) { void setKeyFrames(@NonNull final int[] keyFrames) {
this.keyFrames = keyFrames; this.keyFrames = keyFrames;
...@@ -104,23 +132,27 @@ public class ChunkHandler { ...@@ -104,23 +132,27 @@ public class ChunkHandler {
return (chunkId & TYPE_AUDIO) == TYPE_AUDIO; return (chunkId & TYPE_AUDIO) == TYPE_AUDIO;
} }
public boolean newChunk(int tag, int size, ExtractorInput input) throws IOException { /**
final int remaining = size - trackOutput.sampleData(input, size, false); * Process a new chunk
if (remaining == 0) { * @param size total size of the chunk
* @return True if the chunk has been completely processed. False implies {@link #resume}
* will be called
*/
public boolean newChunk(int size, @NonNull ExtractorInput input) throws IOException {
final int sampled = trackOutput.sampleData(input, size, false);
if (sampled == size) {
done(size); done(size);
return true; return true;
} else { } else {
chunkSize = size; chunkSize = size;
chunkRemaining = remaining; chunkRemaining = size - sampled;
return false; return false;
} }
} }
/** /**
* Resume a partial read of a chunk * Resume a partial read of a chunk
* @param input * May be called multiple times
* @return
* @throws IOException
*/ */
boolean resume(ExtractorInput input) throws IOException { boolean resume(ExtractorInput input) throws IOException {
chunkRemaining -= trackOutput.sampleData(input, chunkRemaining, false); chunkRemaining -= trackOutput.sampleData(input, chunkRemaining, false);
...@@ -133,25 +165,29 @@ public class ChunkHandler { ...@@ -133,25 +165,29 @@ public class ChunkHandler {
} }
/** /**
* Done reading a chunk * Done reading a chunk. Send the timing info and advance the clock
* @param size * @param size the amount of data passed to the trackOutput
*/ */
void done(final int size) { void done(final int size) {
if (size > 0) { if (size > 0) {
trackOutput.sampleMetadata( trackOutput.sampleMetadata(
clock.getUs(), (isKeyFrame() ? C.BUFFER_FLAG_KEY_FRAME : 0), size, 0, null); clock.getUs(), (isKeyFrame() ? C.BUFFER_FLAG_KEY_FRAME : 0), size, 0, null);
} }
final ChunkClock clock = getClock();
//Log.d(AviExtractor.TAG, "Frame: " + (isVideo()? 'V' : 'A') + " us=" + clock.getUs() + " size=" + size + " frame=" + clock.getIndex() + " key=" + isKeyFrame()); //Log.d(AviExtractor.TAG, "Frame: " + (isVideo()? 'V' : 'A') + " us=" + clock.getUs() + " size=" + size + " frame=" + clock.getIndex() + " key=" + isKeyFrame());
clock.advance(); clock.advance();
} }
/**
* Gets the streamId.
* @return The unique stream id for this file
*/
public int getId() { public int getId() {
return ((chunkId >> 8) & 0xf) + ( chunkId & 0xf) * 10; return ((chunkId >> 8) & 0xf) + (chunkId & 0xf) * 10;
} }
/** /**
* A seek occurred * A seek occurred
* @param index of the chunk
*/ */
public void setIndex(int index) { public void setIndex(int index) {
getClock().setIndex(index); getClock().setIndex(index);
......
...@@ -26,7 +26,7 @@ import java.io.IOException; ...@@ -26,7 +26,7 @@ import java.io.IOException;
/** /**
* Peeks an MP4V stream looking for pixelWidthHeightRatio data * Peeks an MP4V stream looking for pixelWidthHeightRatio data
*/ */
public class Mp4vChunkHandler extends NalChunkPeeker { public class Mp4vChunkHandler extends NalChunkHandler {
@VisibleForTesting @VisibleForTesting
static final byte SEQUENCE_START_CODE = (byte)0xb0; static final byte SEQUENCE_START_CODE = (byte)0xb0;
@VisibleForTesting @VisibleForTesting
......
...@@ -16,37 +16,45 @@ ...@@ -16,37 +16,45 @@
package com.google.android.exoplayer2.extractor.avi; package com.google.android.exoplayer2.extractor.avi;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.audio.MpegAudioUtil; import com.google.android.exoplayer2.audio.MpegAudioUtil;
import com.google.android.exoplayer2.extractor.ExtractorInput; import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
import java.io.IOException; import java.io.IOException;
public class Mp3ChunkHandler extends ChunkHandler { /**
* Resolves several issues with Mpeg Audio
* 1. That muxers don't always mux MPEG audio on the frame boundary
* 2. That some codecs can't handle multiple or partial frames (Pixels)
*/
public class MpegAudioChunkHandler extends ChunkHandler {
private final MpegAudioUtil.Header header = new MpegAudioUtil.Header(); private final MpegAudioUtil.Header header = new MpegAudioUtil.Header();
private final ParsableByteArray scratch = new ParsableByteArray(0); private final ParsableByteArray scratch = new ParsableByteArray(8);
private final int fps; private final int samplesPerSecond;
//Bytes remaining in the Mpeg Audio frame
private int frameRemaining; private int frameRemaining;
private long us = 0L; private long timeUs = 0L;
Mp3ChunkHandler(int id, @NonNull TrackOutput trackOutput, @NonNull ChunkClock clock, int fps) { MpegAudioChunkHandler(int id, @NonNull TrackOutput trackOutput, @NonNull ChunkClock clock,
int samplesPerSecond) {
super(id, TYPE_AUDIO, trackOutput, clock); super(id, TYPE_AUDIO, trackOutput, clock);
this.fps = fps; this.samplesPerSecond = samplesPerSecond;
} }
@Override @Override
public boolean newChunk(int tag, int size, ExtractorInput input) throws IOException { public boolean newChunk(int size, @NonNull ExtractorInput input) throws IOException {
if (size == 0) { if (size == 0) {
//Empty frame, advance the clock and sync
clock.advance(); clock.advance();
syncUs(); syncTime();
//Log.d(AviExtractor.TAG, "Blank Frame: us=" + us);
return true; return true;
} }
chunkRemaining = size; chunkRemaining = size;
if (process(input)) { if (process(input)) {
//If we scratch is the entire buffer, we didn't find a MP3 header, so just dump the chunk // Fail Over: If the scratch is the entire chunk, we didn't find a MP3 header.
// Dump the chunk as is and hope the decoder can handle it.
if (scratch.limit() == size) { if (scratch.limit() == size) {
scratch.setPosition(0); scratch.setPosition(0);
trackOutput.sampleData(scratch, size); trackOutput.sampleData(scratch, size);
...@@ -59,7 +67,7 @@ public class Mp3ChunkHandler extends ChunkHandler { ...@@ -59,7 +67,7 @@ public class Mp3ChunkHandler extends ChunkHandler {
} }
@Override @Override
boolean resume(ExtractorInput input) throws IOException { boolean resume(@NonNull ExtractorInput input) throws IOException {
if (process(input)) { if (process(input)) {
clock.advance(); clock.advance();
return true; return true;
...@@ -67,6 +75,11 @@ public class Mp3ChunkHandler extends ChunkHandler { ...@@ -67,6 +75,11 @@ public class Mp3ChunkHandler extends ChunkHandler {
return false; return false;
} }
/**
* Read from input to scratch
* @param bytes to attempt to read
* @return {@link C#RESULT_END_OF_INPUT} or number of bytes read.
*/
int readScratch(ExtractorInput input, int bytes) throws IOException { int readScratch(ExtractorInput input, int bytes) throws IOException {
final int toRead = Math.min(bytes, chunkRemaining); final int toRead = Math.min(bytes, chunkRemaining);
final int read = input.read(scratch.getData(), scratch.limit(), toRead); final int read = input.read(scratch.getData(), scratch.limit(), toRead);
...@@ -78,7 +91,12 @@ public class Mp3ChunkHandler extends ChunkHandler { ...@@ -78,7 +91,12 @@ public class Mp3ChunkHandler extends ChunkHandler {
return read; return read;
} }
private boolean findFrame(ExtractorInput input) throws IOException { /**
* Attempt to find a frame header in the input
* @return true if a frame header was found
*/
@VisibleForTesting
boolean findFrame(ExtractorInput input) throws IOException {
scratch.reset(0); scratch.reset(0);
scratch.ensureCapacity(scratch.limit() + chunkRemaining); scratch.ensureCapacity(scratch.limit() + chunkRemaining);
int toRead = 4; int toRead = 4;
...@@ -96,8 +114,14 @@ public class Mp3ChunkHandler extends ChunkHandler { ...@@ -96,8 +114,14 @@ public class Mp3ChunkHandler extends ChunkHandler {
return false; return false;
} }
private boolean process(ExtractorInput input) throws IOException { /**
* Process the chunk by breaking it in Mpeg audio frames
* @return true if the chunk has been completely processed
*/
@VisibleForTesting
boolean process(ExtractorInput input) throws IOException {
if (frameRemaining == 0) { if (frameRemaining == 0) {
//Find the next frame
if (findFrame(input)) { if (findFrame(input)) {
final int scratchBytes = scratch.bytesLeft(); final int scratchBytes = scratch.bytesLeft();
trackOutput.sampleData(scratch, scratchBytes); trackOutput.sampleData(scratch, scratchBytes);
...@@ -107,14 +131,11 @@ public class Mp3ChunkHandler extends ChunkHandler { ...@@ -107,14 +131,11 @@ public class Mp3ChunkHandler extends ChunkHandler {
} }
} }
final int bytes = trackOutput.sampleData(input, Math.min(frameRemaining, chunkRemaining), false); final int bytes = trackOutput.sampleData(input, Math.min(frameRemaining, chunkRemaining), false);
if (bytes == C.RESULT_END_OF_INPUT) {
return true;
}
frameRemaining -= bytes; frameRemaining -= bytes;
if (frameRemaining == 0) { if (frameRemaining == 0) {
trackOutput.sampleMetadata(us, C.BUFFER_FLAG_KEY_FRAME, header.frameSize, 0, null); trackOutput.sampleMetadata(timeUs, C.BUFFER_FLAG_KEY_FRAME, header.frameSize, 0, null);
//Log.d(AviExtractor.TAG, "MP3: us=" + us); //Log.d(AviExtractor.TAG, "MP3: us=" + us);
us += header.samplesPerFrame * C.MICROS_PER_SECOND / fps; timeUs += header.samplesPerFrame * C.MICROS_PER_SECOND / samplesPerSecond;
} }
chunkRemaining -= bytes; chunkRemaining -= bytes;
return chunkRemaining == 0; return chunkRemaining == 0;
...@@ -123,11 +144,11 @@ public class Mp3ChunkHandler extends ChunkHandler { ...@@ -123,11 +144,11 @@ public class Mp3ChunkHandler extends ChunkHandler {
@Override @Override
public void setIndex(int index) { public void setIndex(int index) {
super.setIndex(index); super.setIndex(index);
syncUs(); syncTime();
} }
private void syncUs() { private void syncTime() {
us = clock.getUs(); timeUs = clock.getUs();
frameRemaining = 0; frameRemaining = 0;
} }
} }
...@@ -25,7 +25,7 @@ import java.util.Arrays; ...@@ -25,7 +25,7 @@ import java.util.Arrays;
* Generic base class for NAL (0x00 0x00 0x01) chunk headers * Generic base class for NAL (0x00 0x00 0x01) chunk headers
* Theses are used by AVC and MP4V (XVID) * Theses are used by AVC and MP4V (XVID)
*/ */
public abstract class NalChunkPeeker extends ChunkHandler { public abstract class NalChunkHandler extends ChunkHandler {
private static final int SEEK_PEEK_SIZE = 256; private static final int SEEK_PEEK_SIZE = 256;
private final int peekSize; private final int peekSize;
...@@ -33,7 +33,7 @@ public abstract class NalChunkPeeker extends ChunkHandler { ...@@ -33,7 +33,7 @@ public abstract class NalChunkPeeker extends ChunkHandler {
transient byte[] buffer; transient byte[] buffer;
transient int pos; transient int pos;
NalChunkPeeker(int id, @NonNull TrackOutput trackOutput, NalChunkHandler(int id, @NonNull TrackOutput trackOutput,
@NonNull ChunkClock clock, int peakSize) { @NonNull ChunkClock clock, int peakSize) {
super(id, TYPE_VIDEO, trackOutput, clock); super(id, TYPE_VIDEO, trackOutput, clock);
if (peakSize < 5) { if (peakSize < 5) {
...@@ -113,9 +113,9 @@ public abstract class NalChunkPeeker extends ChunkHandler { ...@@ -113,9 +113,9 @@ public abstract class NalChunkPeeker extends ChunkHandler {
abstract boolean skip(byte nalType); abstract boolean skip(byte nalType);
public boolean newChunk(int tag, int size, ExtractorInput input) throws IOException { public boolean newChunk(int size, ExtractorInput input) throws IOException {
peek(input, size); peek(input, size);
return super.newChunk(tag, size, input); return super.newChunk(size, input);
} }
public void peek(ExtractorInput input, final int size) throws IOException { public void peek(ExtractorInput input, final int size) throws IOException {
......
...@@ -16,13 +16,6 @@ ...@@ -16,13 +16,6 @@
package com.google.android.exoplayer2.extractor.avi; package com.google.android.exoplayer2.extractor.avi;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.util.Log;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.nio.BufferOverflowException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
...@@ -39,7 +32,6 @@ public class ResidentBox extends Box { ...@@ -39,7 +32,6 @@ public class ResidentBox extends Box {
/** /**
* Returns shallow copy of this ByteBuffer with the position at 0 * Returns shallow copy of this ByteBuffer with the position at 0
* @return
*/ */
@NonNull @NonNull
public ByteBuffer getByteBuffer() { public ByteBuffer getByteBuffer() {
......
...@@ -18,7 +18,7 @@ package com.google.android.exoplayer2.extractor.avi; ...@@ -18,7 +18,7 @@ package com.google.android.exoplayer2.extractor.avi;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
/** /**
* Human readable stream name * Box containing a human readable stream name
*/ */
public class StreamNameBox extends ResidentBox { public class StreamNameBox extends ResidentBox {
public static final int STRN = 's' | ('t' << 8) | ('r' << 16) | ('n' << 24); public static final int STRN = 's' | ('t' << 8) | ('r' << 16) | ('n' << 24);
......
...@@ -39,12 +39,12 @@ public class AvcChunkPeekerTest { ...@@ -39,12 +39,12 @@ public class AvcChunkPeekerTest {
(byte)0xFE,(byte)0x9E,0x10,0,0}; (byte)0xFE,(byte)0x9E,0x10,0,0};
private FakeTrackOutput fakeTrackOutput; private FakeTrackOutput fakeTrackOutput;
private AvcChunkHandler avcChunkPeeker; private AvcChunkHandler avcChunkHandler;
@Before @Before
public void before() { public void before() {
fakeTrackOutput = new FakeTrackOutput(false); fakeTrackOutput = new FakeTrackOutput(false);
avcChunkPeeker = new AvcChunkHandler(0, fakeTrackOutput, avcChunkHandler = new AvcChunkHandler(0, fakeTrackOutput,
new ChunkClock(10_000_000L, 24 * 10), FORMAT_BUILDER_AVC); new ChunkClock(10_000_000L, 24 * 10), FORMAT_BUILDER_AVC);
} }
...@@ -55,26 +55,26 @@ public class AvcChunkPeekerTest { ...@@ -55,26 +55,26 @@ public class AvcChunkPeekerTest {
final FakeExtractorInput input = new FakeExtractorInput.Builder().setData(bytes).build(); final FakeExtractorInput input = new FakeExtractorInput.Builder().setData(bytes).build();
avcChunkPeeker.peek(input, bytes.length); avcChunkHandler.peek(input, bytes.length);
} }
@Test @Test
public void peek_givenStreamHeader() throws IOException { public void peek_givenStreamHeader() throws IOException {
peekStreamHeader(); peekStreamHeader();
final PicCountClock picCountClock = avcChunkPeeker.getPicCountClock(); final PicCountClock picCountClock = avcChunkHandler.getPicCountClock();
Assert.assertNotNull(picCountClock); Assert.assertNotNull(picCountClock);
Assert.assertEquals(64, picCountClock.getMaxPicCount()); Assert.assertEquals(64, picCountClock.getMaxPicCount());
Assert.assertEquals(0, avcChunkPeeker.getSpsData().picOrderCountType); Assert.assertEquals(0, avcChunkHandler.getSpsData().picOrderCountType);
Assert.assertEquals(1.18f, fakeTrackOutput.lastFormat.pixelWidthHeightRatio, 0.01f); Assert.assertEquals(1.18f, fakeTrackOutput.lastFormat.pixelWidthHeightRatio, 0.01f);
} }
@Test @Test
public void newChunk_givenStreamHeaderAndPSlice() throws IOException { public void newChunk_givenStreamHeaderAndPSlice() throws IOException {
peekStreamHeader(); peekStreamHeader();
final PicCountClock picCountClock = avcChunkPeeker.getPicCountClock(); final PicCountClock picCountClock = avcChunkHandler.getPicCountClock();
final FakeExtractorInput input = new FakeExtractorInput.Builder().setData(P_SLICE).build(); final FakeExtractorInput input = new FakeExtractorInput.Builder().setData(P_SLICE).build();
avcChunkPeeker.newChunk(0, P_SLICE.length, input); avcChunkHandler.newChunk(P_SLICE.length, input);
Assert.assertEquals(12, picCountClock.getLastPicCount()); Assert.assertEquals(12, picCountClock.getLastPicCount());
} }
......
...@@ -15,7 +15,6 @@ ...@@ -15,7 +15,6 @@
*/ */
package com.google.android.exoplayer2.extractor.avi; package com.google.android.exoplayer2.extractor.avi;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.extractor.SeekMap; import com.google.android.exoplayer2.extractor.SeekMap;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
...@@ -23,26 +22,25 @@ import org.junit.Test; ...@@ -23,26 +22,25 @@ import org.junit.Test;
public class AviSeekMapTest { public class AviSeekMapTest {
@Test @Test
public void setFrames_givenExactSeekPointMatch() { public void getFrames_givenExactSeekPointMatch() {
final AviSeekMap aviSeekMap = DataHelper.getAviSeekMap(); final AviSeekMap aviSeekMap = DataHelper.getAviSeekMap();
final long position = aviSeekMap.keyFrameOffsetsDiv2[1] * 2L + aviSeekMap.seekOffset; final long position = aviSeekMap.keyFrameOffsetsDiv2[1] * 2L + aviSeekMap.seekOffset;
final int secs = 4; final int secs = 4;
final ChunkHandler[] chunkHandlers = new ChunkHandler[]{DataHelper.getVideoChunkHandler(secs), final ChunkHandler[] chunkHandlers = new ChunkHandler[]{DataHelper.getVideoChunkHandler(secs),
DataHelper.getAudioChunkHandler(secs)}; DataHelper.getAudioChunkHandler(secs)};
aviSeekMap.setFrames(position, C.MICROS_PER_SECOND, chunkHandlers); int[] indexes = aviSeekMap.getIndexes(position);
for (int i=0;i<chunkHandlers.length;i++) { for (int i=0;i<chunkHandlers.length;i++) {
Assert.assertEquals(aviSeekMap.seekIndexes[i][1], chunkHandlers[i].getClock().getIndex()); Assert.assertEquals(aviSeekMap.seekIndexes[i][1], indexes[i]);
} }
} }
@Test @Test
public void setFrames_givenBadPosition() { public void setFrames_givenBadPosition() {
final AviSeekMap aviSeekMap = DataHelper.getAviSeekMap(); final AviSeekMap aviSeekMap = DataHelper.getAviSeekMap();
final ChunkHandler[] chunkHandlers = new ChunkHandler[2];
try { try {
aviSeekMap.setFrames(1L, C.MICROS_PER_SECOND, chunkHandlers); aviSeekMap.getIndexes(1L);
Assert.fail(); Assert.fail();
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
//Intentionally blank //Intentionally blank
......
/*
* Copyright (C) 2022 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.extractor.avi;
import org.junit.Assert;
import org.junit.Test;
public class ChunkHandlerTest {
@Test
public void setClock_givenLinearClock() {
final ChunkClock linearClock = new ChunkClock(1_000_000L, 30);
final ChunkHandler chunkHandler = DataHelper.getVideoChunkHandler(1);
chunkHandler.setClock(linearClock);
Assert.assertSame(linearClock, chunkHandler.getClock());
}
}
...@@ -19,9 +19,9 @@ import com.google.android.exoplayer2.extractor.ExtractorInput; ...@@ -19,9 +19,9 @@ import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.testutil.FakeTrackOutput; import com.google.android.exoplayer2.testutil.FakeTrackOutput;
import java.io.IOException; import java.io.IOException;
public class MockNalChunkPeeker extends NalChunkPeeker { public class MockNalChunkHandler extends NalChunkHandler {
private boolean skip; private boolean skip;
public MockNalChunkPeeker(int peakSize, boolean skip) { public MockNalChunkHandler(int peakSize, boolean skip) {
super(0, new FakeTrackOutput(false), new ChunkClock(1_000_000L, 24), peakSize); super(0, new FakeTrackOutput(false), new ChunkClock(1_000_000L, 24), peakSize);
this.skip = skip; this.skip = skip;
} }
......
...@@ -16,7 +16,6 @@ ...@@ -16,7 +16,6 @@
package com.google.android.exoplayer2.extractor.avi; package com.google.android.exoplayer2.extractor.avi;
import com.google.android.exoplayer2.testutil.FakeExtractorInput; import com.google.android.exoplayer2.testutil.FakeExtractorInput;
import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import org.junit.Assert; import org.junit.Assert;
...@@ -26,7 +25,7 @@ public class NalChunkPeekerTest { ...@@ -26,7 +25,7 @@ public class NalChunkPeekerTest {
@Test @Test
public void construct_givenTooSmallPeekSize() { public void construct_givenTooSmallPeekSize() {
try { try {
new MockNalChunkPeeker(4, false); new MockNalChunkHandler(4, false);
Assert.fail(); Assert.fail();
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
//Intentionally blank //Intentionally blank
...@@ -36,7 +35,7 @@ public class NalChunkPeekerTest { ...@@ -36,7 +35,7 @@ public class NalChunkPeekerTest {
@Test @Test
public void peek_givenNoData() { public void peek_givenNoData() {
final FakeExtractorInput input = new FakeExtractorInput.Builder().build(); final FakeExtractorInput input = new FakeExtractorInput.Builder().build();
final MockNalChunkPeeker peeker = new MockNalChunkPeeker(5, false); final MockNalChunkHandler peeker = new MockNalChunkHandler(5, false);
try { try {
peeker.peek(input, 10); peeker.peek(input, 10);
} catch (IOException e) { } catch (IOException e) {
...@@ -46,7 +45,7 @@ public class NalChunkPeekerTest { ...@@ -46,7 +45,7 @@ public class NalChunkPeekerTest {
@Test @Test
public void peek_givenNoNal() { public void peek_givenNoNal() {
final FakeExtractorInput input = new FakeExtractorInput.Builder().setData(new byte[10]).build(); final FakeExtractorInput input = new FakeExtractorInput.Builder().setData(new byte[10]).build();
final MockNalChunkPeeker peeker = new MockNalChunkPeeker(5, false); final MockNalChunkHandler peeker = new MockNalChunkHandler(5, false);
try { try {
peeker.peek(input, 10); peeker.peek(input, 10);
} catch (IOException e) { } catch (IOException e) {
...@@ -59,7 +58,7 @@ public class NalChunkPeekerTest { ...@@ -59,7 +58,7 @@ public class NalChunkPeekerTest {
DataHelper.appendNal(byteBuffer, (byte)32); DataHelper.appendNal(byteBuffer, (byte)32);
final FakeExtractorInput input = new FakeExtractorInput.Builder().setData(byteBuffer.array()).build(); final FakeExtractorInput input = new FakeExtractorInput.Builder().setData(byteBuffer.array()).build();
final MockNalChunkPeeker peeker = new MockNalChunkPeeker(5, true); final MockNalChunkHandler peeker = new MockNalChunkHandler(5, true);
try { try {
peeker.peek(input, 10); peeker.peek(input, 10);
Assert.assertEquals(0, input.getPeekPosition()); Assert.assertEquals(0, input.getPeekPosition());
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment