Commit 4ea913fc by olly Committed by Oliver Woodman

Create WavExtractor.OutputWriter to handle different data formats

- Create PcmOutputWriter for PCM.
- In a future change an ImaAdPcmOutputWriter will be introduced
  for IMA ADPCM support.

PiperOrigin-RevId: 285238246
parent d62dc9dc
...@@ -35,19 +35,17 @@ import java.io.IOException; ...@@ -35,19 +35,17 @@ import java.io.IOException;
*/ */
public final class WavExtractor implements Extractor { public final class WavExtractor implements Extractor {
/** Arbitrary maximum sample size of 32KB, which is ~170ms of 16-bit stereo PCM audio at 48KHz. */
private static final int MAX_SAMPLE_SIZE = 32 * 1024;
/** 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()};
/** Arbitrary maximum input size of 32KB, which is ~170ms of 16-bit stereo PCM audio at 48KHz. */
private static final int MAX_INPUT_SIZE = 32 * 1024;
private ExtractorOutput extractorOutput; private ExtractorOutput extractorOutput;
private TrackOutput trackOutput; private TrackOutput trackOutput;
private WavHeader header; private OutputWriter outputWriter;
private WavSeekMap seekMap;
private int dataStartPosition; private int dataStartPosition;
private long dataEndPosition; private long dataEndPosition;
private int pendingBytes;
public WavExtractor() { public WavExtractor() {
dataStartPosition = C.POSITION_UNSET; dataStartPosition = C.POSITION_UNSET;
...@@ -63,13 +61,14 @@ public final class WavExtractor implements Extractor { ...@@ -63,13 +61,14 @@ public final class WavExtractor implements Extractor {
public void init(ExtractorOutput output) { public void init(ExtractorOutput output) {
extractorOutput = output; extractorOutput = output;
trackOutput = output.track(0, C.TRACK_TYPE_AUDIO); trackOutput = output.track(0, C.TRACK_TYPE_AUDIO);
header = null;
output.endTracks(); output.endTracks();
} }
@Override @Override
public void seek(long position, long timeUs) { public void seek(long position, long timeUs) {
pendingBytes = 0; if (outputWriter != null) {
outputWriter.reset();
}
} }
@Override @Override
...@@ -80,8 +79,8 @@ public final class WavExtractor implements Extractor { ...@@ -80,8 +79,8 @@ public final class WavExtractor implements Extractor {
@Override @Override
public int read(ExtractorInput input, PositionHolder seekPosition) public int read(ExtractorInput input, PositionHolder seekPosition)
throws IOException, InterruptedException { throws IOException, InterruptedException {
if (header == null) { if (outputWriter == null) {
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 new ParserException("Unsupported or unrecognized wav header."); throw new ParserException("Unsupported or unrecognized wav header.");
...@@ -92,24 +91,108 @@ public final class WavExtractor implements Extractor { ...@@ -92,24 +91,108 @@ public final class WavExtractor implements Extractor {
if (pcmEncoding == C.ENCODING_INVALID) { if (pcmEncoding == C.ENCODING_INVALID) {
throw new ParserException("Unsupported WAV format type: " + header.formatType); throw new ParserException("Unsupported WAV format type: " + header.formatType);
} }
outputWriter = new PcmOutputWriter(extractorOutput, trackOutput, header, pcmEncoding);
}
if (dataStartPosition == C.POSITION_UNSET) {
Pair<Long, Long> 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();
if (bytesLeft <= 0) {
return Extractor.RESULT_END_OF_INPUT;
}
return outputWriter.sampleData(input, bytesLeft) ? RESULT_CONTINUE : RESULT_END_OF_INPUT;
}
/** Writes to the extractor's output. */
private interface OutputWriter {
/** Resets the writer. */
void reset();
/**
* Initializes the writer.
*
* <p>Must be called once, before any calls to {@link #sampleData(ExtractorInput, long)}.
*
* @param dataStartPosition The byte position (inclusive) in the stream at which data starts.
* @param dataEndPosition The end position (exclusive) in the stream at which data ends.
* @throws ParserException If an error occurs initializing the writer.
*/
void init(int dataStartPosition, long dataEndPosition) throws ParserException;
/**
* Consumes sample data from {@code input}, writing corresponding samples to the extractor's
* output.
*
* <p>Must not be called until after {@link #init(int, long)} has been called.
*
* @param input The input from which to read.
* @param bytesLeft The number of sample data bytes left to be read from the input.
* @return True if data was consumed. False if the end of the stream has been reached.
* @throws IOException If an error occurs reading from the input.
* @throws InterruptedException If the thread has been interrupted.
*/
boolean sampleData(ExtractorInput input, long bytesLeft)
throws IOException, InterruptedException;
}
private static final class PcmOutputWriter implements OutputWriter {
// PCM specific header validation. private final ExtractorOutput extractorOutput;
private final TrackOutput trackOutput;
private final WavHeader header;
private final @C.PcmEncoding int pcmEncoding;
private WavSeekMap seekMap;
private int pendingBytes;
public PcmOutputWriter(
ExtractorOutput extractorOutput,
TrackOutput trackOutput,
WavHeader header,
@C.PcmEncoding int pcmEncoding) {
this.extractorOutput = extractorOutput;
this.trackOutput = trackOutput;
this.header = header;
this.pcmEncoding = pcmEncoding;
}
@Override
public void reset() {
pendingBytes = 0;
}
@Override
public void init(int dataStartPosition, long dataEndPosition) throws ParserException {
// Validate the header.
int expectedBytesPerFrame = header.numChannels * header.bitsPerSample / 8; int expectedBytesPerFrame = header.numChannels * header.bitsPerSample / 8;
if (header.blockAlign != expectedBytesPerFrame) { if (header.blockAlign != expectedBytesPerFrame) {
throw new ParserException( throw new ParserException(
"Unexpected bytes per frame: " "Expected block alignment: " + expectedBytesPerFrame + "; got: " + header.blockAlign);
+ header.blockAlign
+ "; expected: "
+ expectedBytesPerFrame);
} }
// Output the seek map.
seekMap =
new WavSeekMap(header, /* samplesPerBlock= */ 1, dataStartPosition, dataEndPosition);
extractorOutput.seekMap(seekMap);
// Output the format.
Format format = Format format =
Format.createAudioSampleFormat( Format.createAudioSampleFormat(
/* id= */ null, /* id= */ null,
MimeTypes.AUDIO_RAW, MimeTypes.AUDIO_RAW,
/* codecs= */ null, /* codecs= */ null,
/* bitrate= */ header.averageBytesPerSecond * 8, /* bitrate= */ header.averageBytesPerSecond * 8,
MAX_INPUT_SIZE, MAX_SAMPLE_SIZE,
header.numChannels, header.numChannels,
header.sampleRateHz, header.sampleRateHz,
pcmEncoding, pcmEncoding,
...@@ -120,30 +203,17 @@ public final class WavExtractor implements Extractor { ...@@ -120,30 +203,17 @@ public final class WavExtractor implements Extractor {
trackOutput.format(format); trackOutput.format(format);
} }
if (dataStartPosition == C.POSITION_UNSET) { @Override
Pair<Long, Long> dataBounds = WavHeaderReader.skipToData(input); public boolean sampleData(ExtractorInput input, long bytesLeft)
dataStartPosition = dataBounds.first.intValue(); throws IOException, InterruptedException {
dataEndPosition = dataBounds.second; int maxBytesToRead = (int) Math.min(MAX_SAMPLE_SIZE - pendingBytes, bytesLeft);
seekMap = int numBytesAppended = trackOutput.sampleData(input, maxBytesToRead, true);
new WavSeekMap(header, /* samplesPerBlock= */ 1, dataStartPosition, dataEndPosition); boolean wereBytesAppended = numBytesAppended != RESULT_END_OF_INPUT;
extractorOutput.seekMap(seekMap); if (wereBytesAppended) {
} else if (input.getPosition() == 0) { pendingBytes += numBytesAppended;
input.skipFully(dataStartPosition);
}
Assertions.checkState(dataEndPosition != C.POSITION_UNSET);
long bytesLeft = dataEndPosition - input.getPosition();
if (bytesLeft <= 0) {
return Extractor.RESULT_END_OF_INPUT;
}
int maxBytesToRead = (int) Math.min(MAX_INPUT_SIZE - pendingBytes, bytesLeft);
int bytesAppended = trackOutput.sampleData(input, maxBytesToRead, true);
if (bytesAppended != RESULT_END_OF_INPUT) {
pendingBytes += bytesAppended;
} }
// For PCM blockAlign is the frame size, and samples must consist of a whole number of frames. // blockAlign is the frame size, and samples must consist of a whole number of frames.
int bytesPerFrame = header.blockAlign; int bytesPerFrame = header.blockAlign;
int pendingFrames = pendingBytes / bytesPerFrame; int pendingFrames = pendingBytes / bytesPerFrame;
if (pendingFrames > 0) { if (pendingFrames > 0) {
...@@ -154,6 +224,7 @@ public final class WavExtractor implements Extractor { ...@@ -154,6 +224,7 @@ public final class WavExtractor implements Extractor {
timeUs, C.BUFFER_FLAG_KEY_FRAME, size, pendingBytes, /* encryptionData= */ null); timeUs, C.BUFFER_FLAG_KEY_FRAME, size, pendingBytes, /* encryptionData= */ null);
} }
return bytesAppended == RESULT_END_OF_INPUT ? RESULT_END_OF_INPUT : RESULT_CONTINUE; return wereBytesAppended;
}
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment