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:
kimvde 2021-10-29 17:39:07 +01:00 committed by Ian Baker
parent 51aee84d13
commit 8586127d88
2 changed files with 100 additions and 58 deletions

View File

@ -19,6 +19,7 @@ import static java.lang.Math.max;
import static java.lang.Math.min;
import android.util.Pair;
import androidx.annotation.IntDef;
import androidx.media3.common.C;
import androidx.media3.common.Format;
import androidx.media3.common.MimeTypes;
@ -35,8 +36,14 @@ import androidx.media3.extractor.PositionHolder;
import androidx.media3.extractor.TrackOutput;
import androidx.media3.extractor.WavUtil;
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.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/** Extracts data from WAV byte streams. */
@UnstableApi
@ -52,13 +59,26 @@ public final class WavExtractor implements Extractor {
/** Factory for {@link WavExtractor} instances. */
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 TrackOutput trackOutput;
private @State int state;
private @MonotonicNonNull OutputWriter outputWriter;
private int dataStartPosition;
private long dataEndPosition;
public WavExtractor() {
state = STATE_READING_HEADER;
dataStartPosition = C.POSITION_UNSET;
dataEndPosition = C.POSITION_UNSET;
}
@ -77,6 +97,7 @@ public final class WavExtractor implements Extractor {
@Override
public void seek(long position, long timeUs) {
state = position == 0 ? STATE_READING_HEADER : STATE_READING_SAMPLE_DATA;
if (outputWriter != null) {
outputWriter.reset(timeUs);
}
@ -88,15 +109,44 @@ public final class WavExtractor implements Extractor {
}
@Override
@ReadResult
public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException {
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);
if (header == null) {
// Should only happen if the media wasn't sniffed.
throw ParserException.createForMalformedContainer(
"Unsupported or unrecognized wav header.", /* cause= */ null);
}
input.skipFully((int) (input.getPeekPosition() - input.getPosition()));
if (header.formatType == WavUtil.TYPE_IMA_ADPCM) {
outputWriter = new ImaAdPcmOutputWriter(extractorOutput, trackOutput, header);
@ -127,26 +177,24 @@ public final class WavExtractor implements Extractor {
new PassthroughOutputWriter(
extractorOutput, trackOutput, header, MimeTypes.AUDIO_RAW, pcmEncoding);
}
state = STATE_SKIPPING_TO_SAMPLE_DATA;
}
if (dataStartPosition == C.POSITION_UNSET) {
Pair<Long, Long> dataBounds = WavHeaderReader.skipToData(input);
private void skipToSampleData(ExtractorInput input) throws IOException {
Pair<Long, Long> dataBounds = WavHeaderReader.skipToSampleData(input);
dataStartPosition = dataBounds.first.intValue();
dataEndPosition = dataBounds.second;
outputWriter.init(dataStartPosition, dataEndPosition);
} else if (input.getPosition() == 0) {
input.skipFully(dataStartPosition);
Assertions.checkNotNull(outputWriter).init(dataStartPosition, dataEndPosition);
state = STATE_READING_SAMPLE_DATA;
}
@ReadResult
private int readSampleData(ExtractorInput input) throws IOException {
Assertions.checkState(dataEndPosition != C.POSITION_UNSET);
long bytesLeft = dataEndPosition - input.getPosition();
return outputWriter.sampleData(input, bytesLeft) ? RESULT_END_OF_INPUT : RESULT_CONTINUE;
}
@EnsuresNonNull({"extractorOutput", "trackOutput"})
private void assertInitialized() {
Assertions.checkStateNotNull(trackOutput);
Util.castNonNull(extractorOutput);
return Assertions.checkNotNull(outputWriter).sampleData(input, bytesLeft)
? RESULT_END_OF_INPUT
: RESULT_CONTINUE;
}
/** Writes to the extractor's output. */

View File

@ -108,7 +108,7 @@ import java.io.IOException;
* @throws ParserException If an error occurs parsing chunks.
* @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);
// 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.
ChunkHeader chunkHeader = ChunkHeader.peek(input, scratch);
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);
}
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) {
throw ParserException.createForUnsupportedContainerFeature(
"Chunk is too large (~2GB+) to skip; id: " + chunkHeader.id);