Found out RESULT_SEEK is a bad thing. Greatly improved Extractor efficiency.

This commit is contained in:
Dustin 2022-02-02 14:35:52 -07:00
parent e9fcc967a3
commit 1528b8b5ee
6 changed files with 59 additions and 64 deletions

View File

@ -72,15 +72,6 @@ public class AviExtractor implements Extractor {
} }
} }
static int alignPositionHolder(@NonNull ExtractorInput input, @NonNull PositionHolder seekPosition) {
final long position = input.getPosition();
if ((position & 1) == 1) {
seekPosition.position = position + 1;
return RESULT_SEEK;
}
return RESULT_CONTINUE;
}
@NonNull @NonNull
static ByteBuffer allocate(int bytes) { static ByteBuffer allocate(int bytes) {
final byte[] buffer = new byte[bytes]; final byte[] buffer = new byte[bytes];
@ -503,47 +494,56 @@ public class AviExtractor implements Extractor {
return null; return null;
} }
int readSamples(@NonNull ExtractorInput input, @NonNull PositionHolder seekPosition) throws IOException { int readSamples(@NonNull ExtractorInput input) throws IOException {
if (chunkHandler != null) { if (chunkHandler != null) {
if (chunkHandler.resume(input)) { if (chunkHandler.resume(input)) {
chunkHandler = null; chunkHandler = null;
return alignPositionHolder(input, seekPosition); alignInput(input);
} }
} else { } else {
ByteBuffer byteBuffer = allocate(8); final int toRead = 8;
ByteBuffer byteBuffer = allocate(toRead);
final byte[] bytes = byteBuffer.array(); final byte[] bytes = byteBuffer.array();
alignInput(input); alignInput(input);
input.readFully(bytes, 0, 1); input.readFully(bytes, 0, toRead);
//This is super inefficient, but should be rare
while (bytes[0] == 0) { while (bytes[0] == 0) {
input.readFully(bytes, 0, 1); for (int i=1;i<toRead;i++) {
bytes[i - 1] = bytes[i];
}
int read = input.read(bytes, toRead - 1, 1);
if (read == C.RESULT_END_OF_INPUT) {
return RESULT_END_OF_INPUT;
}
} }
if (input.getPosition() >= moviEnd) {
return RESULT_END_OF_INPUT;
}
input.readFully(bytes, 1, 7);
final int chunkId = byteBuffer.getInt(); final int chunkId = byteBuffer.getInt();
if (chunkId == ListBox.LIST) { if (chunkId == ListBox.LIST) {
seekPosition.position = input.getPosition() + 8; input.skipFully(8);
return RESULT_SEEK; return RESULT_CONTINUE;
} }
final int size = byteBuffer.getInt(); final int size = byteBuffer.getInt();
if (chunkId == JUNK) { if (chunkId == JUNK) {
seekPosition.position = alignPosition(input.getPosition() + size); input.skipFully(size);
return RESULT_SEEK; alignInput(input);
return RESULT_CONTINUE;
} }
final AviTrack aviTrack = getAviTrack(chunkId); final AviTrack aviTrack = getAviTrack(chunkId);
if (aviTrack == null) { if (aviTrack == null) {
seekPosition.position = alignPosition(input.getPosition() + size); input.skipFully(size);
alignInput(input);
w("Unknown tag=" + toString(chunkId) + " pos=" + (input.getPosition() - 8) w("Unknown tag=" + toString(chunkId) + " pos=" + (input.getPosition() - 8)
+ " size=" + size + " moviEnd=" + moviEnd); + " size=" + size + " moviEnd=" + moviEnd);
return RESULT_SEEK; return RESULT_CONTINUE;
} }
if (aviTrack.newChunk(chunkId, size, input)) { if (aviTrack.newChunk(chunkId, size, input)) {
return alignPositionHolder(input, seekPosition); alignInput(input);
} else { } else {
chunkHandler = aviTrack; chunkHandler = aviTrack;
} }
} }
if (input.getPosition() == input.getLength()) {
return C.RESULT_END_OF_INPUT;
}
return RESULT_CONTINUE; return RESULT_CONTINUE;
} }
@ -551,7 +551,7 @@ public class AviExtractor implements Extractor {
public int read(@NonNull ExtractorInput input, @NonNull PositionHolder seekPosition) throws IOException { public int read(@NonNull ExtractorInput input, @NonNull PositionHolder seekPosition) throws IOException {
switch (state) { switch (state) {
case STATE_READ_SAMPLES: case STATE_READ_SAMPLES:
return readSamples(input, seekPosition); return readSamples(input);
case STATE_SEEK_START: case STATE_SEEK_START:
state = STATE_READ_SAMPLES; state = STATE_READ_SAMPLES;
seekPosition.position = moviOffset + 4; seekPosition.position = moviOffset + 4;

View File

@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer2.extractor.avi; package com.google.android.exoplayer2.extractor.avi;
import com.google.android.exoplayer2.C;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
/** /**
@ -46,7 +47,7 @@ public class StreamHeaderBox extends ResidentBox {
} }
public long getDurationUs() { public long getDurationUs() {
return 1_000_000L * getScale() * getLength() / getRate(); return C.MICROS_PER_SECOND * getScale() * getLength() / getRate();
} }
public int getSteamType() { public int getSteamType() {

View File

@ -112,4 +112,22 @@ public class AviExtractorRoboTest {
Assert.assertEquals(aviTrack.getClock().durationUs, streamHeaderBox.getDurationUs()); Assert.assertEquals(aviTrack.getClock().durationUs, streamHeaderBox.getDurationUs());
} }
@Test
public void readSamples_fragmentedChunk() throws IOException {
AviExtractor aviExtractor = AviExtractorTest.setupVideoAviExtractor();
final AviTrack aviTrack = aviExtractor.getVideoTrack();
final int size = 24 + 16;
final ByteBuffer byteBuffer = AviExtractor.allocate(size + 8);
byteBuffer.putInt(aviTrack.chunkId);
byteBuffer.putInt(size);
final ExtractorInput chunk = new FakeExtractorInput.Builder().setData(byteBuffer.array()).
setSimulatePartialReads(true).build();
Assert.assertEquals(Extractor.RESULT_CONTINUE, aviExtractor.read(chunk, new PositionHolder()));
Assert.assertEquals(Extractor.RESULT_END_OF_INPUT, aviExtractor.read(chunk, new PositionHolder()));
final FakeTrackOutput fakeTrackOutput = (FakeTrackOutput) aviTrack.trackOutput;
Assert.assertEquals(size, fakeTrackOutput.getSampleData(0).length);
}
} }

View File

@ -303,28 +303,6 @@ public class AviExtractorTest {
Assert.assertEquals(0, aviExtractor.aviSeekMap.seekOffset); Assert.assertEquals(0, aviExtractor.aviSeekMap.seekOffset);
} }
@Test
public void alignPositionHolder_givenOddPosition() {
final FakeExtractorInput fakeExtractorInput = new FakeExtractorInput.Builder().
setData(new byte[4]).build();
fakeExtractorInput.setPosition(1);
final PositionHolder positionHolder = new PositionHolder();
final int result = AviExtractor.alignPositionHolder(fakeExtractorInput, positionHolder);
Assert.assertEquals(Extractor.RESULT_SEEK, result);
Assert.assertEquals(2, positionHolder.position);
}
@Test
public void alignPositionHolder_givenEvenPosition() {
final FakeExtractorInput fakeExtractorInput = new FakeExtractorInput.Builder().
setData(new byte[4]).build();
fakeExtractorInput.setPosition(2);
final PositionHolder positionHolder = new PositionHolder();
final int result = AviExtractor.alignPositionHolder(fakeExtractorInput, positionHolder);
Assert.assertEquals(Extractor.RESULT_CONTINUE, result);
}
@Test @Test
public void readHeaderList_givenBadHeader() throws IOException { public void readHeaderList_givenBadHeader() throws IOException {
final FakeExtractorInput input = new FakeExtractorInput.Builder().setData(new byte[32]).build(); final FakeExtractorInput input = new FakeExtractorInput.Builder().setData(new byte[32]).build();
@ -405,7 +383,7 @@ public class AviExtractorTest {
Assert.assertEquals(64 * 1024 + 8, positionHolder.position); Assert.assertEquals(64 * 1024 + 8, positionHolder.position);
} }
private AviExtractor setupVideoAviExtractor() { static AviExtractor setupVideoAviExtractor() {
final AviExtractor aviExtractor = new AviExtractor(); final AviExtractor aviExtractor = new AviExtractor();
aviExtractor.setAviHeader(DataHelper.createAviHeaderBox()); aviExtractor.setAviHeader(DataHelper.createAviHeaderBox());
final FakeExtractorOutput fakeExtractorOutput = new FakeExtractorOutput(); final FakeExtractorOutput fakeExtractorOutput = new FakeExtractorOutput();
@ -444,31 +422,27 @@ public class AviExtractorTest {
final ExtractorInput input = new FakeExtractorInput.Builder().setData(byteBuffer.array()) final ExtractorInput input = new FakeExtractorInput.Builder().setData(byteBuffer.array())
.build(); .build();
Assert.assertEquals(Extractor.RESULT_CONTINUE, aviExtractor.read(input, new PositionHolder())); Assert.assertEquals(Extractor.RESULT_END_OF_INPUT, aviExtractor.read(input, new PositionHolder()));
final FakeTrackOutput fakeTrackOutput = (FakeTrackOutput) aviTrack.trackOutput; final FakeTrackOutput fakeTrackOutput = (FakeTrackOutput) aviTrack.trackOutput;
Assert.assertEquals(24, fakeTrackOutput.getSampleData(0).length); Assert.assertEquals(24, fakeTrackOutput.getSampleData(0).length);
} }
@Test @Test
public void readSamples_fragmentedChunk() throws IOException { public void readSamples_givenLeadingZeros() throws IOException {
AviExtractor aviExtractor = setupVideoAviExtractor(); AviExtractor aviExtractor = setupVideoAviExtractor();
final AviTrack aviTrack = aviExtractor.getVideoTrack(); final AviTrack aviTrack = aviExtractor.getVideoTrack();
final int size = 24 + 16; final ByteBuffer byteBuffer = AviExtractor.allocate(48);
final ByteBuffer byteBuffer = AviExtractor.allocate(32); byteBuffer.position(16);
byteBuffer.putInt(aviTrack.chunkId); byteBuffer.putInt(aviTrack.chunkId);
byteBuffer.putInt(size); byteBuffer.putInt(24);
final ExtractorInput chunk0 = new FakeExtractorInput.Builder().setData(byteBuffer.array()) final ExtractorInput input = new FakeExtractorInput.Builder().setData(byteBuffer.array())
.build(); .build();
Assert.assertEquals(Extractor.RESULT_CONTINUE, aviExtractor.read(chunk0, new PositionHolder())); Assert.assertEquals(Extractor.RESULT_END_OF_INPUT, aviExtractor.read(input, new PositionHolder()));
final ExtractorInput chunk1 = new FakeExtractorInput.Builder().setData(new byte[16])
.build();
Assert.assertEquals(Extractor.RESULT_CONTINUE, aviExtractor.read(chunk1, new PositionHolder()));
final FakeTrackOutput fakeTrackOutput = (FakeTrackOutput) aviTrack.trackOutput; final FakeTrackOutput fakeTrackOutput = (FakeTrackOutput) aviTrack.trackOutput;
Assert.assertEquals(size, fakeTrackOutput.getSampleData(0).length); Assert.assertEquals(24, fakeTrackOutput.getSampleData(0).length);
} }
@Test @Test

View File

@ -15,6 +15,7 @@
*/ */
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;
@ -29,7 +30,7 @@ public class AviSeekMapTest {
final AviTrack[] aviTracks = new AviTrack[]{DataHelper.getVideoAviTrack(secs), final AviTrack[] aviTracks = new AviTrack[]{DataHelper.getVideoAviTrack(secs),
DataHelper.getAudioAviTrack(secs)}; DataHelper.getAudioAviTrack(secs)};
aviSeekMap.setFrames(position, 1_000_000L, aviTracks); aviSeekMap.setFrames(position, C.MICROS_PER_SECOND, aviTracks);
for (int i=0;i<aviTracks.length;i++) { for (int i=0;i<aviTracks.length;i++) {
Assert.assertEquals(aviSeekMap.seekIndexes[i][1], aviTracks[i].getClock().getIndex()); Assert.assertEquals(aviSeekMap.seekIndexes[i][1], aviTracks[i].getClock().getIndex());
} }
@ -41,7 +42,7 @@ public class AviSeekMapTest {
final AviTrack[] aviTracks = new AviTrack[2]; final AviTrack[] aviTracks = new AviTrack[2];
try { try {
aviSeekMap.setFrames(1L, 1_000_000L, aviTracks); aviSeekMap.setFrames(1L, C.MICROS_PER_SECOND, aviTracks);
Assert.fail(); Assert.fail();
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
//Intentionally blank //Intentionally blank

View File

@ -38,5 +38,6 @@ public class StreamHeaderBoxTest {
Assert.assertEquals(FPS24, streamHeaderBox.getFrameRate(), 0.1); Assert.assertEquals(FPS24, streamHeaderBox.getFrameRate(), 0.1);
Assert.assertEquals(9 * DataHelper.FPS, streamHeaderBox.getLength()); Assert.assertEquals(9 * DataHelper.FPS, streamHeaderBox.getLength());
Assert.assertEquals(128 * 1024, streamHeaderBox.getSuggestedBufferSize()); Assert.assertEquals(128 * 1024, streamHeaderBox.getSuggestedBufferSize());
Assert.assertTrue(streamHeaderBox.toString().startsWith("scale=" + streamHeaderBox.getScale()));
} }
} }