Allow no output frame holder BinarySearchSeeker

PiperOrigin-RevId: 283544187
This commit is contained in:
kimvde 2019-12-03 15:51:44 +00:00 committed by bachinger
parent f28a17f9eb
commit 23b54a95d2
8 changed files with 60 additions and 64 deletions

View File

@ -20,10 +20,12 @@ import static org.junit.Assert.fail;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.ext.flac.FlacBinarySearchSeeker.OutputFrameHolder;
import com.google.android.exoplayer2.extractor.SeekMap; import com.google.android.exoplayer2.extractor.SeekMap;
import com.google.android.exoplayer2.testutil.FakeExtractorInput; import com.google.android.exoplayer2.testutil.FakeExtractorInput;
import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.testutil.TestUtil;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -47,19 +49,20 @@ public final class FlacBinarySearchSeekerTest {
throws IOException, FlacDecoderException, InterruptedException { throws IOException, FlacDecoderException, InterruptedException {
byte[] data = byte[] data =
TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), NOSEEKTABLE_FLAC); TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), NOSEEKTABLE_FLAC);
FakeExtractorInput input = new FakeExtractorInput.Builder().setData(data).build(); FakeExtractorInput input = new FakeExtractorInput.Builder().setData(data).build();
FlacDecoderJni decoderJni = new FlacDecoderJni(); FlacDecoderJni decoderJni = new FlacDecoderJni();
decoderJni.setData(input); decoderJni.setData(input);
OutputFrameHolder outputFrameHolder = new OutputFrameHolder(ByteBuffer.allocateDirect(0));
FlacBinarySearchSeeker seeker = FlacBinarySearchSeeker seeker =
new FlacBinarySearchSeeker( new FlacBinarySearchSeeker(
decoderJni.decodeStreamMetadata(), decoderJni.decodeStreamMetadata(),
/* firstFramePosition= */ 0, /* firstFramePosition= */ 0,
data.length, data.length,
decoderJni); decoderJni,
outputFrameHolder);
SeekMap seekMap = seeker.getSeekMap(); SeekMap seekMap = seeker.getSeekMap();
assertThat(seekMap).isNotNull(); assertThat(seekMap).isNotNull();
assertThat(seekMap.getDurationUs()).isEqualTo(DURATION_US); assertThat(seekMap.getDurationUs()).isEqualTo(DURATION_US);
assertThat(seekMap.isSeekable()).isTrue(); assertThat(seekMap.isSeekable()).isTrue();
@ -70,18 +73,20 @@ public final class FlacBinarySearchSeekerTest {
throws IOException, FlacDecoderException, InterruptedException { throws IOException, FlacDecoderException, InterruptedException {
byte[] data = byte[] data =
TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), NOSEEKTABLE_FLAC); TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), NOSEEKTABLE_FLAC);
FakeExtractorInput input = new FakeExtractorInput.Builder().setData(data).build(); FakeExtractorInput input = new FakeExtractorInput.Builder().setData(data).build();
FlacDecoderJni decoderJni = new FlacDecoderJni(); FlacDecoderJni decoderJni = new FlacDecoderJni();
decoderJni.setData(input); decoderJni.setData(input);
OutputFrameHolder outputFrameHolder = new OutputFrameHolder(ByteBuffer.allocateDirect(0));
FlacBinarySearchSeeker seeker = FlacBinarySearchSeeker seeker =
new FlacBinarySearchSeeker( new FlacBinarySearchSeeker(
decoderJni.decodeStreamMetadata(), decoderJni.decodeStreamMetadata(),
/* firstFramePosition= */ 0, /* firstFramePosition= */ 0,
data.length, data.length,
decoderJni); decoderJni,
outputFrameHolder);
seeker.setSeekTargetUs(/* timeUs= */ 1000); seeker.setSeekTargetUs(/* timeUs= */ 1000);
assertThat(seeker.isSeeking()).isTrue(); assertThat(seeker.isSeeking()).isTrue();
} }
} }

View File

