From fa98935c0615efc9432de96c6cd397fa22204ec1 Mon Sep 17 00:00:00 2001 From: kimvde Date: Fri, 29 Oct 2021 16:39:07 +0000 Subject: [PATCH] WavExtractor: split read stages into states This refactoring is the basis to support RF64 (see Issue: google/ExoPlayer#9543). #minor-release PiperOrigin-RevId: 406377924 --- .../extractor/wav/WavExtractor.java | 148 ++++++++++++------ .../extractor/wav/WavHeaderReader.java | 10 +- 2 files changed, 100 insertions(+), 58 deletions(-) diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavExtractor.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavExtractor.java index c1dd2a6ddc..97d98b5fcc 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavExtractor.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavExtractor.java @@ -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 com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.ParserException; @@ -34,8 +35,14 @@ import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.Util; 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. */ public final class WavExtractor implements Extractor { @@ -50,13 +57,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; } @@ -75,6 +95,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); } @@ -86,59 +107,21 @@ public final class WavExtractor implements Extractor { } @Override + @ReadResult public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException { assertInitialized(); - if (outputWriter == null) { - 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); - } - - if (header.formatType == WavUtil.TYPE_IMA_ADPCM) { - outputWriter = new ImaAdPcmOutputWriter(extractorOutput, trackOutput, header); - } else if (header.formatType == WavUtil.TYPE_ALAW) { - outputWriter = - new PassthroughOutputWriter( - extractorOutput, - trackOutput, - header, - MimeTypes.AUDIO_ALAW, - /* pcmEncoding= */ Format.NO_VALUE); - } else if (header.formatType == WavUtil.TYPE_MLAW) { - outputWriter = - new PassthroughOutputWriter( - extractorOutput, - trackOutput, - header, - MimeTypes.AUDIO_MLAW, - /* pcmEncoding= */ Format.NO_VALUE); - } else { - @C.PcmEncoding - int pcmEncoding = WavUtil.getPcmEncodingForType(header.formatType, header.bitsPerSample); - if (pcmEncoding == C.ENCODING_INVALID) { - throw ParserException.createForUnsupportedContainerFeature( - "Unsupported WAV format type: " + header.formatType); - } - outputWriter = - new PassthroughOutputWriter( - extractorOutput, trackOutput, header, MimeTypes.AUDIO_RAW, pcmEncoding); - } + 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(); } - - if (dataStartPosition == C.POSITION_UNSET) { - Pair dataBounds = WavHeaderReader.skipToData(input); - dataStartPosition = dataBounds.first.intValue(); - dataEndPosition = dataBounds.second; - outputWriter.init(dataStartPosition, dataEndPosition); - } else if (input.getPosition() == 0) { - input.skipFully(dataStartPosition); - } - - 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"}) @@ -147,6 +130,71 @@ public final class WavExtractor implements Extractor { 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); + } else if (header.formatType == WavUtil.TYPE_ALAW) { + outputWriter = + new PassthroughOutputWriter( + extractorOutput, + trackOutput, + header, + MimeTypes.AUDIO_ALAW, + /* pcmEncoding= */ Format.NO_VALUE); + } else if (header.formatType == WavUtil.TYPE_MLAW) { + outputWriter = + new PassthroughOutputWriter( + extractorOutput, + trackOutput, + header, + MimeTypes.AUDIO_MLAW, + /* pcmEncoding= */ Format.NO_VALUE); + } else { + @C.PcmEncoding + int pcmEncoding = WavUtil.getPcmEncodingForType(header.formatType, header.bitsPerSample); + if (pcmEncoding == C.ENCODING_INVALID) { + throw ParserException.createForUnsupportedContainerFeature( + "Unsupported WAV format type: " + header.formatType); + } + outputWriter = + new PassthroughOutputWriter( + extractorOutput, trackOutput, header, MimeTypes.AUDIO_RAW, pcmEncoding); + } + state = STATE_SKIPPING_TO_SAMPLE_DATA; + } + + private void skipToSampleData(ExtractorInput input) throws IOException { + Pair dataBounds = WavHeaderReader.skipToSampleData(input); + dataStartPosition = dataBounds.first.intValue(); + dataEndPosition = dataBounds.second; + 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 Assertions.checkNotNull(outputWriter).sampleData(input, bytesLeft) + ? RESULT_END_OF_INPUT + : RESULT_CONTINUE; + } + /** Writes to the extractor's output. */ private interface OutputWriter { diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeaderReader.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeaderReader.java index f794933d16..147fba9c53 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeaderReader.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeaderReader.java @@ -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 skipToData(ExtractorInput input) throws IOException { + public static Pair 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); - } + 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);