Skip to content

Commit

Permalink
Merge pull request #9543 from KasemJaffer:rf64
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 408816643
  • Loading branch information
tonihei committed Nov 11, 2021
1 parent 334fe47 commit eec8d31
Show file tree
Hide file tree
Showing 11 changed files with 237 additions and 23 deletions.
3 changes: 3 additions & 0 deletions RELEASENOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
`buildVideoRenderers()` or `buildAudioRenderers()` can access the codec
adapter factory and pass it to `MediaCodecRenderer` instances they
create.
* Extractors:
* WAV: Add support for RF64 streams
([#9543](https://github.com/google/ExoPlayer/issues/9543).

### 2.16.0 (2021-11-04)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ public final class WavUtil {
public static final int FMT_FOURCC = 0x666d7420;
/** Four character code for "data". */
public static final int DATA_FOURCC = 0x64617461;
/** Four character code for "RF64". */
public static final int RF64_FOURCC = 0x52463634;
/** Four character code for "ds64". */
public static final int DS64_FOURCC = 0x64733634;

/** WAVE type value for integer PCM audio data. */
public static final int TYPE_PCM = 0x0001;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import com.google.android.exoplayer2.extractor.PositionHolder;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.Util;
Expand All @@ -47,6 +48,8 @@
/** Extracts data from WAV byte streams. */
public final class WavExtractor implements Extractor {

private static final String TAG = "WavExtractor";

/**
* When outputting PCM data to a {@link TrackOutput}, we can choose how many frames are grouped
* into each sample, and hence each sample's duration. This is the target number of samples to
Expand All @@ -63,26 +66,30 @@ public final class WavExtractor implements Extractor {
@Target({ElementType.TYPE_USE})
@IntDef({
STATE_READING_FILE_TYPE,
STATE_READING_RF64_SAMPLE_DATA_SIZE,
STATE_READING_FORMAT,
STATE_SKIPPING_TO_SAMPLE_DATA,
STATE_READING_SAMPLE_DATA
})
private @interface State {}

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 static final int STATE_READING_RF64_SAMPLE_DATA_SIZE = 1;
private static final int STATE_READING_FORMAT = 2;
private static final int STATE_SKIPPING_TO_SAMPLE_DATA = 3;
private static final int STATE_READING_SAMPLE_DATA = 4;

private @MonotonicNonNull ExtractorOutput extractorOutput;
private @MonotonicNonNull TrackOutput trackOutput;
private @State int state;
private long rf64SampleDataSize;
private @MonotonicNonNull OutputWriter outputWriter;
private int dataStartPosition;
private long dataEndPosition;

public WavExtractor() {
state = STATE_READING_FILE_TYPE;
rf64SampleDataSize = C.LENGTH_UNSET;
dataStartPosition = C.POSITION_UNSET;
dataEndPosition = C.POSITION_UNSET;
}
Expand Down Expand Up @@ -120,6 +127,9 @@ public int read(ExtractorInput input, PositionHolder seekPosition) throws IOExce
case STATE_READING_FILE_TYPE:
readFileType(input);
return Extractor.RESULT_CONTINUE;
case STATE_READING_RF64_SAMPLE_DATA_SIZE:
readRf64SampleDataSize(input);
return Extractor.RESULT_CONTINUE;
case STATE_READING_FORMAT:
readFormat(input);
return Extractor.RESULT_CONTINUE;
Expand Down Expand Up @@ -152,6 +162,11 @@ private void readFileType(ExtractorInput input) throws IOException {
"Unsupported or unrecognized wav file type.", /* cause= */ null);
}
input.skipFully((int) (input.getPeekPosition() - input.getPosition()));
state = STATE_READING_RF64_SAMPLE_DATA_SIZE;
}

private void readRf64SampleDataSize(ExtractorInput input) throws IOException {
rf64SampleDataSize = WavHeaderReader.readRf64SampleDataSize(input);
state = STATE_READING_FORMAT;
}

