mirror of
https://github.com/androidx/media.git
synced 2025-05-03 21:57:46 +08:00
Merge pull request #9543 from KasemJaffer:rf64
PiperOrigin-RevId: 408816643
This commit is contained in:
commit
04517e17d4
@ -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)
|
||||
|
||||
|
@ -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;
|
||||
|
@ -31,6 +31,7 @@ import com.google.android.exoplayer2.extractor.ExtractorsFactory;
|
||||
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;
|
||||
@ -47,6 +48,8 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
/** 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
|
||||
@ -63,6 +66,7 @@ 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
|
||||
@ -70,19 +74,22 @@ public final class WavExtractor implements Extractor {
|
||||
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;
|
||||
}
|
||||
@ -120,6 +127,9 @@ public final class WavExtractor implements Extractor {
|
||||
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;
|
||||
@ -152,6 +162,11 @@ public final class WavExtractor implements Extractor {
|
||||
"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;
|
||||
}
|
||||
|
||||
@ -194,7 +209,18 @@ public final class WavExtractor implements Extractor {
|
||||
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;
|
||||
}
|
||||
|
@ -32,20 +32,19 @@ import java.io.IOException;
|
||||
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;
|
||||
}
|
||||
|
||||
@ -60,11 +59,36 @@ import java.io.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}.
|
||||
*/
|
||||
@ -104,13 +128,14 @@ import java.io.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.
|
||||
*/
|
||||
@ -125,13 +150,7 @@ import java.io.IOException;
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -53,4 +53,10 @@ public final class WavExtractorTest {
|
||||
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
testdata/src/test/assets/extractordumps/wav/sample_rf64.wav.0.dump
vendored
Normal file
36
testdata/src/test/assets/extractordumps/wav/sample_rf64.wav.0.dump
vendored
Normal file
@ -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
testdata/src/test/assets/extractordumps/wav/sample_rf64.wav.1.dump
vendored
Normal file
32
testdata/src/test/assets/extractordumps/wav/sample_rf64.wav.1.dump
vendored
Normal file
@ -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
testdata/src/test/assets/extractordumps/wav/sample_rf64.wav.2.dump
vendored
Normal file
28
testdata/src/test/assets/extractordumps/wav/sample_rf64.wav.2.dump
vendored
Normal file
@ -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
testdata/src/test/assets/extractordumps/wav/sample_rf64.wav.3.dump
vendored
Normal file
24
testdata/src/test/assets/extractordumps/wav/sample_rf64.wav.3.dump
vendored
Normal file
@ -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
|
36
testdata/src/test/assets/extractordumps/wav/sample_rf64.wav.unknown_length.dump
vendored
Normal file
36
testdata/src/test/assets/extractordumps/wav/sample_rf64.wav.unknown_length.dump
vendored
Normal file
@ -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
|
BIN
testdata/src/test/assets/media/wav/sample_rf64.wav
vendored
Normal file
BIN
testdata/src/test/assets/media/wav/sample_rf64.wav
vendored
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user