WavExtractor: split read stages into states
This refactoring is the basis to support RF64 (see Issue: google/ExoPlayer#9543). #minor-release PiperOrigin-RevId: 406377924
This commit is contained in:
parent
51aee84d13
commit
8586127d88
@ -19,6 +19,7 @@ import static java.lang.Math.max;
|
|||||||
import static java.lang.Math.min;
|
import static java.lang.Math.min;
|
||||||
|
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
|
import androidx.annotation.IntDef;
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
import androidx.media3.common.Format;
|
import androidx.media3.common.Format;
|
||||||
import androidx.media3.common.MimeTypes;
|
import androidx.media3.common.MimeTypes;
|
||||||
@ -35,8 +36,14 @@ import androidx.media3.extractor.PositionHolder;
|
|||||||
import androidx.media3.extractor.TrackOutput;
|
import androidx.media3.extractor.TrackOutput;
|
||||||
import androidx.media3.extractor.WavUtil;
|
import androidx.media3.extractor.WavUtil;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
|
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
|
||||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
|
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||||
|
|
||||||
/** Extracts data from WAV byte streams. */
|
/** Extracts data from WAV byte streams. */
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@ -52,13 +59,26 @@ public final class WavExtractor implements Extractor {
|
|||||||
/** Factory for {@link WavExtractor} instances. */
|
/** Factory for {@link WavExtractor} instances. */
|
||||||
public static final ExtractorsFactory FACTORY = () -> new Extractor[] {new WavExtractor()};
|
public static final ExtractorsFactory FACTORY = () -> new Extractor[] {new WavExtractor()};
|
||||||
|
|
||||||
|
/** Parser state. */
|
||||||
|
@Documented
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@Target({ElementType.TYPE_USE})
|
||||||
|
@IntDef({STATE_READING_HEADER, STATE_SKIPPING_TO_SAMPLE_DATA, STATE_READING_SAMPLE_DATA})
|
||||||
|
private @interface State {}
|
||||||
|
|
||||||
|
private static final int STATE_READING_HEADER = 0;
|
||||||
|
private static final int STATE_SKIPPING_TO_SAMPLE_DATA = 1;
|
||||||
|
private static final int STATE_READING_SAMPLE_DATA = 2;
|
||||||
|
|
||||||
private @MonotonicNonNull ExtractorOutput extractorOutput;
|
private @MonotonicNonNull ExtractorOutput extractorOutput;
|
||||||
private @MonotonicNonNull TrackOutput trackOutput;
|
private @MonotonicNonNull TrackOutput trackOutput;
|
||||||
|
private @State int state;
|
||||||
private @MonotonicNonNull OutputWriter outputWriter;
|
private @MonotonicNonNull OutputWriter outputWriter;
|
||||||
private int dataStartPosition;
|
private int dataStartPosition;
|
||||||
private long dataEndPosition;
|
private long dataEndPosition;
|
||||||
|
|
||||||
public WavExtractor() {
|
public WavExtractor() {
|
||||||
|
state = STATE_READING_HEADER;
|
||||||
dataStartPosition = C.POSITION_UNSET;
|
dataStartPosition = C.POSITION_UNSET;
|
||||||
dataEndPosition = C.POSITION_UNSET;
|
dataEndPosition = C.POSITION_UNSET;
|
||||||
}
|
}
|
||||||
@ -77,6 +97,7 @@ public final class WavExtractor implements Extractor {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void seek(long position, long timeUs) {
|
public void seek(long position, long timeUs) {
|
||||||
|
state = position == 0 ? STATE_READING_HEADER : STATE_READING_SAMPLE_DATA;
|
||||||
if (outputWriter != null) {
|
if (outputWriter != null) {
|
||||||
outputWriter.reset(timeUs);
|
outputWriter.reset(timeUs);
|
||||||
}
|
}
|
||||||
@ -88,15 +109,44 @@ public final class WavExtractor implements Extractor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ReadResult
|
||||||
public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException {
|
public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException {
|
||||||
assertInitialized();
|
assertInitialized();
|
||||||
if (outputWriter == null) {
|
switch (state) {
|
||||||
|
case STATE_READING_HEADER:
|
||||||
|
readHeader(input);
|
||||||
|
return Extractor.RESULT_CONTINUE;
|
||||||
|
case STATE_SKIPPING_TO_SAMPLE_DATA:
|
||||||
|
skipToSampleData(input);
|
||||||
|
return Extractor.RESULT_CONTINUE;
|
||||||
|
case STATE_READING_SAMPLE_DATA:
|
||||||
|
return readSampleData(input);
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EnsuresNonNull({"extractorOutput", "trackOutput"})
|
||||||
|
private void assertInitialized() {
|
||||||
|
Assertions.checkStateNotNull(trackOutput);
|
||||||
|
Util.castNonNull(extractorOutput);
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresNonNull({"extractorOutput", "trackOutput"})
|
||||||
|
private void readHeader(ExtractorInput input) throws IOException {
|
||||||
|
Assertions.checkState(input.getPosition() == 0);
|
||||||
|
if (dataStartPosition != C.POSITION_UNSET) {
|
||||||
|
input.skipFully(dataStartPosition);
|
||||||
|
state = STATE_READING_SAMPLE_DATA;
|
||||||
|
return;
|
||||||
|
}
|
||||||
WavHeader header = WavHeaderReader.peek(input);
|
WavHeader header = WavHeaderReader.peek(input);
|
||||||
if (header == null) {
|
if (header == null) {
|
||||||
// Should only happen if the media wasn't sniffed.
|
// Should only happen if the media wasn't sniffed.
|
||||||
throw ParserException.createForMalformedContainer(
|
throw ParserException.createForMalformedContainer(
|
||||||
"Unsupported or unrecognized wav header.", /* cause= */ null);
|
"Unsupported or unrecognized wav header.", /* cause= */ null);
|
||||||
}
|
}
|
||||||
|
input.skipFully((int) (input.getPeekPosition() - input.getPosition()));
|
||||||
|
|
||||||
if (header.formatType == WavUtil.TYPE_IMA_ADPCM) {
|
if (header.formatType == WavUtil.TYPE_IMA_ADPCM) {
|
||||||
outputWriter = new ImaAdPcmOutputWriter(extractorOutput, trackOutput, header);
|
outputWriter = new ImaAdPcmOutputWriter(extractorOutput, trackOutput, header);
|
||||||
@ -127,26 +177,24 @@ public final class WavExtractor implements Extractor {
|
|||||||
new PassthroughOutputWriter(
|
new PassthroughOutputWriter(
|
||||||
extractorOutput, trackOutput, header, MimeTypes.AUDIO_RAW, pcmEncoding);
|
extractorOutput, trackOutput, header, MimeTypes.AUDIO_RAW, pcmEncoding);
|
||||||
}
|
}
|
||||||
|
state = STATE_SKIPPING_TO_SAMPLE_DATA;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dataStartPosition == C.POSITION_UNSET) {
|
private void skipToSampleData(ExtractorInput input) throws IOException {
|
||||||
Pair<Long, Long> dataBounds = WavHeaderReader.skipToData(input);
|
Pair<Long, Long> dataBounds = WavHeaderReader.skipToSampleData(input);
|
||||||
dataStartPosition = dataBounds.first.intValue();
|
dataStartPosition = dataBounds.first.intValue();
|
||||||
dataEndPosition = dataBounds.second;
|
dataEndPosition = dataBounds.second;
|
||||||
outputWriter.init(dataStartPosition, dataEndPosition);
|
Assertions.checkNotNull(outputWriter).init(dataStartPosition, dataEndPosition);
|
||||||
} else if (input.getPosition() == 0) {
|
state = STATE_READING_SAMPLE_DATA;
|
||||||
input.skipFully(dataStartPosition);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ReadResult
|
||||||
|
private int readSampleData(ExtractorInput input) throws IOException {
|
||||||
Assertions.checkState(dataEndPosition != C.POSITION_UNSET);
|
Assertions.checkState(dataEndPosition != C.POSITION_UNSET);
|
||||||
long bytesLeft = dataEndPosition - input.getPosition();
|
long bytesLeft = dataEndPosition - input.getPosition();
|
||||||
return outputWriter.sampleData(input, bytesLeft) ? RESULT_END_OF_INPUT : RESULT_CONTINUE;
|
return Assertions.checkNotNull(outputWriter).sampleData(input, bytesLeft)
|
||||||
}
|
? RESULT_END_OF_INPUT
|
||||||
|
: RESULT_CONTINUE;
|
||||||
@EnsuresNonNull({"extractorOutput", "trackOutput"})
|
|
||||||
private void assertInitialized() {
|
|
||||||
Assertions.checkStateNotNull(trackOutput);
|
|
||||||
Util.castNonNull(extractorOutput);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Writes to the extractor's output. */
|
/** Writes to the extractor's output. */
|
||||||
|
@ -108,7 +108,7 @@ import java.io.IOException;
|
|||||||
* @throws ParserException If an error occurs parsing chunks.
|
* @throws ParserException If an error occurs parsing chunks.
|
||||||
* @throws IOException If reading from the input fails.
|
* @throws IOException If reading from the input fails.
|
||||||
*/
|
*/
|
||||||
public static Pair<Long, Long> skipToData(ExtractorInput input) throws IOException {
|
public static Pair<Long, Long> skipToSampleData(ExtractorInput input) throws IOException {
|
||||||
Assertions.checkNotNull(input);
|
Assertions.checkNotNull(input);
|
||||||
|
|
||||||
// Make sure the peek position is set to the read position before we peek the first header.
|
// Make sure the peek position is set to the read position before we peek the first header.
|
||||||
@ -118,14 +118,8 @@ import java.io.IOException;
|
|||||||
// Skip all chunks until we find the data header.
|
// Skip all chunks until we find the data header.
|
||||||
ChunkHeader chunkHeader = ChunkHeader.peek(input, scratch);
|
ChunkHeader chunkHeader = ChunkHeader.peek(input, scratch);
|
||||||
while (chunkHeader.id != WavUtil.DATA_FOURCC) {
|
while (chunkHeader.id != WavUtil.DATA_FOURCC) {
|
||||||
if (chunkHeader.id != WavUtil.RIFF_FOURCC && chunkHeader.id != WavUtil.FMT_FOURCC) {
|
|
||||||
Log.w(TAG, "Ignoring unknown WAV chunk: " + chunkHeader.id);
|
Log.w(TAG, "Ignoring unknown WAV chunk: " + chunkHeader.id);
|
||||||
}
|
|
||||||
long bytesToSkip = ChunkHeader.SIZE_IN_BYTES + chunkHeader.size;
|
long bytesToSkip = ChunkHeader.SIZE_IN_BYTES + chunkHeader.size;
|
||||||
// Override size of RIFF chunk, since it describes its size as the entire file.
|
|
||||||
if (chunkHeader.id == WavUtil.RIFF_FOURCC) {
|
|
||||||
bytesToSkip = ChunkHeader.SIZE_IN_BYTES + 4;
|
|
||||||
}
|
|
||||||
if (bytesToSkip > Integer.MAX_VALUE) {
|
if (bytesToSkip > Integer.MAX_VALUE) {
|
||||||
throw ParserException.createForUnsupportedContainerFeature(
|
throw ParserException.createForUnsupportedContainerFeature(
|
||||||
"Chunk is too large (~2GB+) to skip; id: " + chunkHeader.id);
|
"Chunk is too large (~2GB+) to skip; id: " + chunkHeader.id);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user