Expand Down Expand Up @@ -194,7 +209,18 @@ private void readFormat(ExtractorInput input) throws IOException {
private void skipToSampleData(ExtractorInput input) throws IOException {
Pair<Long, Long> dataBounds = WavHeaderReader.skipToSampleData(input);
dataStartPosition = dataBounds.first.intValue();
dataEndPosition = dataBounds.second;
long dataSize = dataBounds.second;
if (rf64SampleDataSize != C.LENGTH_UNSET && dataSize == 0xFFFFFFFFL) {
// Following EBU - Tech 3306-2007, the data size indicated in the ds64 chunk should only be
// used if the size of the data chunk is unset.
dataSize = rf64SampleDataSize;
}
dataEndPosition = dataStartPosition + dataSize;
long inputLength = input.getLength();
if (inputLength != C.LENGTH_UNSET && dataEndPosition > inputLength) {
Log.w(TAG, "Data exceeds input length: " + dataEndPosition + ", " + inputLength);
dataEndPosition = inputLength;
}
Assertions.checkNotNull(outputWriter).init(dataStartPosition, dataEndPosition);
state = STATE_READING_SAMPLE_DATA;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,20 +32,19 @@
private static final String TAG = "WavHeaderReader";

/**
* Returns whether the given {@code input} starts with a RIFF chunk header, followed by a WAVE
* tag.
* Returns whether the given {@code input} starts with a RIFF or RF64 chunk header, followed by a
* WAVE tag.
*
* @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.
* @return Whether the given {@code input} starts with a RIFF or RF64 chunk header, followed by a
* WAVE tag.
* @throws IOException If peeking from the input fails.
*/
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) {
if (chunkHeader.id != WavUtil.RIFF_FOURCC && chunkHeader.id != WavUtil.RF64_FOURCC) {
return false;
}

Expand All @@ -60,11 +59,36 @@ public static boolean checkFileType(ExtractorInput input) throws IOException {
return true;
}

/**
* Reads the ds64 chunk defined in EBU - TECH 3306-2007, if present. If there is no such chunk,
* the input's position is left unchanged.
*
* @param input Input stream to read from. The position should point to the byte following the
* WAVE tag.
* @throws IOException If reading from the input fails.
* @return The value of the data size field in the ds64 chunk, or {@link C#LENGTH_UNSET} if there
* is no such chunk.
*/
public static long readRf64SampleDataSize(ExtractorInput input) throws IOException {
ParsableByteArray scratch = new ParsableByteArray(ChunkHeader.SIZE_IN_BYTES);
ChunkHeader chunkHeader = ChunkHeader.peek(input, scratch);
if (chunkHeader.id != WavUtil.DS64_FOURCC) {
input.resetPeekPosition();
return C.LENGTH_UNSET;
}
input.advancePeekPosition(8); // RIFF size
scratch.setPosition(0);
input.peekFully(scratch.getData(), 0, 8);
long sampleDataSize = scratch.readLittleEndianLong();
input.skipFully(ChunkHeader.SIZE_IN_BYTES + (int) chunkHeader.size);
return sampleDataSize;
}

/**
* 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.
* following the ds64 chunk if present, or to the byte following the WAVE tag otherwise.
* @throws IOException If reading from the input fails.
* @return A new {@code WavFormat} read from {@code input}.
*/
Expand Down Expand Up @@ -104,13 +128,14 @@ public static WavFormat readFormat(ExtractorInput input) throws IOException {
}

/**
* Skips to the data in the given WAV input stream, and returns its bounds. After calling, the
* input stream's position will point to the start of sample data in the WAV. If an exception is
* thrown, the input position will be left pointing to a chunk header (that may not be the data
* chunk header).
* Skips to the data in the given WAV input stream, and returns its start position and size. After
* calling, the input stream's position will point to the start of sample data in the WAV. If an
* exception is thrown, the input position will be left pointing to a chunk header (that may not
* be the data chunk header).
*
* @param input The input stream, whose read position must be pointing to a valid chunk header.
* @return The byte positions at which the data starts (inclusive) and ends (exclusive).
* @return The byte positions at which the data starts (inclusive) and the size of the data, in
* bytes.
* @throws ParserException If an error occurs parsing chunks.
* @throws IOException If reading from the input fails.
*/
Expand All @@ -125,13 +150,7 @@ public static Pair<Long, Long> skipToSampleData(ExtractorInput input) throws IOE
input.skipFully(ChunkHeader.SIZE_IN_BYTES);

long dataStartPosition = input.getPosition();
long dataEndPosition = dataStartPosition + chunkHeader.size;
long inputLength = input.getLength();
if (inputLength != C.LENGTH_UNSET && dataEndPosition > inputLength) {
Log.w(TAG, "Data exceeds input length: " + dataEndPosition + ", " + inputLength);
dataEndPosition = inputLength;
}
return Pair.create(dataStartPosition, dataEndPosition);
return Pair.create(dataStartPosition, chunkHeader.size);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,10 @@ public void sample_imaAdpcm() throws Exception {
ExtractorAsserts.assertBehavior(
WavExtractor::new, "media/wav/sample_ima_adpcm.wav", simulationConfig);
}

@Test
public void sample_rf64() throws Exception {
ExtractorAsserts.assertBehavior(
WavExtractor::new, "media/wav/sample_rf64.wav", simulationConfig);
}
}
36 changes: 36 additions & 0 deletions testdata/src/test/assets/extractordumps/wav/sample_rf64.wav.0.dump
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
seekMap:
isSeekable = true
duration = 348625
getPosition(0) = [[timeUs=0, position=80]]
getPosition(1) = [[timeUs=0, position=80], [timeUs=20, position=84]]
getPosition(174312) = [[timeUs=174291, position=33544], [timeUs=174312, position=33548]]
getPosition(348625) = [[timeUs=348604, position=67012]]
numberOfTracks = 1
track 0:
total output bytes = 66936
sample count = 4
format 0:
averageBitrate = 1536000
peakBitrate = 1536000
sampleMimeType = audio/raw
maxInputSize = 19200
channelCount = 2
sampleRate = 48000
pcmEncoding = 2
sample 0:
time = 0
flags = 1
data = length 19200, hash EF6C7C27
sample 1:
time = 100000
flags = 1
data = length 19200, hash 5AB97AFC
sample 2:
time = 200000
flags = 1
data = length 19200, hash 37920F33
sample 3:
time = 300000
flags = 1
data = length 9336, hash 135F1C30
tracksEnded = true
32 changes: 32 additions & 0 deletions testdata/src/test/assets/extractordumps/wav/sample_rf64.wav.1.dump
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
seekMap:
isSeekable = true
duration = 348625
getPosition(0) = [[timeUs=0, position=80]]
getPosition(1) = [[timeUs=0, position=80], [timeUs=20, position=84]]
getPosition(174312) = [[timeUs=174291, position=33544], [timeUs=174312, position=33548]]
getPosition(348625) = [[timeUs=348604, position=67012]]
numberOfTracks = 1
track 0:
total output bytes = 44628
sample count = 3
format 0:
averageBitrate = 1536000
peakBitrate = 1536000
sampleMimeType = audio/raw
maxInputSize = 19200
channelCount = 2
sampleRate = 48000
pcmEncoding = 2
sample 0:
time = 116208
flags = 1
data = length 19200, hash E4B962ED
sample 1:
time = 216208
flags = 1
data = length 19200, hash 4F13D6CF
sample 2:
time = 316208
flags = 1
data = length 6228, hash 3FB5F446
tracksEnded = true
28 changes: 28 additions & 0 deletions testdata/src/test/assets/extractordumps/wav/sample_rf64.wav.2.dump
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
seekMap:
isSeekable = true
duration = 348625
getPosition(0) = [[timeUs=0, position=80]]
getPosition(1) = [[timeUs=0, position=80], [timeUs=20, position=84]]
getPosition(174312) = [[timeUs=174291, position=33544], [timeUs=174312, position=33548]]
getPosition(348625) = [[timeUs=348604, position=67012]]
numberOfTracks = 1
track 0:
total output bytes = 22316
sample count = 2
format 0:
averageBitrate = 1536000
peakBitrate = 1536000
sampleMimeType = audio/raw
maxInputSize = 19200
channelCount = 2
sampleRate = 48000
pcmEncoding = 2
sample 0:
time = 232416
flags = 1
data = length 19200, hash F82E494B
sample 1:
time = 332416
flags = 1
data = length 3116, hash 93C99CFD
tracksEnded = true
24 changes: 24 additions & 0 deletions testdata/src/test/assets/extractordumps/wav/sample_rf64.wav.3.dump
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
seekMap:
isSeekable = true
duration = 348625
getPosition(0) = [[timeUs=0, position=80]]
getPosition(1) = [[timeUs=0, position=80], [timeUs=20, position=84]]
getPosition(174312) = [[timeUs=174291, position=33544], [timeUs=174312, position=33548]]
getPosition(348625) = [[timeUs=348604, position=67012]]
numberOfTracks = 1
track 0:
total output bytes = 4
sample count = 1
format 0:
averageBitrate = 1536000
peakBitrate = 1536000
sampleMimeType = audio/raw
maxInputSize = 19200
channelCount = 2
sampleRate = 48000
pcmEncoding = 2
sample 0:
time = 348625
flags = 1
data = length 4, hash FFD4C53F
tracksEnded = true
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
seekMap:
isSeekable = true
duration = 348625
getPosition(0) = [[timeUs=0, position=80]]
getPosition(1) = [[timeUs=0, position=80], [timeUs=20, position=84]]
getPosition(174312) = [[timeUs=174291, position=33544], [timeUs=174312, position=33548]]
getPosition(348625) = [[timeUs=348604, position=67012]]
numberOfTracks = 1
track 0:
total output bytes = 66936
sample count = 4
format 0:
averageBitrate = 1536000
peakBitrate = 1536000
sampleMimeType = audio/raw
maxInputSize = 19200
channelCount = 2
sampleRate = 48000
pcmEncoding = 2
sample 0:
time = 0
flags = 1
data = length 19200, hash EF6C7C27
sample 1:
time = 100000
flags = 1
data = length 19200, hash 5AB97AFC
sample 2:
time = 200000
flags = 1
data = length 19200, hash 37920F33
sample 3:
time = 300000
flags = 1
data = length 9336, hash 135F1C30
tracksEnded = true
Binary file not shown.

0 comments on commit eec8d31

Please sign in to comment.