WavExtractor: split header reading state into 2 states
This refactoring is the basis to support RF64 (see Issue: google/ExoPlayer#9543). #minor-release PiperOrigin-RevId: 407301056
This commit is contained in:
parent
043a80a5ba
commit
7168f93829
@ -63,12 +63,18 @@ public final class WavExtractor implements Extractor {
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target({ElementType.TYPE_USE})
|
||||
@IntDef({STATE_READING_HEADER, STATE_SKIPPING_TO_SAMPLE_DATA, STATE_READING_SAMPLE_DATA})
|
||||
@IntDef({
|
||||
STATE_READING_FILE_TYPE,
|
||||
STATE_READING_FORMAT,
|
||||
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 static final int STATE_READING_FILE_TYPE = 0;
|
||||
private static final int STATE_READING_FORMAT = 1;
|
||||
private static final int STATE_SKIPPING_TO_SAMPLE_DATA = 2;
|
||||
private static final int STATE_READING_SAMPLE_DATA = 3;
|
||||
|
||||
private @MonotonicNonNull ExtractorOutput extractorOutput;
|
||||
private @MonotonicNonNull TrackOutput trackOutput;
|
||||
@ -78,14 +84,14 @@ public final class WavExtractor implements Extractor {
|
||||
private long dataEndPosition;
|
||||
|
||||
public WavExtractor() {
|
||||
state = STATE_READING_HEADER;
|
||||
state = STATE_READING_FILE_TYPE;
|
||||
dataStartPosition = C.POSITION_UNSET;
|
||||
dataEndPosition = C.POSITION_UNSET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sniff(ExtractorInput input) throws IOException {
|
||||
return WavHeaderReader.peek(input) != null;
|
||||
return WavHeaderReader.checkFileType(input);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -97,7 +103,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;
|
||||
state = position == 0 ? STATE_READING_FILE_TYPE : STATE_READING_SAMPLE_DATA;
|
||||
if (outputWriter != null) {
|
||||
outputWriter.reset(timeUs);
|
||||
}
|
||||
@ -113,8 +119,11 @@ public final class WavExtractor implements Extractor {
|
||||
public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException {
|
||||
assertInitialized();
|
||||
switch (state) {
|
||||
case STATE_READING_HEADER:
|
||||
readHeader(input);
|
||||
case STATE_READING_FILE_TYPE:
|
||||
readFileType(input);
|
||||
return Extractor.RESULT_CONTINUE;
|
||||
case STATE_READING_FORMAT:
|
||||
readFormat(input);
|
||||
return Extractor.RESULT_CONTINUE;
|
||||
case STATE_SKIPPING_TO_SAMPLE_DATA:
|
||||
skipToSampleData(input);
|
||||
@ -132,50 +141,54 @@ public final class WavExtractor implements Extractor {
|
||||
Util.castNonNull(extractorOutput);
|
||||
}
|
||||
|
||||
@RequiresNonNull({"extractorOutput", "trackOutput"})
|
||||
private void readHeader(ExtractorInput input) throws IOException {
|
||||
private void readFileType(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) {
|
||||
if (!WavHeaderReader.checkFileType(input)) {
|
||||
// Should only happen if the media wasn't sniffed.
|
||||
throw ParserException.createForMalformedContainer(
|
||||
"Unsupported or unrecognized wav header.", /* cause= */ null);
|
||||
"Unsupported or unrecognized wav file type.", /* cause= */ null);
|
||||
}
|
||||
input.skipFully((int) (input.getPeekPosition() - input.getPosition()));
|
||||
state = STATE_READING_FORMAT;
|
||||
}
|
||||
|
||||
if (header.formatType == WavUtil.TYPE_IMA_ADPCM) {
|
||||
outputWriter = new ImaAdPcmOutputWriter(extractorOutput, trackOutput, header);
|
||||
} else if (header.formatType == WavUtil.TYPE_ALAW) {
|
||||
@RequiresNonNull({"extractorOutput", "trackOutput"})
|
||||
private void readFormat(ExtractorInput input) throws IOException {
|
||||
WavFormat wavFormat = WavHeaderReader.readFormat(input);
|
||||
if (wavFormat.formatType == WavUtil.TYPE_IMA_ADPCM) {
|
||||
outputWriter = new ImaAdPcmOutputWriter(extractorOutput, trackOutput, wavFormat);
|
||||
} else if (wavFormat.formatType == WavUtil.TYPE_ALAW) {
|
||||
outputWriter =
|
||||
new PassthroughOutputWriter(
|
||||
extractorOutput,
|
||||
trackOutput,
|
||||
header,
|
||||
wavFormat,
|
||||
MimeTypes.AUDIO_ALAW,
|
||||
/* pcmEncoding= */ Format.NO_VALUE);
|
||||
} else if (header.formatType == WavUtil.TYPE_MLAW) {
|
||||
} else if (wavFormat.formatType == WavUtil.TYPE_MLAW) {
|
||||
outputWriter =
|
||||
new PassthroughOutputWriter(
|
||||
extractorOutput,
|
||||
trackOutput,
|
||||
header,
|
||||
wavFormat,
|
||||
MimeTypes.AUDIO_MLAW,
|
||||
/* pcmEncoding= */ Format.NO_VALUE);
|
||||
} else {
|
||||
@C.PcmEncoding
|
||||
int pcmEncoding = WavUtil.getPcmEncodingForType(header.formatType, header.bitsPerSample);
|
||||
int pcmEncoding =
|
||||
WavUtil.getPcmEncodingForType(wavFormat.formatType, wavFormat.bitsPerSample);
|
||||
if (pcmEncoding == C.ENCODING_INVALID) {
|
||||
throw ParserException.createForUnsupportedContainerFeature(
|
||||
"Unsupported WAV format type: " + header.formatType);
|
||||
"Unsupported WAV format type: " + wavFormat.formatType);
|
||||
}
|
||||
outputWriter =
|
||||
new PassthroughOutputWriter(
|
||||
extractorOutput, trackOutput, header, MimeTypes.AUDIO_RAW, pcmEncoding);
|
||||
extractorOutput, trackOutput, wavFormat, MimeTypes.AUDIO_RAW, pcmEncoding);
|
||||
}
|
||||
state = STATE_SKIPPING_TO_SAMPLE_DATA;
|
||||
}
|
||||
@ -236,7 +249,7 @@ public final class WavExtractor implements Extractor {
|
||||
|
||||
private final ExtractorOutput extractorOutput;
|
||||
private final TrackOutput trackOutput;
|
||||
private final WavHeader header;
|
||||
private final WavFormat wavFormat;
|
||||
private final Format format;
|
||||
/** The target size of each output sample, in bytes. */
|
||||
private final int targetSampleSizeBytes;
|
||||
@ -258,33 +271,33 @@ public final class WavExtractor implements Extractor {
|
||||
public PassthroughOutputWriter(
|
||||
ExtractorOutput extractorOutput,
|
||||
TrackOutput trackOutput,
|
||||
WavHeader header,
|
||||
WavFormat wavFormat,
|
||||
String mimeType,
|
||||
@C.PcmEncoding int pcmEncoding)
|
||||
throws ParserException {
|
||||
this.extractorOutput = extractorOutput;
|
||||
this.trackOutput = trackOutput;
|
||||
this.header = header;
|
||||
this.wavFormat = wavFormat;
|
||||
|
||||
int bytesPerFrame = header.numChannels * header.bitsPerSample / 8;
|
||||
// Validate the header. Blocks are expected to correspond to single frames.
|
||||
if (header.blockSize != bytesPerFrame) {
|
||||
int bytesPerFrame = wavFormat.numChannels * wavFormat.bitsPerSample / 8;
|
||||
// Validate the WAV format. Blocks are expected to correspond to single frames.
|
||||
if (wavFormat.blockSize != bytesPerFrame) {
|
||||
throw ParserException.createForMalformedContainer(
|
||||
"Expected block size: " + bytesPerFrame + "; got: " + header.blockSize,
|
||||
"Expected block size: " + bytesPerFrame + "; got: " + wavFormat.blockSize,
|
||||
/* cause= */ null);
|
||||
}
|
||||
|
||||
int constantBitrate = header.frameRateHz * bytesPerFrame * 8;
|
||||
int constantBitrate = wavFormat.frameRateHz * bytesPerFrame * 8;
|
||||
targetSampleSizeBytes =
|
||||
max(bytesPerFrame, header.frameRateHz * bytesPerFrame / TARGET_SAMPLES_PER_SECOND);
|
||||
max(bytesPerFrame, wavFormat.frameRateHz * bytesPerFrame / TARGET_SAMPLES_PER_SECOND);
|
||||
format =
|
||||
new Format.Builder()
|
||||
.setSampleMimeType(mimeType)
|
||||
.setAverageBitrate(constantBitrate)
|
||||
.setPeakBitrate(constantBitrate)
|
||||
.setMaxInputSize(targetSampleSizeBytes)
|
||||
.setChannelCount(header.numChannels)
|
||||
.setSampleRate(header.frameRateHz)
|
||||
.setChannelCount(wavFormat.numChannels)
|
||||
.setSampleRate(wavFormat.frameRateHz)
|
||||
.setPcmEncoding(pcmEncoding)
|
||||
.build();
|
||||
}
|
||||
@ -299,7 +312,7 @@ public final class WavExtractor implements Extractor {
|
||||
@Override
|
||||
public void init(int dataStartPosition, long dataEndPosition) {
|
||||
extractorOutput.seekMap(
|
||||
new WavSeekMap(header, /* framesPerBlock= */ 1, dataStartPosition, dataEndPosition));
|
||||
new WavSeekMap(wavFormat, /* framesPerBlock= */ 1, dataStartPosition, dataEndPosition));
|
||||
trackOutput.format(format);
|
||||
}
|
||||
|
||||
@ -320,13 +333,13 @@ public final class WavExtractor implements Extractor {
|
||||
// Write the corresponding sample metadata. Samples must be a whole number of frames. It's
|
||||
// possible that the number of pending output bytes is not a whole number of frames if the
|
||||
// stream ended unexpectedly.
|
||||
int bytesPerFrame = header.blockSize;
|
||||
int bytesPerFrame = wavFormat.blockSize;
|
||||
int pendingFrames = pendingOutputBytes / bytesPerFrame;
|
||||
if (pendingFrames > 0) {
|
||||
long timeUs =
|
||||
startTimeUs
|
||||
+ Util.scaleLargeTimestamp(
|
||||
outputFrameCount, C.MICROS_PER_SECOND, header.frameRateHz);
|
||||
outputFrameCount, C.MICROS_PER_SECOND, wavFormat.frameRateHz);
|
||||
int size = pendingFrames * bytesPerFrame;
|
||||
int offset = pendingOutputBytes - size;
|
||||
trackOutput.sampleMetadata(
|
||||
@ -356,7 +369,7 @@ public final class WavExtractor implements Extractor {
|
||||
|
||||
private final ExtractorOutput extractorOutput;
|
||||
private final TrackOutput trackOutput;
|
||||
private final WavHeader header;
|
||||
private final WavFormat wavFormat;
|
||||
|
||||
/** Number of frames per block of the input (yet to be decoded) data. */
|
||||
private final int framesPerBlock;
|
||||
@ -386,23 +399,26 @@ public final class WavExtractor implements Extractor {
|
||||
private long outputFrameCount;
|
||||
|
||||
public ImaAdPcmOutputWriter(
|
||||
ExtractorOutput extractorOutput, TrackOutput trackOutput, WavHeader header)
|
||||
ExtractorOutput extractorOutput, TrackOutput trackOutput, WavFormat wavFormat)
|
||||
throws ParserException {
|
||||
this.extractorOutput = extractorOutput;
|
||||
this.trackOutput = trackOutput;
|
||||
this.header = header;
|
||||
targetSampleSizeFrames = max(1, header.frameRateHz / TARGET_SAMPLES_PER_SECOND);
|
||||
this.wavFormat = wavFormat;
|
||||
targetSampleSizeFrames = max(1, wavFormat.frameRateHz / TARGET_SAMPLES_PER_SECOND);
|
||||
|
||||
ParsableByteArray scratch = new ParsableByteArray(header.extraData);
|
||||
ParsableByteArray scratch = new ParsableByteArray(wavFormat.extraData);
|
||||
scratch.readLittleEndianUnsignedShort();
|
||||
framesPerBlock = scratch.readLittleEndianUnsignedShort();
|
||||
|
||||
int numChannels = header.numChannels;
|
||||
// Validate the header. This calculation is defined in "Microsoft Multimedia Standards Update
|
||||
int numChannels = wavFormat.numChannels;
|
||||
// Validate the WAV format. This calculation is defined in "Microsoft Multimedia Standards
|
||||
// Update
|
||||
// - New Multimedia Types and Data Techniques" (1994). See the "IMA ADPCM Wave Type" and "DVI
|
||||
// ADPCM Wave Type" sections, and the calculation of wSamplesPerBlock in the latter.
|
||||
int expectedFramesPerBlock =
|
||||
(((header.blockSize - (4 * numChannels)) * 8) / (header.bitsPerSample * numChannels)) + 1;
|
||||
(((wavFormat.blockSize - (4 * numChannels)) * 8)
|
||||
/ (wavFormat.bitsPerSample * numChannels))
|
||||
+ 1;
|
||||
if (framesPerBlock != expectedFramesPerBlock) {
|
||||
throw ParserException.createForMalformedContainer(
|
||||
"Expected frames per block: " + expectedFramesPerBlock + "; got: " + framesPerBlock,
|
||||
@ -412,22 +428,22 @@ public final class WavExtractor implements Extractor {
|
||||
// Calculate the number of blocks we'll need to decode to obtain an output sample of the
|
||||
// target sample size, and allocate suitably sized buffers for input and decoded data.
|
||||
int maxBlocksToDecode = Util.ceilDivide(targetSampleSizeFrames, framesPerBlock);
|
||||
inputData = new byte[maxBlocksToDecode * header.blockSize];
|
||||
inputData = new byte[maxBlocksToDecode * wavFormat.blockSize];
|
||||
decodedData =
|
||||
new ParsableByteArray(
|
||||
maxBlocksToDecode * numOutputFramesToBytes(framesPerBlock, numChannels));
|
||||
|
||||
// Create the format. We calculate the bitrate of the data before decoding, since this is the
|
||||
// bitrate of the stream itself.
|
||||
int constantBitrate = header.frameRateHz * header.blockSize * 8 / framesPerBlock;
|
||||
int constantBitrate = wavFormat.frameRateHz * wavFormat.blockSize * 8 / framesPerBlock;
|
||||
format =
|
||||
new Format.Builder()
|
||||
.setSampleMimeType(MimeTypes.AUDIO_RAW)
|
||||
.setAverageBitrate(constantBitrate)
|
||||
.setPeakBitrate(constantBitrate)
|
||||
.setMaxInputSize(numOutputFramesToBytes(targetSampleSizeFrames, numChannels))
|
||||
.setChannelCount(header.numChannels)
|
||||
.setSampleRate(header.frameRateHz)
|
||||
.setChannelCount(wavFormat.numChannels)
|
||||
.setSampleRate(wavFormat.frameRateHz)
|
||||
.setPcmEncoding(C.ENCODING_PCM_16BIT)
|
||||
.build();
|
||||
}
|
||||
@ -443,7 +459,7 @@ public final class WavExtractor implements Extractor {
|
||||
@Override
|
||||
public void init(int dataStartPosition, long dataEndPosition) {
|
||||
extractorOutput.seekMap(
|
||||
new WavSeekMap(header, framesPerBlock, dataStartPosition, dataEndPosition));
|
||||
new WavSeekMap(wavFormat, framesPerBlock, dataStartPosition, dataEndPosition));
|
||||
trackOutput.format(format);
|
||||
}
|
||||
|
||||
@ -455,7 +471,7 @@ public final class WavExtractor implements Extractor {
|
||||
targetSampleSizeFrames - numOutputBytesToFrames(pendingOutputBytes);
|
||||
// Calculate the whole number of blocks that we need to decode to obtain this many frames.
|
||||
int blocksToDecode = Util.ceilDivide(targetFramesRemaining, framesPerBlock);
|
||||
int targetReadBytes = blocksToDecode * header.blockSize;
|
||||
int targetReadBytes = blocksToDecode * wavFormat.blockSize;
|
||||
|
||||
// Read input data until we've reached the target number of blocks, or the end of the data.
|
||||
boolean endOfSampleData = bytesLeft == 0;
|
||||
@ -469,11 +485,11 @@ public final class WavExtractor implements Extractor {
|
||||
}
|
||||
}
|
||||
|
||||
int pendingBlockCount = pendingInputBytes / header.blockSize;
|
||||
int pendingBlockCount = pendingInputBytes / wavFormat.blockSize;
|
||||
if (pendingBlockCount > 0) {
|
||||
// We have at least one whole block to decode.
|
||||
decode(inputData, pendingBlockCount, decodedData);
|
||||
pendingInputBytes -= pendingBlockCount * header.blockSize;
|
||||
pendingInputBytes -= pendingBlockCount * wavFormat.blockSize;
|
||||
|
||||
// Write all of the decoded data to the track output.
|
||||
int decodedDataSize = decodedData.limit();
|
||||
@ -501,7 +517,8 @@ public final class WavExtractor implements Extractor {
|
||||
private void writeSampleMetadata(int sampleFrames) {
|
||||
long timeUs =
|
||||
startTimeUs
|
||||
+ Util.scaleLargeTimestamp(outputFrameCount, C.MICROS_PER_SECOND, header.frameRateHz);
|
||||
+ Util.scaleLargeTimestamp(
|
||||
outputFrameCount, C.MICROS_PER_SECOND, wavFormat.frameRateHz);
|
||||
int size = numOutputFramesToBytes(sampleFrames);
|
||||
int offset = pendingOutputBytes - size;
|
||||
trackOutput.sampleMetadata(
|
||||
@ -519,7 +536,7 @@ public final class WavExtractor implements Extractor {
|
||||
*/
|
||||
private void decode(byte[] input, int blockCount, ParsableByteArray output) {
|
||||
for (int blockIndex = 0; blockIndex < blockCount; blockIndex++) {
|
||||
for (int channelIndex = 0; channelIndex < header.numChannels; channelIndex++) {
|
||||
for (int channelIndex = 0; channelIndex < wavFormat.numChannels; channelIndex++) {
|
||||
decodeBlockForChannel(input, blockIndex, channelIndex, output.getData());
|
||||
}
|
||||
}
|
||||
@ -530,8 +547,8 @@ public final class WavExtractor implements Extractor {
|
||||
|
||||
private void decodeBlockForChannel(
|
||||
byte[] input, int blockIndex, int channelIndex, byte[] output) {
|
||||
int blockSize = header.blockSize;
|
||||
int numChannels = header.numChannels;
|
||||
int blockSize = wavFormat.blockSize;
|
||||
int numChannels = wavFormat.numChannels;
|
||||
|
||||
// The input data consists for a four byte header [Ci] for each of the N channels, followed
|
||||
// by interleaved data segments [Ci-DATAj], each of which are four bytes long.
|
||||
@ -592,11 +609,11 @@ public final class WavExtractor implements Extractor {
|
||||
}
|
||||
|
||||
private int numOutputBytesToFrames(int bytes) {
|
||||
return bytes / (2 * header.numChannels);
|
||||
return bytes / (2 * wavFormat.numChannels);
|
||||
}
|
||||
|
||||
private int numOutputFramesToBytes(int frames) {
|
||||
return numOutputFramesToBytes(frames, header.numChannels);
|
||||
return numOutputFramesToBytes(frames, wavFormat.numChannels);
|
||||
}
|
||||
|
||||
private static int numOutputFramesToBytes(int frames, int numChannels) {
|
||||
|
@ -15,8 +15,8 @@
|
||||
*/
|
||||
package androidx.media3.extractor.wav;
|
||||
|
||||
/** Header for a WAV file. */
|
||||
/* package */ final class WavHeader {
|
||||
/** Format information for a WAV file. */
|
||||
/* package */ final class WavFormat {
|
||||
|
||||
/**
|
||||
* The format type. Standard format types are the "WAVE form Registration Number" constants
|
||||
@ -33,10 +33,10 @@ package androidx.media3.extractor.wav;
|
||||
public final int blockSize;
|
||||
/** Bits per sample for a single channel. */
|
||||
public final int bitsPerSample;
|
||||
/** Extra data appended to the format chunk of the header. */
|
||||
/** Extra data appended to the format chunk. */
|
||||
public final byte[] extraData;
|
||||
|
||||
public WavHeader(
|
||||
public WavFormat(
|
||||
int formatType,
|
||||
int numChannels,
|
||||
int frameRateHz,
|
@ -16,7 +16,6 @@
|
||||
package androidx.media3.extractor.wav;
|
||||
|
||||
import android.util.Pair;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.ParserException;
|
||||
import androidx.media3.common.util.Assertions;
|
||||
@ -27,45 +26,56 @@ import androidx.media3.extractor.ExtractorInput;
|
||||
import androidx.media3.extractor.WavUtil;
|
||||
import java.io.IOException;
|
||||
|
||||
/** Reads a {@code WavHeader} from an input stream; supports resuming from input failures. */
|
||||
/** Reads a WAV header from an input stream; supports resuming from input failures. */
|
||||
/* package */ final class WavHeaderReader {
|
||||
|
||||
private static final String TAG = "WavHeaderReader";
|
||||
|
||||
/**
|
||||
* Peeks and returns a {@code WavHeader}.
|
||||
* Returns whether the given {@code input} starts with a RIFF chunk header, followed by a WAVE
|
||||
* tag.
|
||||
*
|
||||
* @param input Input stream to peek the WAV header from.
|
||||
* @throws ParserException If the input file is an incorrect RIFF WAV.
|
||||
* @param input The input stream to peek from. The position should point to the start of the
|
||||
* stream.
|
||||
* @return Whether the given {@code input} starts with a RIFF chunk header, followed by a WAVE
|
||||
* tag.
|
||||
* @throws IOException If peeking from the input fails.
|
||||
* @return A new {@code WavHeader} peeked from {@code input}, or null if the input is not a
|
||||
* supported WAV format.
|
||||
*/
|
||||
@Nullable
|
||||
public static WavHeader peek(ExtractorInput input) throws IOException {
|
||||
Assertions.checkNotNull(input);
|
||||
|
||||
// Allocate a scratch buffer large enough to store the format chunk.
|
||||
ParsableByteArray scratch = new ParsableByteArray(16);
|
||||
|
||||
public static boolean checkFileType(ExtractorInput input) throws IOException {
|
||||
ParsableByteArray scratch = new ParsableByteArray(ChunkHeader.SIZE_IN_BYTES);
|
||||
// Attempt to read the RIFF chunk.
|
||||
ChunkHeader chunkHeader = ChunkHeader.peek(input, scratch);
|
||||
if (chunkHeader.id != WavUtil.RIFF_FOURCC) {
|
||||
return null;
|
||||
return false;
|
||||
}
|
||||
|
||||
input.peekFully(scratch.getData(), 0, 4);
|
||||
scratch.setPosition(0);
|
||||
int riffFormat = scratch.readInt();
|
||||
if (riffFormat != WavUtil.WAVE_FOURCC) {
|
||||
Log.e(TAG, "Unsupported RIFF format: " + riffFormat);
|
||||
return null;
|
||||
int formType = scratch.readInt();
|
||||
if (formType != WavUtil.WAVE_FOURCC) {
|
||||
Log.e(TAG, "Unsupported form type: " + formType);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads and returns a {@code WavFormat}.
|
||||
*
|
||||
* @param input Input stream to read the WAV format from. The position should point to the byte
|
||||
* following the WAVE tag.
|
||||
* @throws IOException If reading from the input fails.
|
||||
* @return A new {@code WavFormat} read from {@code input}.
|
||||
*/
|
||||
public static WavFormat readFormat(ExtractorInput input) throws IOException {
|
||||
// Allocate a scratch buffer large enough to store the format chunk.
|
||||
ParsableByteArray scratch = new ParsableByteArray(16);
|
||||
|
||||
// Skip chunks until we find the format chunk.
|
||||
chunkHeader = ChunkHeader.peek(input, scratch);
|
||||
ChunkHeader chunkHeader = ChunkHeader.peek(input, scratch);
|
||||
while (chunkHeader.id != WavUtil.FMT_FOURCC) {
|
||||
input.advancePeekPosition((int) chunkHeader.size);
|
||||
input.skipFully(ChunkHeader.SIZE_IN_BYTES + (int) chunkHeader.size);
|
||||
chunkHeader = ChunkHeader.peek(input, scratch);
|
||||
}
|
||||
|
||||
@ -88,7 +98,8 @@ import java.io.IOException;
|
||||
extraData = Util.EMPTY_BYTE_ARRAY;
|
||||
}
|
||||
|
||||
return new WavHeader(
|
||||
input.skipFully((int) (input.getPeekPosition() - input.getPosition()));
|
||||
return new WavFormat(
|
||||
audioFormatType,
|
||||
numChannels,
|
||||
frameRateHz,
|
||||
@ -109,8 +120,6 @@ import java.io.IOException;
|
||||
* @throws IOException If reading from the input fails.
|
||||
*/
|
||||
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.
|
||||
input.resetPeekPosition();
|
||||
|
||||
|
@ -22,18 +22,18 @@ import androidx.media3.extractor.SeekPoint;
|
||||
|
||||
/* package */ final class WavSeekMap implements SeekMap {
|
||||
|
||||
private final WavHeader wavHeader;
|
||||
private final WavFormat wavFormat;
|
||||
private final int framesPerBlock;
|
||||
private final long firstBlockPosition;
|
||||
private final long blockCount;
|
||||
private final long durationUs;
|
||||
|
||||
public WavSeekMap(
|
||||
WavHeader wavHeader, int framesPerBlock, long dataStartPosition, long dataEndPosition) {
|
||||
this.wavHeader = wavHeader;
|
||||
WavFormat wavFormat, int framesPerBlock, long dataStartPosition, long dataEndPosition) {
|
||||
this.wavFormat = wavFormat;
|
||||
this.framesPerBlock = framesPerBlock;
|
||||
this.firstBlockPosition = dataStartPosition;
|
||||
this.blockCount = (dataEndPosition - dataStartPosition) / wavHeader.blockSize;
|
||||
this.blockCount = (dataEndPosition - dataStartPosition) / wavFormat.blockSize;
|
||||
durationUs = blockIndexToTimeUs(blockCount);
|
||||
}
|
||||
|
||||
@ -50,17 +50,17 @@ import androidx.media3.extractor.SeekPoint;
|
||||
@Override
|
||||
public SeekPoints getSeekPoints(long timeUs) {
|
||||
// Calculate the containing block index, constraining to valid indices.
|
||||
long blockIndex = (timeUs * wavHeader.frameRateHz) / (C.MICROS_PER_SECOND * framesPerBlock);
|
||||
long blockIndex = (timeUs * wavFormat.frameRateHz) / (C.MICROS_PER_SECOND * framesPerBlock);
|
||||
blockIndex = Util.constrainValue(blockIndex, 0, blockCount - 1);
|
||||
|
||||
long seekPosition = firstBlockPosition + (blockIndex * wavHeader.blockSize);
|
||||
long seekPosition = firstBlockPosition + (blockIndex * wavFormat.blockSize);
|
||||
long seekTimeUs = blockIndexToTimeUs(blockIndex);
|
||||
SeekPoint seekPoint = new SeekPoint(seekTimeUs, seekPosition);
|
||||
if (seekTimeUs >= timeUs || blockIndex == blockCount - 1) {
|
||||
return new SeekPoints(seekPoint);
|
||||
} else {
|
||||
long secondBlockIndex = blockIndex + 1;
|
||||
long secondSeekPosition = firstBlockPosition + (secondBlockIndex * wavHeader.blockSize);
|
||||
long secondSeekPosition = firstBlockPosition + (secondBlockIndex * wavFormat.blockSize);
|
||||
long secondSeekTimeUs = blockIndexToTimeUs(secondBlockIndex);
|
||||
SeekPoint secondSeekPoint = new SeekPoint(secondSeekTimeUs, secondSeekPosition);
|
||||
return new SeekPoints(seekPoint, secondSeekPoint);
|
||||
@ -69,6 +69,6 @@ import androidx.media3.extractor.SeekPoint;
|
||||
|
||||
private long blockIndexToTimeUs(long blockIndex) {
|
||||
return Util.scaleLargeTimestamp(
|
||||
blockIndex * framesPerBlock, C.MICROS_PER_SECOND, wavHeader.frameRateHz);
|
||||
blockIndex * framesPerBlock, C.MICROS_PER_SECOND, wavFormat.frameRateHz);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user