mirror of
https://github.com/androidx/media.git
synced 2025-05-18 13:09:56 +08:00
Simplify FlacExtractor (step toward enabling nullness checking)
- Inline some unnecessarily split out helper methods - Clear ExtractorInput from FlacDecoderJni data after usage - Clean up exception handling for StreamInfo decode failures PiperOrigin-RevId: 256524955
This commit is contained in:
parent
67ad84f121
commit
98714235ad
@ -52,7 +52,7 @@ public final class FlacBinarySearchSeekerTest {
|
|||||||
|
|
||||||
FlacBinarySearchSeeker seeker =
|
FlacBinarySearchSeeker seeker =
|
||||||
new FlacBinarySearchSeeker(
|
new FlacBinarySearchSeeker(
|
||||||
decoderJni.decodeMetadata(), /* firstFramePosition= */ 0, data.length, decoderJni);
|
decoderJni.decodeStreamInfo(), /* firstFramePosition= */ 0, data.length, decoderJni);
|
||||||
|
|
||||||
SeekMap seekMap = seeker.getSeekMap();
|
SeekMap seekMap = seeker.getSeekMap();
|
||||||
assertThat(seekMap).isNotNull();
|
assertThat(seekMap).isNotNull();
|
||||||
@ -70,7 +70,7 @@ public final class FlacBinarySearchSeekerTest {
|
|||||||
decoderJni.setData(input);
|
decoderJni.setData(input);
|
||||||
FlacBinarySearchSeeker seeker =
|
FlacBinarySearchSeeker seeker =
|
||||||
new FlacBinarySearchSeeker(
|
new FlacBinarySearchSeeker(
|
||||||
decoderJni.decodeMetadata(), /* firstFramePosition= */ 0, data.length, decoderJni);
|
decoderJni.decodeStreamInfo(), /* firstFramePosition= */ 0, data.length, decoderJni);
|
||||||
|
|
||||||
seeker.setSeekTargetUs(/* timeUs= */ 1000);
|
seeker.setSeekTargetUs(/* timeUs= */ 1000);
|
||||||
assertThat(seeker.isSeeking()).isTrue();
|
assertThat(seeker.isSeeking()).isTrue();
|
||||||
|
@ -28,7 +28,7 @@ import org.junit.runner.RunWith;
|
|||||||
public class FlacExtractorTest {
|
public class FlacExtractorTest {
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() throws Exception {
|
public void setUp() {
|
||||||
if (!FlacLibrary.isAvailable()) {
|
if (!FlacLibrary.isAvailable()) {
|
||||||
fail("Flac library not available.");
|
fail("Flac library not available.");
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ package com.google.android.exoplayer2.ext.flac;
|
|||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
|
import com.google.android.exoplayer2.ParserException;
|
||||||
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
|
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
|
||||||
import com.google.android.exoplayer2.decoder.SimpleDecoder;
|
import com.google.android.exoplayer2.decoder.SimpleDecoder;
|
||||||
import com.google.android.exoplayer2.decoder.SimpleOutputBuffer;
|
import com.google.android.exoplayer2.decoder.SimpleOutputBuffer;
|
||||||
@ -59,14 +60,13 @@ import java.util.List;
|
|||||||
decoderJni.setData(ByteBuffer.wrap(initializationData.get(0)));
|
decoderJni.setData(ByteBuffer.wrap(initializationData.get(0)));
|
||||||
FlacStreamInfo streamInfo;
|
FlacStreamInfo streamInfo;
|
||||||
try {
|
try {
|
||||||
streamInfo = decoderJni.decodeMetadata();
|
streamInfo = decoderJni.decodeStreamInfo();
|
||||||
|
} catch (ParserException e) {
|
||||||
|
throw new FlacDecoderException("Failed to decode StreamInfo", e);
|
||||||
} catch (IOException | InterruptedException e) {
|
} catch (IOException | InterruptedException e) {
|
||||||
// Never happens.
|
// Never happens.
|
||||||
throw new IllegalStateException(e);
|
throw new IllegalStateException(e);
|
||||||
}
|
}
|
||||||
if (streamInfo == null) {
|
|
||||||
throw new FlacDecoderException("Metadata decoding failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
int initialInputBufferSize =
|
int initialInputBufferSize =
|
||||||
maxInputBufferSize != Format.NO_VALUE ? maxInputBufferSize : streamInfo.maxFrameSize;
|
maxInputBufferSize != Format.NO_VALUE ? maxInputBufferSize : streamInfo.maxFrameSize;
|
||||||
|
@ -17,6 +17,7 @@ package com.google.android.exoplayer2.ext.flac;
|
|||||||
|
|
||||||
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.ParserException;
|
||||||
import com.google.android.exoplayer2.extractor.ExtractorInput;
|
import com.google.android.exoplayer2.extractor.ExtractorInput;
|
||||||
import com.google.android.exoplayer2.util.FlacStreamInfo;
|
import com.google.android.exoplayer2.util.FlacStreamInfo;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
@ -48,7 +49,6 @@ import java.nio.ByteBuffer;
|
|||||||
@Nullable private byte[] tempBuffer;
|
@Nullable private byte[] tempBuffer;
|
||||||
private boolean endOfExtractorInput;
|
private boolean endOfExtractorInput;
|
||||||
|
|
||||||
@SuppressWarnings("nullness:method.invocation.invalid")
|
|
||||||
public FlacDecoderJni() throws FlacDecoderException {
|
public FlacDecoderJni() throws FlacDecoderException {
|
||||||
if (!FlacLibrary.isAvailable()) {
|
if (!FlacLibrary.isAvailable()) {
|
||||||
throw new FlacDecoderException("Failed to load decoder native libraries.");
|
throw new FlacDecoderException("Failed to load decoder native libraries.");
|
||||||
@ -60,37 +60,46 @@ import java.nio.ByteBuffer;
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets data to be parsed by libflac.
|
* Sets the data to be parsed.
|
||||||
*
|
*
|
||||||
* @param byteBufferData Source {@link ByteBuffer}.
|
* @param byteBufferData Source {@link ByteBuffer}.
|
||||||
*/
|
*/
|
||||||
public void setData(ByteBuffer byteBufferData) {
|
public void setData(ByteBuffer byteBufferData) {
|
||||||
this.byteBufferData = byteBufferData;
|
this.byteBufferData = byteBufferData;
|
||||||
this.extractorInput = null;
|
this.extractorInput = null;
|
||||||
this.tempBuffer = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets data to be parsed by libflac.
|
* Sets the data to be parsed.
|
||||||
*
|
*
|
||||||
* @param extractorInput Source {@link ExtractorInput}.
|
* @param extractorInput Source {@link ExtractorInput}.
|
||||||
*/
|
*/
|
||||||
public void setData(ExtractorInput extractorInput) {
|
public void setData(ExtractorInput extractorInput) {
|
||||||
this.byteBufferData = null;
|
this.byteBufferData = null;
|
||||||
this.extractorInput = extractorInput;
|
this.extractorInput = extractorInput;
|
||||||
if (tempBuffer == null) {
|
|
||||||
this.tempBuffer = new byte[TEMP_BUFFER_SIZE];
|
|
||||||
}
|
|
||||||
endOfExtractorInput = false;
|
endOfExtractorInput = false;
|
||||||
|
if (tempBuffer == null) {
|
||||||
|
tempBuffer = new byte[TEMP_BUFFER_SIZE];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the end of the data to be parsed has been reached, or true if no data was set.
|
||||||
|
*/
|
||||||
public boolean isEndOfData() {
|
public boolean isEndOfData() {
|
||||||
if (byteBufferData != null) {
|
if (byteBufferData != null) {
|
||||||
return byteBufferData.remaining() == 0;
|
return byteBufferData.remaining() == 0;
|
||||||
} else if (extractorInput != null) {
|
} else if (extractorInput != null) {
|
||||||
return endOfExtractorInput;
|
return endOfExtractorInput;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return true;
|
}
|
||||||
|
|
||||||
|
/** Clears the data to be parsed. */
|
||||||
|
public void clearData() {
|
||||||
|
byteBufferData = null;
|
||||||
|
extractorInput = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -99,12 +108,11 @@ import java.nio.ByteBuffer;
|
|||||||
* <p>This method blocks until at least one byte of data can be read, the end of the input is
|
* <p>This method blocks until at least one byte of data can be read, the end of the input is
|
||||||
* detected or an exception is thrown.
|
* detected or an exception is thrown.
|
||||||
*
|
*
|
||||||
* <p>This method is called from the native code.
|
|
||||||
*
|
|
||||||
* @param target A target {@link ByteBuffer} into which data should be written.
|
* @param target A target {@link ByteBuffer} into which data should be written.
|
||||||
* @return Returns the number of bytes read, or -1 on failure. If all of the data has already been
|
* @return Returns the number of bytes read, or -1 on failure. If all of the data has already been
|
||||||
* read from the source, then 0 is returned.
|
* read from the source, then 0 is returned.
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("unused") // Called from native code.
|
||||||
public int read(ByteBuffer target) throws IOException, InterruptedException {
|
public int read(ByteBuffer target) throws IOException, InterruptedException {
|
||||||
int byteCount = target.remaining();
|
int byteCount = target.remaining();
|
||||||
if (byteBufferData != null) {
|
if (byteBufferData != null) {
|
||||||
@ -135,8 +143,12 @@ import java.nio.ByteBuffer;
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Decodes and consumes the StreamInfo section from the FLAC stream. */
|
/** Decodes and consumes the StreamInfo section from the FLAC stream. */
|
||||||
public FlacStreamInfo decodeMetadata() throws IOException, InterruptedException {
|
public FlacStreamInfo decodeStreamInfo() throws IOException, InterruptedException {
|
||||||
return flacDecodeMetadata(nativeDecoderContext);
|
FlacStreamInfo streamInfo = flacDecodeMetadata(nativeDecoderContext);
|
||||||
|
if (streamInfo == null) {
|
||||||
|
throw new ParserException("Failed to decode StreamInfo");
|
||||||
|
}
|
||||||
|
return streamInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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;
|
import com.google.android.exoplayer2.extractor.BinarySearchSeeker.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;
|
||||||
@ -75,22 +75,19 @@ public final class FlacExtractor implements Extractor {
|
|||||||
private static final byte[] FLAC_SIGNATURE = {'f', 'L', 'a', 'C', 0, 0, 0, 0x22};
|
private static final byte[] FLAC_SIGNATURE = {'f', 'L', 'a', 'C', 0, 0, 0, 0x22};
|
||||||
|
|
||||||
private final Id3Peeker id3Peeker;
|
private final Id3Peeker id3Peeker;
|
||||||
private final boolean isId3MetadataDisabled;
|
private final boolean id3MetadataDisabled;
|
||||||
|
|
||||||
private FlacDecoderJni decoderJni;
|
@Nullable private FlacDecoderJni decoderJni;
|
||||||
|
@Nullable private ExtractorOutput extractorOutput;
|
||||||
|
@Nullable private TrackOutput trackOutput;
|
||||||
|
|
||||||
private ExtractorOutput extractorOutput;
|
private boolean streamInfoDecoded;
|
||||||
private TrackOutput trackOutput;
|
@Nullable private FlacStreamInfo streamInfo;
|
||||||
|
@Nullable private ParsableByteArray outputBuffer;
|
||||||
|
@Nullable private OutputFrameHolder outputFrameHolder;
|
||||||
|
|
||||||
private ParsableByteArray outputBuffer;
|
@Nullable private Metadata id3Metadata;
|
||||||
private ByteBuffer outputByteBuffer;
|
@Nullable private FlacBinarySearchSeeker binarySearchSeeker;
|
||||||
private BinarySearchSeeker.OutputFrameHolder outputFrameHolder;
|
|
||||||
private FlacStreamInfo streamInfo;
|
|
||||||
|
|
||||||
private Metadata id3Metadata;
|
|
||||||
private @Nullable FlacBinarySearchSeeker flacBinarySearchSeeker;
|
|
||||||
|
|
||||||
private boolean readPastStreamInfo;
|
|
||||||
|
|
||||||
/** Constructs an instance with flags = 0. */
|
/** Constructs an instance with flags = 0. */
|
||||||
public FlacExtractor() {
|
public FlacExtractor() {
|
||||||
@ -104,7 +101,7 @@ public final class FlacExtractor implements Extractor {
|
|||||||
*/
|
*/
|
||||||
public FlacExtractor(int flags) {
|
public FlacExtractor(int flags) {
|
||||||
id3Peeker = new Id3Peeker();
|
id3Peeker = new Id3Peeker();
|
||||||
isId3MetadataDisabled = (flags & FLAG_DISABLE_ID3_METADATA) != 0;
|
id3MetadataDisabled = (flags & FLAG_DISABLE_ID3_METADATA) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -130,48 +127,53 @@ public final class FlacExtractor implements Extractor {
|
|||||||
@Override
|
@Override
|
||||||
public int read(final ExtractorInput input, PositionHolder seekPosition)
|
public int read(final ExtractorInput input, PositionHolder seekPosition)
|
||||||
throws IOException, InterruptedException {
|
throws IOException, InterruptedException {
|
||||||
if (input.getPosition() == 0 && !isId3MetadataDisabled && id3Metadata == null) {
|
if (input.getPosition() == 0 && !id3MetadataDisabled && id3Metadata == null) {
|
||||||
id3Metadata = peekId3Data(input);
|
id3Metadata = peekId3Data(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
decoderJni.setData(input);
|
decoderJni.setData(input);
|
||||||
readPastStreamInfo(input);
|
|
||||||
|
|
||||||
if (flacBinarySearchSeeker != null && flacBinarySearchSeeker.isSeeking()) {
|
|
||||||
return handlePendingSeek(input, seekPosition);
|
|
||||||
}
|
|
||||||
|
|
||||||
long lastDecodePosition = decoderJni.getDecodePosition();
|
|
||||||
try {
|
try {
|
||||||
decoderJni.decodeSampleWithBacktrackPosition(outputByteBuffer, lastDecodePosition);
|
decodeStreamInfo(input);
|
||||||
} catch (FlacDecoderJni.FlacFrameDecodeException e) {
|
|
||||||
throw new IOException("Cannot read frame at position " + lastDecodePosition, e);
|
|
||||||
}
|
|
||||||
int outputSize = outputByteBuffer.limit();
|
|
||||||
if (outputSize == 0) {
|
|
||||||
return RESULT_END_OF_INPUT;
|
|
||||||
}
|
|
||||||
|
|
||||||
writeLastSampleToOutput(outputSize, decoderJni.getLastFrameTimestamp());
|
if (binarySearchSeeker != null && binarySearchSeeker.isSeeking()) {
|
||||||
return decoderJni.isEndOfData() ? RESULT_END_OF_INPUT : RESULT_CONTINUE;
|
return handlePendingSeek(input, seekPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteBuffer outputByteBuffer = outputFrameHolder.byteBuffer;
|
||||||
|
long lastDecodePosition = decoderJni.getDecodePosition();
|
||||||
|
try {
|
||||||
|
decoderJni.decodeSampleWithBacktrackPosition(outputByteBuffer, lastDecodePosition);
|
||||||
|
} catch (FlacDecoderJni.FlacFrameDecodeException e) {
|
||||||
|
throw new IOException("Cannot read frame at position " + lastDecodePosition, e);
|
||||||
|
}
|
||||||
|
int outputSize = outputByteBuffer.limit();
|
||||||
|
if (outputSize == 0) {
|
||||||
|
return RESULT_END_OF_INPUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
outputSample(outputBuffer, outputSize, decoderJni.getLastFrameTimestamp());
|
||||||
|
return decoderJni.isEndOfData() ? RESULT_END_OF_INPUT : RESULT_CONTINUE;
|
||||||
|
} finally {
|
||||||
|
decoderJni.clearData();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void seek(long position, long timeUs) {
|
public void seek(long position, long timeUs) {
|
||||||
if (position == 0) {
|
if (position == 0) {
|
||||||
readPastStreamInfo = false;
|
streamInfoDecoded = false;
|
||||||
}
|
}
|
||||||
if (decoderJni != null) {
|
if (decoderJni != null) {
|
||||||
decoderJni.reset(position);
|
decoderJni.reset(position);
|
||||||
}
|
}
|
||||||
if (flacBinarySearchSeeker != null) {
|
if (binarySearchSeeker != null) {
|
||||||
flacBinarySearchSeeker.setSeekTargetUs(timeUs);
|
binarySearchSeeker.setSeekTargetUs(timeUs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void release() {
|
public void release() {
|
||||||
flacBinarySearchSeeker = null;
|
binarySearchSeeker = null;
|
||||||
if (decoderJni != null) {
|
if (decoderJni != null) {
|
||||||
decoderJni.release();
|
decoderJni.release();
|
||||||
decoderJni = null;
|
decoderJni = null;
|
||||||
@ -179,16 +181,15 @@ public final class FlacExtractor implements Extractor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Peeks ID3 tag data (if present) at the beginning of the input.
|
* Peeks ID3 tag data at the beginning of the input.
|
||||||
*
|
*
|
||||||
* @return The first ID3 tag decoded into a {@link Metadata} object. May be null if ID3 tag is not
|
* @return The first ID3 tag {@link Metadata}, or null if an ID3 tag is not present in the input.
|
||||||
* present in the input.
|
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
private Metadata peekId3Data(ExtractorInput input) throws IOException, InterruptedException {
|
private Metadata peekId3Data(ExtractorInput input) throws IOException, InterruptedException {
|
||||||
input.resetPeekPosition();
|
input.resetPeekPosition();
|
||||||
Id3Decoder.FramePredicate id3FramePredicate =
|
Id3Decoder.FramePredicate id3FramePredicate =
|
||||||
isId3MetadataDisabled ? Id3Decoder.NO_FRAMES_PREDICATE : null;
|
id3MetadataDisabled ? Id3Decoder.NO_FRAMES_PREDICATE : null;
|
||||||
return id3Peeker.peekId3Data(input, id3FramePredicate);
|
return id3Peeker.peekId3Data(input, id3FramePredicate);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,68 +200,61 @@ public final class FlacExtractor implements Extractor {
|
|||||||
*/
|
*/
|
||||||
private boolean peekFlacSignature(ExtractorInput input) throws IOException, InterruptedException {
|
private boolean peekFlacSignature(ExtractorInput input) throws IOException, InterruptedException {
|
||||||
byte[] header = new byte[FLAC_SIGNATURE.length];
|
byte[] header = new byte[FLAC_SIGNATURE.length];
|
||||||
input.peekFully(header, 0, FLAC_SIGNATURE.length);
|
input.peekFully(header, /* offset= */ 0, FLAC_SIGNATURE.length);
|
||||||
return Arrays.equals(header, FLAC_SIGNATURE);
|
return Arrays.equals(header, FLAC_SIGNATURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readPastStreamInfo(ExtractorInput input) throws InterruptedException, IOException {
|
private void decodeStreamInfo(ExtractorInput input) throws InterruptedException, IOException {
|
||||||
if (readPastStreamInfo) {
|
if (streamInfoDecoded) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
FlacStreamInfo streamInfo = decodeStreamInfo(input);
|
FlacStreamInfo streamInfo;
|
||||||
readPastStreamInfo = true;
|
|
||||||
if (this.streamInfo == null) {
|
|
||||||
updateFlacStreamInfo(input, streamInfo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateFlacStreamInfo(ExtractorInput input, FlacStreamInfo streamInfo) {
|
|
||||||
this.streamInfo = streamInfo;
|
|
||||||
outputSeekMap(input, streamInfo);
|
|
||||||
outputFormat(streamInfo);
|
|
||||||
outputBuffer = new ParsableByteArray(streamInfo.maxDecodedFrameSize());
|
|
||||||
outputByteBuffer = ByteBuffer.wrap(outputBuffer.data);
|
|
||||||
outputFrameHolder = new BinarySearchSeeker.OutputFrameHolder(outputByteBuffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
private FlacStreamInfo decodeStreamInfo(ExtractorInput input)
|
|
||||||
throws InterruptedException, IOException {
|
|
||||||
try {
|
try {
|
||||||
FlacStreamInfo streamInfo = decoderJni.decodeMetadata();
|
streamInfo = decoderJni.decodeStreamInfo();
|
||||||
if (streamInfo == null) {
|
|
||||||
throw new IOException("Metadata decoding failed");
|
|
||||||
}
|
|
||||||
return streamInfo;
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
decoderJni.reset(0);
|
decoderJni.reset(/* newPosition= */ 0);
|
||||||
input.setRetryPosition(0, e);
|
input.setRetryPosition(/* position= */ 0, e);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
streamInfoDecoded = true;
|
||||||
|
if (this.streamInfo == null) {
|
||||||
|
this.streamInfo = streamInfo;
|
||||||
|
outputSeekMap(streamInfo, input.getLength());
|
||||||
|
outputFormat(streamInfo, id3MetadataDisabled ? null : id3Metadata);
|
||||||
|
outputBuffer = new ParsableByteArray(streamInfo.maxDecodedFrameSize());
|
||||||
|
outputFrameHolder = new OutputFrameHolder(ByteBuffer.wrap(outputBuffer.data));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void outputSeekMap(ExtractorInput input, FlacStreamInfo streamInfo) {
|
private int handlePendingSeek(ExtractorInput input, PositionHolder seekPosition)
|
||||||
boolean hasSeekTable = decoderJni.getSeekPosition(0) != -1;
|
throws InterruptedException, IOException {
|
||||||
SeekMap seekMap =
|
int seekResult = binarySearchSeeker.handlePendingSeek(input, seekPosition, outputFrameHolder);
|
||||||
hasSeekTable
|
ByteBuffer outputByteBuffer = outputFrameHolder.byteBuffer;
|
||||||
? new FlacSeekMap(streamInfo.durationUs(), decoderJni)
|
if (seekResult == RESULT_CONTINUE && outputByteBuffer.limit() > 0) {
|
||||||
: getSeekMapForNonSeekTableFlac(input, streamInfo);
|
outputSample(outputBuffer, outputByteBuffer.limit(), outputFrameHolder.timeUs);
|
||||||
|
}
|
||||||
|
return seekResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void outputSeekMap(FlacStreamInfo streamInfo, long inputLength) {
|
||||||
|
boolean hasSeekTable = decoderJni.getSeekPosition(/* timeUs= */ 0) != -1;
|
||||||
|
SeekMap seekMap;
|
||||||
|
if (hasSeekTable) {
|
||||||
|
seekMap = new FlacSeekMap(streamInfo.durationUs(), decoderJni);
|
||||||
|
} else if (inputLength != C.LENGTH_UNSET) {
|
||||||
|
long firstFramePosition = decoderJni.getDecodePosition();
|
||||||
|
binarySearchSeeker =
|
||||||
|
new FlacBinarySearchSeeker(streamInfo, firstFramePosition, inputLength, decoderJni);
|
||||||
|
seekMap = binarySearchSeeker.getSeekMap();
|
||||||
|
} else {
|
||||||
|
seekMap = new SeekMap.Unseekable(streamInfo.durationUs());
|
||||||
|
}
|
||||||
extractorOutput.seekMap(seekMap);
|
extractorOutput.seekMap(seekMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
private SeekMap getSeekMapForNonSeekTableFlac(ExtractorInput input, FlacStreamInfo streamInfo) {
|
private void outputFormat(FlacStreamInfo streamInfo, Metadata metadata) {
|
||||||
long inputLength = input.getLength();
|
|
||||||
if (inputLength != C.LENGTH_UNSET) {
|
|
||||||
long firstFramePosition = decoderJni.getDecodePosition();
|
|
||||||
flacBinarySearchSeeker =
|
|
||||||
new FlacBinarySearchSeeker(streamInfo, firstFramePosition, inputLength, decoderJni);
|
|
||||||
return flacBinarySearchSeeker.getSeekMap();
|
|
||||||
} else { // can't seek at all, because there's no SeekTable and the input length is unknown.
|
|
||||||
return new SeekMap.Unseekable(streamInfo.durationUs());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void outputFormat(FlacStreamInfo streamInfo) {
|
|
||||||
Format mediaFormat =
|
Format mediaFormat =
|
||||||
Format.createAudioSampleFormat(
|
Format.createAudioSampleFormat(
|
||||||
/* id= */ null,
|
/* id= */ null,
|
||||||
@ -277,25 +271,15 @@ public final class FlacExtractor implements Extractor {
|
|||||||
/* drmInitData= */ null,
|
/* drmInitData= */ null,
|
||||||
/* selectionFlags= */ 0,
|
/* selectionFlags= */ 0,
|
||||||
/* language= */ null,
|
/* language= */ null,
|
||||||
isId3MetadataDisabled ? null : id3Metadata);
|
metadata);
|
||||||
trackOutput.format(mediaFormat);
|
trackOutput.format(mediaFormat);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int handlePendingSeek(ExtractorInput input, PositionHolder seekPosition)
|
private void outputSample(ParsableByteArray sampleData, int size, long timeUs) {
|
||||||
throws InterruptedException, IOException {
|
sampleData.setPosition(0);
|
||||||
int seekResult =
|
trackOutput.sampleData(sampleData, size);
|
||||||
flacBinarySearchSeeker.handlePendingSeek(input, seekPosition, outputFrameHolder);
|
trackOutput.sampleMetadata(
|
||||||
ByteBuffer outputByteBuffer = outputFrameHolder.byteBuffer;
|
timeUs, C.BUFFER_FLAG_KEY_FRAME, size, /* offset= */ 0, /* encryptionData= */ null);
|
||||||
if (seekResult == RESULT_CONTINUE && outputByteBuffer.limit() > 0) {
|
|
||||||
writeLastSampleToOutput(outputByteBuffer.limit(), outputFrameHolder.timeUs);
|
|
||||||
}
|
|
||||||
return seekResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeLastSampleToOutput(int size, long lastSampleTimestamp) {
|
|
||||||
outputBuffer.setPosition(0);
|
|
||||||
trackOutput.sampleData(outputBuffer, size);
|
|
||||||
trackOutput.sampleMetadata(lastSampleTimestamp, C.BUFFER_FLAG_KEY_FRAME, size, 0, null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A {@link SeekMap} implementation using a SeekTable within the Flac stream. */
|
/** A {@link SeekMap} implementation using a SeekTable within the Flac stream. */
|
||||||
|
Loading…
x
Reference in New Issue
Block a user