@ -31,16 +31,33 @@ import java.nio.ByteBuffer;
*/ */
/* package */ final class FlacBinarySearchSeeker extends BinarySearchSeeker { /* package */ final class FlacBinarySearchSeeker extends BinarySearchSeeker {
/**
* Holds a frame extracted from a stream, together with the time stamp of the frame in
* microseconds.
*/
public static final class OutputFrameHolder {
public final ByteBuffer byteBuffer;
public long timeUs;
/** Constructs an instance, wrapping the given byte buffer. */
public OutputFrameHolder(ByteBuffer outputByteBuffer) {
this.timeUs = 0;
this.byteBuffer = outputByteBuffer;
}
}
private final FlacDecoderJni decoderJni; private final FlacDecoderJni decoderJni;
public FlacBinarySearchSeeker( public FlacBinarySearchSeeker(
FlacStreamMetadata streamMetadata, FlacStreamMetadata streamMetadata,
long firstFramePosition, long firstFramePosition,
long inputLength, long inputLength,
FlacDecoderJni decoderJni) { FlacDecoderJni decoderJni,
OutputFrameHolder outputFrameHolder) {
super( super(
new FlacSeekTimestampConverter(streamMetadata), new FlacSeekTimestampConverter(streamMetadata),
new FlacTimestampSeeker(decoderJni), new FlacTimestampSeeker(decoderJni, outputFrameHolder),
streamMetadata.getDurationUs(), streamMetadata.getDurationUs(),
/* floorTimePosition= */ 0, /* floorTimePosition= */ 0,
/* ceilingTimePosition= */ streamMetadata.totalSamples, /* ceilingTimePosition= */ streamMetadata.totalSamples,
@ -63,14 +80,15 @@ import java.nio.ByteBuffer;
private static final class FlacTimestampSeeker implements TimestampSeeker { private static final class FlacTimestampSeeker implements TimestampSeeker {
private final FlacDecoderJni decoderJni; private final FlacDecoderJni decoderJni;
private final OutputFrameHolder outputFrameHolder;
private FlacTimestampSeeker(FlacDecoderJni decoderJni) { private FlacTimestampSeeker(FlacDecoderJni decoderJni, OutputFrameHolder outputFrameHolder) {
this.decoderJni = decoderJni; this.decoderJni = decoderJni;
this.outputFrameHolder = outputFrameHolder;
} }
@Override @Override
public TimestampSearchResult searchForTimestamp( public TimestampSearchResult searchForTimestamp(ExtractorInput input, long targetSampleIndex)
ExtractorInput input, long targetSampleIndex, OutputFrameHolder outputFrameHolder)
throws IOException, InterruptedException { throws IOException, InterruptedException {
ByteBuffer outputBuffer = outputFrameHolder.byteBuffer; ByteBuffer outputBuffer = outputFrameHolder.byteBuffer;
long searchPosition = input.getPosition(); long searchPosition = input.getPosition();

View File

@ -21,7 +21,7 @@ import androidx.annotation.IntDef;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.BinarySearchSeeker.OutputFrameHolder; import com.google.android.exoplayer2.ext.flac.FlacBinarySearchSeeker.OutputFrameHolder;
import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.ExtractorInput; import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.ExtractorOutput;
@ -190,11 +190,12 @@ public final class FlacExtractor implements Extractor {
return; return;
} }
FlacDecoderJni flacDecoderJni = decoderJni;
FlacStreamMetadata streamMetadata; FlacStreamMetadata streamMetadata;
try { try {
streamMetadata = decoderJni.decodeStreamMetadata(); streamMetadata = flacDecoderJni.decodeStreamMetadata();
} catch (IOException e) { } catch (IOException e) {
decoderJni.reset(/* newPosition= */ 0); flacDecoderJni.reset(/* newPosition= */ 0);
input.setRetryPosition(/* position= */ 0, e); input.setRetryPosition(/* position= */ 0, e);
throw e; throw e;
} }
@ -202,12 +203,17 @@ public final class FlacExtractor implements Extractor {
streamMetadataDecoded = true; streamMetadataDecoded = true;
if (this.streamMetadata == null) { if (this.streamMetadata == null) {
this.streamMetadata = streamMetadata; this.streamMetadata = streamMetadata;
binarySearchSeeker =
outputSeekMap(decoderJni, streamMetadata, input.getLength(), extractorOutput);
Metadata metadata = streamMetadata.getMetadataCopyWithAppendedEntriesFrom(id3Metadata);
outputFormat(streamMetadata, metadata, trackOutput);
outputBuffer.reset(streamMetadata.getMaxDecodedFrameSize()); outputBuffer.reset(streamMetadata.getMaxDecodedFrameSize());
outputFrameHolder = new OutputFrameHolder(ByteBuffer.wrap(outputBuffer.data)); outputFrameHolder = new OutputFrameHolder(ByteBuffer.wrap(outputBuffer.data));
binarySearchSeeker =
outputSeekMap(
flacDecoderJni,
streamMetadata,
input.getLength(),
extractorOutput,
outputFrameHolder);
Metadata metadata = streamMetadata.getMetadataCopyWithAppendedEntriesFrom(id3Metadata);
outputFormat(streamMetadata, metadata, trackOutput);
} }
} }
@ -219,7 +225,7 @@ public final class FlacExtractor implements Extractor {
OutputFrameHolder outputFrameHolder, OutputFrameHolder outputFrameHolder,
TrackOutput trackOutput) TrackOutput trackOutput)
throws InterruptedException, IOException { throws InterruptedException, IOException {
int seekResult = binarySearchSeeker.handlePendingSeek(input, seekPosition, outputFrameHolder); int seekResult = binarySearchSeeker.handlePendingSeek(input, seekPosition);
ByteBuffer outputByteBuffer = outputFrameHolder.byteBuffer; ByteBuffer outputByteBuffer = outputFrameHolder.byteBuffer;
if (seekResult == RESULT_CONTINUE && outputByteBuffer.limit() > 0) { if (seekResult == RESULT_CONTINUE && outputByteBuffer.limit() > 0) {
outputSample(outputBuffer, outputByteBuffer.limit(), outputFrameHolder.timeUs, trackOutput); outputSample(outputBuffer, outputByteBuffer.limit(), outputFrameHolder.timeUs, trackOutput);
@ -236,7 +242,8 @@ public final class FlacExtractor implements Extractor {
FlacDecoderJni decoderJni, FlacDecoderJni decoderJni,
FlacStreamMetadata streamMetadata, FlacStreamMetadata streamMetadata,
long streamLength, long streamLength,
ExtractorOutput output) { ExtractorOutput output,
OutputFrameHolder outputFrameHolder) {
boolean haveSeekTable = decoderJni.getSeekPoints(/* timeUs= */ 0) != null; boolean haveSeekTable = decoderJni.getSeekPoints(/* timeUs= */ 0) != null;
FlacBinarySearchSeeker binarySearchSeeker = null; FlacBinarySearchSeeker binarySearchSeeker = null;
SeekMap seekMap; SeekMap seekMap;
@ -245,7 +252,8 @@ public final class FlacExtractor implements Extractor {
} else if (streamLength != C.LENGTH_UNSET) { } else if (streamLength != C.LENGTH_UNSET) {
long firstFramePosition = decoderJni.getDecodePosition(); long firstFramePosition = decoderJni.getDecodePosition();
binarySearchSeeker = binarySearchSeeker =
new FlacBinarySearchSeeker(streamMetadata, firstFramePosition, streamLength, decoderJni); new FlacBinarySearchSeeker(
streamMetadata, firstFramePosition, streamLength, decoderJni, outputFrameHolder);
seekMap = binarySearchSeeker.getSeekMap(); seekMap = binarySearchSeeker.getSeekMap();
} else { } else {
seekMap = new SeekMap.Unseekable(streamMetadata.getDurationUs()); seekMap = new SeekMap.Unseekable(streamMetadata.getDurationUs());

View File

@ -24,7 +24,6 @@ import java.io.IOException;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.nio.ByteBuffer;
/** /**
* A seeker that supports seeking within a stream by searching for the target frame using binary * A seeker that supports seeking within a stream by searching for the target frame using binary
@ -48,38 +47,17 @@ public abstract class BinarySearchSeeker {
* *
* @param input The {@link ExtractorInput} from which data should be peeked. * @param input The {@link ExtractorInput} from which data should be peeked.
* @param targetTimestamp The target timestamp. * @param targetTimestamp The target timestamp.
* @param outputFrameHolder If {@link TimestampSearchResult#TYPE_TARGET_TIMESTAMP_FOUND} is
* returned, this holder may be updated to hold the extracted frame that contains the target
* frame/sample associated with the target timestamp.
* @return A {@link TimestampSearchResult} that describes the result of the search. * @return A {@link TimestampSearchResult} that describes the result of the search.
* @throws IOException If an error occurred reading from the input. * @throws IOException If an error occurred reading from the input.
* @throws InterruptedException If the thread was interrupted. * @throws InterruptedException If the thread was interrupted.
*/ */
TimestampSearchResult searchForTimestamp( TimestampSearchResult searchForTimestamp(ExtractorInput input, long targetTimestamp)
ExtractorInput input, long targetTimestamp, OutputFrameHolder outputFrameHolder)
throws IOException, InterruptedException; throws IOException, InterruptedException;
/** Called when a seek operation finishes. */ /** Called when a seek operation finishes. */
default void onSeekFinished() {} default void onSeekFinished() {}
} }
/**
* Holds a frame extracted from a stream, together with the time stamp of the frame in
* microseconds.
*/
public static final class OutputFrameHolder {
public final ByteBuffer byteBuffer;
public long timeUs;
/** Constructs an instance, wrapping the given byte buffer. */
public OutputFrameHolder(ByteBuffer outputByteBuffer) {
this.timeUs = 0;
this.byteBuffer = outputByteBuffer;
}
}
/** /**
* A {@link SeekTimestampConverter} implementation that returns the seek time itself as the * A {@link SeekTimestampConverter} implementation that returns the seek time itself as the
* timestamp for a seek time position. * timestamp for a seek time position.
@ -189,15 +167,11 @@ public abstract class BinarySearchSeeker {
* @param input The {@link ExtractorInput} from which data should be read. * @param input The {@link ExtractorInput} from which data should be read.
* @param seekPositionHolder If {@link Extractor#RESULT_SEEK} is returned, this holder is updated * @param seekPositionHolder If {@link Extractor#RESULT_SEEK} is returned, this holder is updated
* to hold the position of the required seek. * to hold the position of the required seek.
* @param outputFrameHolder If {@link Extractor#RESULT_CONTINUE} is returned, this holder may be
* updated to hold the extracted frame that contains the target sample. The caller needs to
* check the byte buffer limit to see if an extracted frame is available.
* @return One of the {@code RESULT_} values defined in {@link Extractor}. * @return One of the {@code RESULT_} values defined in {@link Extractor}.
* @throws IOException If an error occurred reading from the input. * @throws IOException If an error occurred reading from the input.
* @throws InterruptedException If the thread was interrupted. * @throws InterruptedException If the thread was interrupted.
*/ */
public int handlePendingSeek( public int handlePendingSeek(ExtractorInput input, PositionHolder seekPositionHolder)
ExtractorInput input, PositionHolder seekPositionHolder, OutputFrameHolder outputFrameHolder)
throws InterruptedException, IOException { throws InterruptedException, IOException {
TimestampSeeker timestampSeeker = Assertions.checkNotNull(this.timestampSeeker); TimestampSeeker timestampSeeker = Assertions.checkNotNull(this.timestampSeeker);
while (true) { while (true) {
@ -217,8 +191,7 @@ public abstract class BinarySearchSeeker {
input.resetPeekPosition(); input.resetPeekPosition();
TimestampSearchResult timestampSearchResult = TimestampSearchResult timestampSearchResult =
timestampSeeker.searchForTimestamp( timestampSeeker.searchForTimestamp(input, seekOperationParams.getTargetTimePosition());
input, seekOperationParams.getTargetTimePosition(), outputFrameHolder);
switch (timestampSearchResult.type) { switch (timestampSearchResult.type) {
case TimestampSearchResult.TYPE_POSITION_OVERESTIMATED: case TimestampSearchResult.TYPE_POSITION_OVERESTIMATED:
@ -419,7 +392,7 @@ public abstract class BinarySearchSeeker {
/** /**
* Represents possible search results for {@link * Represents possible search results for {@link
* TimestampSeeker#searchForTimestamp(ExtractorInput, long, OutputFrameHolder)}. * TimestampSeeker#searchForTimestamp(ExtractorInput, long)}.
*/ */
public static final class TimestampSearchResult { public static final class TimestampSearchResult {
@ -495,10 +468,6 @@ public abstract class BinarySearchSeeker {
/** /**
* Returns a result to signal that the target timestamp has been found at {@code * Returns a result to signal that the target timestamp has been found at {@code
* resultBytePosition}, and the seek operation can stop. * resultBytePosition}, and the seek operation can stop.
*
* <p>Note that when this value is returned from {@link
* TimestampSeeker#searchForTimestamp(ExtractorInput, long, OutputFrameHolder)}, the {@link
* OutputFrameHolder} may be updated to hold the target frame as an optimization.
*/ */
public static TimestampSearchResult targetFoundResult(long resultBytePosition) { public static TimestampSearchResult targetFoundResult(long resultBytePosition) {
return new TimestampSearchResult( return new TimestampSearchResult(

View File

@ -69,8 +69,7 @@ import java.io.IOException;
} }
@Override @Override
public TimestampSearchResult searchForTimestamp( public TimestampSearchResult searchForTimestamp(ExtractorInput input, long targetTimestamp)
ExtractorInput input, long targetTimestamp, OutputFrameHolder outputFrameHolder)
throws IOException, InterruptedException { throws IOException, InterruptedException {
long inputPosition = input.getPosition(); long inputPosition = input.getPosition();
int bytesToSearch = (int) Math.min(TIMESTAMP_SEARCH_BYTES, input.getLength() - inputPosition); int bytesToSearch = (int) Math.min(TIMESTAMP_SEARCH_BYTES, input.getLength() - inputPosition);

View File

@ -168,8 +168,7 @@ public final class PsExtractor implements Extractor {
} }
maybeOutputSeekMap(inputLength); maybeOutputSeekMap(inputLength);
if (psBinarySearchSeeker != null && psBinarySearchSeeker.isSeeking()) { if (psBinarySearchSeeker != null && psBinarySearchSeeker.isSeeking()) {
return psBinarySearchSeeker.handlePendingSeek( return psBinarySearchSeeker.handlePendingSeek(input, seekPosition);
input, seekPosition, /* outputFrameHolder= */ null);
} }
input.resetPeekPosition(); input.resetPeekPosition();

View File

@ -73,8 +73,7 @@ import java.io.IOException;
} }
@Override @Override
public TimestampSearchResult searchForTimestamp( public TimestampSearchResult searchForTimestamp(ExtractorInput input, long targetTimestamp)
ExtractorInput input, long targetTimestamp, OutputFrameHolder outputFrameHolder)
throws IOException, InterruptedException { throws IOException, InterruptedException {
long inputPosition = input.getPosition(); long inputPosition = input.getPosition();
int bytesToSearch = (int) Math.min(TIMESTAMP_SEARCH_BYTES, input.getLength() - inputPosition); int bytesToSearch = (int) Math.min(TIMESTAMP_SEARCH_BYTES, input.getLength() - inputPosition);

View File

@ -268,8 +268,7 @@ public final class TsExtractor implements Extractor {
} }
if (tsBinarySearchSeeker != null && tsBinarySearchSeeker.isSeeking()) { if (tsBinarySearchSeeker != null && tsBinarySearchSeeker.isSeeking()) {
return tsBinarySearchSeeker.handlePendingSeek( return tsBinarySearchSeeker.handlePendingSeek(input, seekPosition);
input, seekPosition, /* outputFrameHolder= */ null);
} }
} }