Add Java FLAC extractor
Seeking, live streams support and exposure of vorbis and ID3 data are not part of this commit. Issue: #6406 PiperOrigin-RevId: 281083332
This commit is contained in:
parent
ea342a671c
commit
dcebf93ab4
@ -1,5 +1,13 @@
|
||||
# Release notes #
|
||||
|
||||
### 2.11.2 (TBD) ###
|
||||
|
||||
* Add Java FLAC extractor
|
||||
([#6406](https://github.com/google/ExoPlayer/issues/6406)).
|
||||
This extractor does not support seeking and live streams, and does not expose
|
||||
vorbis, ID3 and picture data. If `DefaultExtractorsFactory` is used, this
|
||||
extractor is only used if the FLAC extension is not loaded.
|
||||
|
||||
### 2.11.1 (2019-12-20) ###
|
||||
|
||||
* UI: Exclude `DefaultTimeBar` region from system gesture detection
|
||||
|
@ -411,6 +411,10 @@
|
||||
"name": "Google Play (Ogg/Vorbis)",
|
||||
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/ogg/play.ogg"
|
||||
},
|
||||
{
|
||||
"name": "Google Play (FLAC)",
|
||||
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/flac/play.flac"
|
||||
},
|
||||
{
|
||||
"name": "Big Buck Bunny video (FLV)",
|
||||
"uri": "https://vod.leasewebcdn.com/bbb.flv?ri=1024&rs=150&start=0"
|
||||
|
@ -41,7 +41,7 @@ import java.nio.ByteBuffer;
|
||||
super(
|
||||
new FlacSeekTimestampConverter(streamMetadata),
|
||||
new FlacTimestampSeeker(decoderJni),
|
||||
streamMetadata.durationUs(),
|
||||
streamMetadata.getDurationUs(),
|
||||
/* floorTimePosition= */ 0,
|
||||
/* ceilingTimePosition= */ streamMetadata.totalSamples,
|
||||
/* floorBytePosition= */ firstFramePosition,
|
||||
|
@ -72,7 +72,7 @@ import java.util.List;
|
||||
int initialInputBufferSize =
|
||||
maxInputBufferSize != Format.NO_VALUE ? maxInputBufferSize : streamMetadata.maxFrameSize;
|
||||
setInitialInputBufferSize(initialInputBufferSize);
|
||||
maxOutputBufferSize = streamMetadata.maxDecodedFrameSize();
|
||||
maxOutputBufferSize = streamMetadata.getMaxDecodedFrameSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -34,6 +34,7 @@ import com.google.android.exoplayer2.extractor.TrackOutput;
|
||||
import com.google.android.exoplayer2.metadata.Metadata;
|
||||
import com.google.android.exoplayer2.metadata.id3.Id3Decoder;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import com.google.android.exoplayer2.util.FlacMetadataReader;
|
||||
import com.google.android.exoplayer2.util.FlacStreamMetadata;
|
||||
import com.google.android.exoplayer2.util.MimeTypes;
|
||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||
@ -42,7 +43,6 @@ import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
@ -72,9 +72,6 @@ public final class FlacExtractor implements Extractor {
|
||||
*/
|
||||
public static final int FLAG_DISABLE_ID3_METADATA = 1;
|
||||
|
||||
/** FLAC stream marker */
|
||||
private static final byte[] FLAC_STREAM_MARKER = {'f', 'L', 'a', 'C'};
|
||||
|
||||
private final ParsableByteArray outputBuffer;
|
||||
private final Id3Peeker id3Peeker;
|
||||
private final boolean id3MetadataDisabled;
|
||||
@ -120,10 +117,8 @@ public final class FlacExtractor implements Extractor {
|
||||
|
||||
@Override
|
||||
public boolean sniff(ExtractorInput input) throws IOException, InterruptedException {
|
||||
if (input.getPosition() == 0) {
|
||||
id3Metadata = peekId3Data(input);
|
||||
}
|
||||
return peekFlacStreamMarker(input);
|
||||
id3Metadata = peekId3Data(input);
|
||||
return FlacMetadataReader.checkAndPeekStreamMarker(input);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -230,7 +225,7 @@ public final class FlacExtractor implements Extractor {
|
||||
metadata = streamMetadata.metadata.copyWithAppendedEntriesFrom(metadata);
|
||||
}
|
||||
outputFormat(streamMetadata, metadata, trackOutput);
|
||||
outputBuffer.reset(streamMetadata.maxDecodedFrameSize());
|
||||
outputBuffer.reset(streamMetadata.getMaxDecodedFrameSize());
|
||||
outputFrameHolder = new OutputFrameHolder(ByteBuffer.wrap(outputBuffer.data));
|
||||
}
|
||||
}
|
||||
@ -251,18 +246,6 @@ public final class FlacExtractor implements Extractor {
|
||||
return seekResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Peeks from the beginning of the input to see if {@link #FLAC_STREAM_MARKER} is present.
|
||||
*
|
||||
* @return Whether the input begins with {@link #FLAC_STREAM_MARKER}.
|
||||
*/
|
||||
private static boolean peekFlacStreamMarker(ExtractorInput input)
|
||||
throws IOException, InterruptedException {
|
||||
byte[] header = new byte[FLAC_STREAM_MARKER.length];
|
||||
input.peekFully(header, /* offset= */ 0, FLAC_STREAM_MARKER.length);
|
||||
return Arrays.equals(header, FLAC_STREAM_MARKER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs a {@link SeekMap} and returns a {@link FlacBinarySearchSeeker} if one is required to
|
||||
* handle seeks.
|
||||
@ -277,14 +260,14 @@ public final class FlacExtractor implements Extractor {
|
||||
FlacBinarySearchSeeker binarySearchSeeker = null;
|
||||
SeekMap seekMap;
|
||||
if (haveSeekTable) {
|
||||
seekMap = new FlacSeekMap(streamMetadata.durationUs(), decoderJni);
|
||||
seekMap = new FlacSeekMap(streamMetadata.getDurationUs(), decoderJni);
|
||||
} else if (streamLength != C.LENGTH_UNSET) {
|
||||
long firstFramePosition = decoderJni.getDecodePosition();
|
||||
binarySearchSeeker =
|
||||
new FlacBinarySearchSeeker(streamMetadata, firstFramePosition, streamLength, decoderJni);
|
||||
seekMap = binarySearchSeeker.getSeekMap();
|
||||
} else {
|
||||
seekMap = new SeekMap.Unseekable(streamMetadata.durationUs());
|
||||
seekMap = new SeekMap.Unseekable(streamMetadata.getDurationUs());
|
||||
}
|
||||
output.seekMap(seekMap);
|
||||
return binarySearchSeeker;
|
||||
@ -297,8 +280,8 @@ public final class FlacExtractor implements Extractor {
|
||||
/* id= */ null,
|
||||
MimeTypes.AUDIO_RAW,
|
||||
/* codecs= */ null,
|
||||
streamMetadata.bitRate(),
|
||||
streamMetadata.maxDecodedFrameSize(),
|
||||
streamMetadata.getBitRate(),
|
||||
streamMetadata.getMaxDecodedFrameSize(),
|
||||
streamMetadata.channels,
|
||||
streamMetadata.sampleRate,
|
||||
getPcmEncoding(streamMetadata.bitsPerSample),
|
||||
|
@ -16,6 +16,7 @@
|
||||
package com.google.android.exoplayer2.extractor;
|
||||
|
||||
import com.google.android.exoplayer2.extractor.amr.AmrExtractor;
|
||||
import com.google.android.exoplayer2.extractor.flac.FlacExtractor;
|
||||
import com.google.android.exoplayer2.extractor.flv.FlvExtractor;
|
||||
import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor;
|
||||
import com.google.android.exoplayer2.extractor.mp3.Mp3Extractor;
|
||||
@ -55,12 +56,13 @@ import java.lang.reflect.Constructor;
|
||||
*/
|
||||
public final class DefaultExtractorsFactory implements ExtractorsFactory {
|
||||
|
||||
private static final Constructor<? extends Extractor> FLAC_EXTRACTOR_CONSTRUCTOR;
|
||||
private static final Constructor<? extends Extractor> FLAC_EXTENSION_EXTRACTOR_CONSTRUCTOR;
|
||||
|
||||
static {
|
||||
Constructor<? extends Extractor> flacExtractorConstructor = null;
|
||||
Constructor<? extends Extractor> flacExtensionExtractorConstructor = null;
|
||||
try {
|
||||
// LINT.IfChange
|
||||
flacExtractorConstructor =
|
||||
flacExtensionExtractorConstructor =
|
||||
Class.forName("com.google.android.exoplayer2.ext.flac.FlacExtractor")
|
||||
.asSubclass(Extractor.class)
|
||||
.getConstructor();
|
||||
@ -71,7 +73,7 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
|
||||
// The FLAC extension is present, but instantiation failed.
|
||||
throw new RuntimeException("Error instantiating FLAC extension", e);
|
||||
}
|
||||
FLAC_EXTRACTOR_CONSTRUCTOR = flacExtractorConstructor;
|
||||
FLAC_EXTENSION_EXTRACTOR_CONSTRUCTOR = flacExtensionExtractorConstructor;
|
||||
}
|
||||
|
||||
private boolean constantBitrateSeekingEnabled;
|
||||
@ -208,7 +210,7 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
|
||||
|
||||
@Override
|
||||
public synchronized Extractor[] createExtractors() {
|
||||
Extractor[] extractors = new Extractor[FLAC_EXTRACTOR_CONSTRUCTOR == null ? 13 : 14];
|
||||
Extractor[] extractors = new Extractor[14];
|
||||
extractors[0] = new MatroskaExtractor(matroskaFlags);
|
||||
extractors[1] = new FragmentedMp4Extractor(fragmentedMp4Flags);
|
||||
extractors[2] = new Mp4Extractor(mp4Flags);
|
||||
@ -237,13 +239,16 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
|
||||
? AmrExtractor.FLAG_ENABLE_CONSTANT_BITRATE_SEEKING
|
||||
: 0));
|
||||
extractors[12] = new Ac4Extractor();
|
||||
if (FLAC_EXTRACTOR_CONSTRUCTOR != null) {
|
||||
// Prefer the FLAC extension extractor because it supports seeking.
|
||||
if (FLAC_EXTENSION_EXTRACTOR_CONSTRUCTOR != null) {
|
||||
try {
|
||||
extractors[13] = FLAC_EXTRACTOR_CONSTRUCTOR.newInstance();
|
||||
extractors[13] = FLAC_EXTENSION_EXTRACTOR_CONSTRUCTOR.newInstance();
|
||||
} catch (Exception e) {
|
||||
// Should never happen.
|
||||
throw new IllegalStateException("Unexpected error creating FLAC extractor", e);
|
||||
}
|
||||
} else {
|
||||
extractors[13] = new FlacExtractor();
|
||||
}
|
||||
return extractors;
|
||||
}
|
||||
|
@ -0,0 +1,302 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer2.extractor.flac;
|
||||
|
||||
import static com.google.android.exoplayer2.util.Util.castNonNull;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.extractor.Extractor;
|
||||
import com.google.android.exoplayer2.extractor.ExtractorInput;
|
||||
import com.google.android.exoplayer2.extractor.ExtractorOutput;
|
||||
import com.google.android.exoplayer2.extractor.ExtractorsFactory;
|
||||
import com.google.android.exoplayer2.extractor.PositionHolder;
|
||||
import com.google.android.exoplayer2.extractor.SeekMap;
|
||||
import com.google.android.exoplayer2.extractor.TrackOutput;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import com.google.android.exoplayer2.util.FlacConstants;
|
||||
import com.google.android.exoplayer2.util.FlacFrameReader;
|
||||
import com.google.android.exoplayer2.util.FlacFrameReader.BlockSizeHolder;
|
||||
import com.google.android.exoplayer2.util.FlacMetadataReader;
|
||||
import com.google.android.exoplayer2.util.FlacMetadataReader.FirstFrameMetadata;
|
||||
import com.google.android.exoplayer2.util.FlacStreamMetadata;
|
||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
// TODO: implement seeking.
|
||||
// TODO: expose vorbis and ID3 data.
|
||||
// TODO: support live streams.
|
||||
/**
|
||||
* Extracts data from FLAC container format.
|
||||
*
|
||||
* <p>The format specification can be found at https://xiph.org/flac/format.html.
|
||||
*/
|
||||
public final class FlacExtractor implements Extractor {
|
||||
|
||||
/** Factory for {@link FlacExtractor} instances. */
|
||||
public static final ExtractorsFactory FACTORY = () -> new Extractor[] {new FlacExtractor()};
|
||||
|
||||
/** Parser state. */
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({
|
||||
STATE_READ_ID3_TAG,
|
||||
STATE_READ_STREAM_MARKER,
|
||||
STATE_READ_STREAM_INFO_BLOCK,
|
||||
STATE_SKIP_OPTIONAL_METADATA_BLOCKS,
|
||||
STATE_GET_FIRST_FRAME_METADATA,
|
||||
STATE_READ_FRAMES
|
||||
})
|
||||
private @interface State {}
|
||||
|
||||
private static final int STATE_READ_ID3_TAG = 0;
|
||||
private static final int STATE_READ_STREAM_MARKER = 1;
|
||||
private static final int STATE_READ_STREAM_INFO_BLOCK = 2;
|
||||
private static final int STATE_SKIP_OPTIONAL_METADATA_BLOCKS = 3;
|
||||
private static final int STATE_GET_FIRST_FRAME_METADATA = 4;
|
||||
private static final int STATE_READ_FRAMES = 5;
|
||||
|
||||
/** Arbitrary scratch length of 32KB, which is ~170ms of 16-bit stereo PCM audio at 48KHz. */
|
||||
private static final int SCRATCH_LENGTH = 32 * 1024;
|
||||
|
||||
/** Value of an unknown block size. */
|
||||
private static final int BLOCK_SIZE_UNKNOWN = -1;
|
||||
|
||||
private final byte[] streamMarkerAndInfoBlock;
|
||||
private final ParsableByteArray scratch;
|
||||
|
||||
private final BlockSizeHolder blockSizeHolder;
|
||||
|
||||
@MonotonicNonNull private ExtractorOutput extractorOutput;
|
||||
@MonotonicNonNull private TrackOutput trackOutput;
|
||||
|
||||
private @State int state;
|
||||
@MonotonicNonNull private FlacStreamMetadata flacStreamMetadata;
|
||||
private int minFrameSize;
|
||||
private int frameStartMarker;
|
||||
private int currentFrameBlockSizeSamples;
|
||||
private int currentFrameBytesWritten;
|
||||
private long totalSamplesWritten;
|
||||
|
||||
public FlacExtractor() {
|
||||
streamMarkerAndInfoBlock =
|
||||
new byte[FlacConstants.STREAM_MARKER_SIZE + FlacConstants.STREAM_INFO_BLOCK_SIZE];
|
||||
scratch = new ParsableByteArray(SCRATCH_LENGTH);
|
||||
blockSizeHolder = new BlockSizeHolder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sniff(ExtractorInput input) throws IOException, InterruptedException {
|
||||
FlacMetadataReader.peekId3Data(input);
|
||||
return FlacMetadataReader.checkAndPeekStreamMarker(input);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(ExtractorOutput output) {
|
||||
extractorOutput = output;
|
||||
trackOutput = output.track(/* id= */ 0, C.TRACK_TYPE_AUDIO);
|
||||
output.endTracks();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(ExtractorInput input, PositionHolder seekPosition)
|
||||
throws IOException, InterruptedException {
|
||||
switch (state) {
|
||||
case STATE_READ_ID3_TAG:
|
||||
readId3Tag(input);
|
||||
return Extractor.RESULT_CONTINUE;
|
||||
case STATE_READ_STREAM_MARKER:
|
||||
readStreamMarker(input);
|
||||
return Extractor.RESULT_CONTINUE;
|
||||
case STATE_READ_STREAM_INFO_BLOCK:
|
||||
readStreamInfoBlock(input);
|
||||
return Extractor.RESULT_CONTINUE;
|
||||
case STATE_SKIP_OPTIONAL_METADATA_BLOCKS:
|
||||
skipOptionalMetadataBlocks(input);
|
||||
return Extractor.RESULT_CONTINUE;
|
||||
case STATE_GET_FIRST_FRAME_METADATA:
|
||||
getFirstFrameMetadata(input);
|
||||
return Extractor.RESULT_CONTINUE;
|
||||
case STATE_READ_FRAMES:
|
||||
return readFrames(input);
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seek(long position, long timeUs) {
|
||||
state = STATE_READ_ID3_TAG;
|
||||
currentFrameBytesWritten = 0;
|
||||
totalSamplesWritten = 0;
|
||||
scratch.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
// Private methods.
|
||||
|
||||
private void readId3Tag(ExtractorInput input) throws IOException, InterruptedException {
|
||||
FlacMetadataReader.readId3Data(input);
|
||||
state = STATE_READ_STREAM_MARKER;
|
||||
}
|
||||
|
||||
private void readStreamMarker(ExtractorInput input) throws IOException, InterruptedException {
|
||||
FlacMetadataReader.readStreamMarker(
|
||||
input, streamMarkerAndInfoBlock, /* scratchWriteIndex= */ 0);
|
||||
state = STATE_READ_STREAM_INFO_BLOCK;
|
||||
}
|
||||
|
||||
private void readStreamInfoBlock(ExtractorInput input) throws IOException, InterruptedException {
|
||||
flacStreamMetadata =
|
||||
FlacMetadataReader.readStreamInfoBlock(
|
||||
input,
|
||||
/* scratchData= */ streamMarkerAndInfoBlock,
|
||||
/* scratchWriteIndex= */ FlacConstants.STREAM_MARKER_SIZE);
|
||||
minFrameSize = Math.max(flacStreamMetadata.minFrameSize, FlacConstants.MIN_FRAME_HEADER_SIZE);
|
||||
boolean isLastMetadataBlock =
|
||||
(streamMarkerAndInfoBlock[FlacConstants.STREAM_MARKER_SIZE] >> 7 & 1) == 1;
|
||||
castNonNull(trackOutput).format(flacStreamMetadata.getFormat(streamMarkerAndInfoBlock));
|
||||
castNonNull(extractorOutput)
|
||||
.seekMap(new SeekMap.Unseekable(flacStreamMetadata.getDurationUs()));
|
||||
|
||||
if (isLastMetadataBlock) {
|
||||
state = STATE_GET_FIRST_FRAME_METADATA;
|
||||
} else {
|
||||
state = STATE_SKIP_OPTIONAL_METADATA_BLOCKS;
|
||||
}
|
||||
}
|
||||
|
||||
private void skipOptionalMetadataBlocks(ExtractorInput input)
|
||||
throws IOException, InterruptedException {
|
||||
FlacMetadataReader.skipMetadataBlocks(input);
|
||||
state = STATE_GET_FIRST_FRAME_METADATA;
|
||||
}
|
||||
|
||||
private void getFirstFrameMetadata(ExtractorInput input)
|
||||
throws IOException, InterruptedException {
|
||||
FirstFrameMetadata firstFrameMetadata = FlacMetadataReader.getFirstFrameMetadata(input);
|
||||
frameStartMarker = firstFrameMetadata.frameStartMarker;
|
||||
currentFrameBlockSizeSamples = firstFrameMetadata.blockSizeSamples;
|
||||
|
||||
state = STATE_READ_FRAMES;
|
||||
}
|
||||
|
||||
// TODO: consider sending bytes within min frame size directly from the input to the sample queue
|
||||
// to avoid unnecessary copies in scratch.
|
||||
private int readFrames(ExtractorInput input) throws IOException, InterruptedException {
|
||||
Assertions.checkNotNull(trackOutput);
|
||||
Assertions.checkNotNull(flacStreamMetadata);
|
||||
|
||||
// Copy more bytes into the scratch.
|
||||
int currentLimit = scratch.limit();
|
||||
int bytesRead =
|
||||
input.read(
|
||||
scratch.data, /* offset= */ currentLimit, /* length= */ SCRATCH_LENGTH - currentLimit);
|
||||
boolean foundEndOfInput = bytesRead == C.RESULT_END_OF_INPUT;
|
||||
if (!foundEndOfInput) {
|
||||
scratch.setLimit(currentLimit + bytesRead);
|
||||
} else if (scratch.bytesLeft() == 0) {
|
||||
return C.RESULT_END_OF_INPUT;
|
||||
}
|
||||
|
||||
// Search for a frame.
|
||||
int positionBeforeFindingAFrame = scratch.getPosition();
|
||||
|
||||
// Skip frame search on the bytes within the minimum frame size.
|
||||
if (currentFrameBytesWritten < minFrameSize) {
|
||||
scratch.skipBytes(Math.min(minFrameSize, scratch.bytesLeft()));
|
||||
}
|
||||
|
||||
int nextFrameBlockSizeSamples = findFrame(scratch, foundEndOfInput);
|
||||
int numberOfFrameBytes = scratch.getPosition() - positionBeforeFindingAFrame;
|
||||
scratch.setPosition(positionBeforeFindingAFrame);
|
||||
trackOutput.sampleData(scratch, numberOfFrameBytes);
|
||||
currentFrameBytesWritten += numberOfFrameBytes;
|
||||
|
||||
// Frame found.
|
||||
if (nextFrameBlockSizeSamples != BLOCK_SIZE_UNKNOWN || foundEndOfInput) {
|
||||
long timeUs = getTimeUs(totalSamplesWritten, flacStreamMetadata.sampleRate);
|
||||
trackOutput.sampleMetadata(
|
||||
timeUs,
|
||||
C.BUFFER_FLAG_KEY_FRAME,
|
||||
currentFrameBytesWritten,
|
||||
/* offset= */ 0,
|
||||
/* encryptionData= */ null);
|
||||
totalSamplesWritten += currentFrameBlockSizeSamples;
|
||||
currentFrameBytesWritten = 0;
|
||||
currentFrameBlockSizeSamples = nextFrameBlockSizeSamples;
|
||||
}
|
||||
|
||||
if (scratch.bytesLeft() < FlacConstants.MAX_FRAME_HEADER_SIZE) {
|
||||
// The next frame header may not fit in the rest of the scratch, so put the trailing bytes at
|
||||
// the start of the scratch, and reset the position and limit.
|
||||
System.arraycopy(
|
||||
scratch.data, scratch.getPosition(), scratch.data, /* destPos= */ 0, scratch.bytesLeft());
|
||||
scratch.reset(scratch.bytesLeft());
|
||||
}
|
||||
|
||||
return Extractor.RESULT_CONTINUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for the start of a frame in {@code scratch}.
|
||||
*
|
||||
* <ul>
|
||||
* <li>If the search is successful, the position is set to the start of the found frame.
|
||||
* <li>Otherwise, the position is set to the first unsearched byte.
|
||||
* </ul>
|
||||
*
|
||||
* @param scratch The array to be searched.
|
||||
* @param foundEndOfInput If the end of input was met when filling in the {@code scratch}.
|
||||
* @return The block size of the frame found, or {@code BLOCK_SIZE_UNKNOWN} if the search was not
|
||||
* successful.
|
||||
*/
|
||||
private int findFrame(ParsableByteArray scratch, boolean foundEndOfInput) {
|
||||
Assertions.checkNotNull(flacStreamMetadata);
|
||||
|
||||
int frameOffset = scratch.getPosition();
|
||||
while (frameOffset <= scratch.limit() - FlacConstants.MAX_FRAME_HEADER_SIZE) {
|
||||
scratch.setPosition(frameOffset);
|
||||
if (FlacFrameReader.checkAndReadFrameHeader(
|
||||
scratch, flacStreamMetadata, frameStartMarker, blockSizeHolder)) {
|
||||
scratch.setPosition(frameOffset);
|
||||
return blockSizeHolder.blockSizeSamples;
|
||||
}
|
||||
frameOffset++;
|
||||
}
|
||||
|
||||
if (foundEndOfInput) {
|
||||
// Reached the end of the file. Assume it's the end of the frame.
|
||||
scratch.setPosition(scratch.limit());
|
||||
} else {
|
||||
scratch.setPosition(frameOffset);
|
||||
}
|
||||
|
||||
return BLOCK_SIZE_UNKNOWN;
|
||||
}
|
||||
|
||||
private long getTimeUs(long numSamples, int sampleRate) {
|
||||
return numSamples * C.MICROS_PER_SECOND / sampleRate;
|
||||
}
|
||||
}
|
@ -15,18 +15,14 @@
|
||||
*/
|
||||
package com.google.android.exoplayer2.extractor.ogg;
|
||||
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.extractor.ExtractorInput;
|
||||
import com.google.android.exoplayer2.extractor.SeekMap;
|
||||
import com.google.android.exoplayer2.extractor.SeekPoint;
|
||||
import com.google.android.exoplayer2.util.FlacStreamMetadata;
|
||||
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.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* {@link StreamReader} to extract Flac data out of Ogg byte stream.
|
||||
@ -72,24 +68,8 @@ import java.util.List;
|
||||
byte[] data = packet.data;
|
||||
if (streamMetadata == null) {
|
||||
streamMetadata = new FlacStreamMetadata(data, 17);
|
||||
int maxInputSize =
|
||||
streamMetadata.maxFrameSize == 0 ? Format.NO_VALUE : streamMetadata.maxFrameSize;
|
||||
byte[] metadata = Arrays.copyOfRange(data, 9, packet.limit());
|
||||
metadata[4] = (byte) 0x80; // Set the last metadata block flag, ignore the other blocks
|
||||
List<byte[]> initializationData = Collections.singletonList(metadata);
|
||||
setupData.format =
|
||||
Format.createAudioSampleFormat(
|
||||
/* id= */ null,
|
||||
MimeTypes.AUDIO_FLAC,
|
||||
/* codecs= */ null,
|
||||
streamMetadata.bitRate(),
|
||||
maxInputSize,
|
||||
streamMetadata.channels,
|
||||
streamMetadata.sampleRate,
|
||||
initializationData,
|
||||
/* drmInitData= */ null,
|
||||
/* selectionFlags= */ 0,
|
||||
/* language= */ null);
|
||||
setupData.format = streamMetadata.getFormat(metadata);
|
||||
} else if ((data[0] & 0x7F) == SEEKTABLE_PACKET_TYPE) {
|
||||
flacOggSeeker = new FlacOggSeeker();
|
||||
flacOggSeeker.parseSeekTable(packet);
|
||||
@ -220,7 +200,7 @@ import java.util.List;
|
||||
|
||||
@Override
|
||||
public long getDurationUs() {
|
||||
return streamMetadata.durationUs();
|
||||
return streamMetadata.getDurationUs();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer2.util;
|
||||
|
||||
/** Defines constants used by the FLAC extractor. */
|
||||
public final class FlacConstants {
|
||||
|
||||
/** Size of the FLAC stream marker in bytes. */
|
||||
public static final int STREAM_MARKER_SIZE = 4;
|
||||
/** Size of the header of a FLAC metadata block in bytes. */
|
||||
public static final int METADATA_BLOCK_HEADER_SIZE = 4;
|
||||
/** Size of the FLAC stream info block (header included) in bytes. */
|
||||
public static final int STREAM_INFO_BLOCK_SIZE = 38;
|
||||
/** Minimum size of a FLAC frame header in bytes. */
|
||||
public static final int MIN_FRAME_HEADER_SIZE = 6;
|
||||
/** Maximum size of a FLAC frame header in bytes. */
|
||||
public static final int MAX_FRAME_HEADER_SIZE = 16;
|
||||
|
||||
private FlacConstants() {}
|
||||
}
|
@ -0,0 +1,257 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer2.util;
|
||||
|
||||
/** Reads and peeks FLAC frame elements. */
|
||||
public final class FlacFrameReader {
|
||||
|
||||
/** Holds a frame block size. */
|
||||
public static final class BlockSizeHolder {
|
||||
/** The block size in samples. */
|
||||
public int blockSizeSamples;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given FLAC frame header is valid and, if so, reads it and writes the frame
|
||||
* block size in {@code blockSizeHolder}.
|
||||
*
|
||||
* <p>If the header is valid, the position of {@code scratch} is moved to the byte following it.
|
||||
* Otherwise, there is no guarantee on the position.
|
||||
*
|
||||
* @param scratch The array to read the data from, whose position must correspond to the frame
|
||||
* header.
|
||||
* @param flacStreamMetadata The stream metadata.
|
||||
* @param frameStartMarker The frame start marker of the stream.
|
||||
* @param blockSizeHolder The holder used to contain the block size.
|
||||
* @return Whether the frame header is valid.
|
||||
*/
|
||||
public static boolean checkAndReadFrameHeader(
|
||||
ParsableByteArray scratch,
|
||||
FlacStreamMetadata flacStreamMetadata,
|
||||
int frameStartMarker,
|
||||
BlockSizeHolder blockSizeHolder) {
|
||||
int frameStartPosition = scratch.getPosition();
|
||||
|
||||
long frameHeaderBytes = scratch.readUnsignedInt();
|
||||
if (frameHeaderBytes >>> 16 != frameStartMarker) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int blockSizeKey = (int) (frameHeaderBytes >> 12 & 0xF);
|
||||
int sampleRateKey = (int) (frameHeaderBytes >> 8 & 0xF);
|
||||
int channelAssignmentKey = (int) (frameHeaderBytes >> 4 & 0xF);
|
||||
int bitsPerSampleKey = (int) (frameHeaderBytes >> 1 & 0x7);
|
||||
boolean reservedBit = (frameHeaderBytes & 1) == 1;
|
||||
return checkChannelAssignment(channelAssignmentKey, flacStreamMetadata)
|
||||
&& checkBitsPerSample(bitsPerSampleKey, flacStreamMetadata)
|
||||
&& !reservedBit
|
||||
&& checkAndReadUtf8Data(scratch)
|
||||
&& checkAndReadBlockSizeSamples(scratch, flacStreamMetadata, blockSizeKey, blockSizeHolder)
|
||||
&& checkAndReadSampleRate(scratch, flacStreamMetadata, sampleRateKey)
|
||||
&& checkAndReadCrc(scratch, frameStartPosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the block size of the given frame.
|
||||
*
|
||||
* <p>If no exception is thrown, the position of {@code scratch} is left unchanged. Otherwise,
|
||||
* there is no guarantee on the position.
|
||||
*
|
||||
* @param scratch The array to get the data from, whose position must correspond to the start of a
|
||||
* frame.
|
||||
* @return The block size in samples, or -1 if the block size is invalid.
|
||||
*/
|
||||
public static int getFrameBlockSizeSamples(ParsableByteArray scratch) {
|
||||
int blockSizeKey = (scratch.data[2] & 0xFF) >> 4;
|
||||
if (blockSizeKey < 6 || blockSizeKey > 7) {
|
||||
return readFrameBlockSizeSamplesFromKey(scratch, blockSizeKey);
|
||||
}
|
||||
scratch.skipBytes(4);
|
||||
scratch.readUtf8EncodedLong();
|
||||
int blockSizeSamples = readFrameBlockSizeSamplesFromKey(scratch, blockSizeKey);
|
||||
scratch.setPosition(0);
|
||||
return blockSizeSamples;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the given block size.
|
||||
*
|
||||
* @param scratch The array to read the data from, whose position must correspond to the block
|
||||
* size bits.
|
||||
* @param blockSizeKey The key in the block size lookup table.
|
||||
* @return The block size in samples.
|
||||
*/
|
||||
public static int readFrameBlockSizeSamplesFromKey(ParsableByteArray scratch, int blockSizeKey) {
|
||||
switch (blockSizeKey) {
|
||||
case 1:
|
||||
return 192;
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
case 5:
|
||||
return 576 << (blockSizeKey - 2);
|
||||
case 6:
|
||||
return scratch.readUnsignedByte() + 1;
|
||||
case 7:
|
||||
return scratch.readUnsignedShort() + 1;
|
||||
case 8:
|
||||
case 9:
|
||||
case 10:
|
||||
case 11:
|
||||
case 12:
|
||||
case 13:
|
||||
case 14:
|
||||
case 15:
|
||||
return 256 << (blockSizeKey - 8);
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given channel assignment is valid.
|
||||
*
|
||||
* @param channelAssignmentKey The channel assignment lookup key.
|
||||
* @param flacStreamMetadata The stream metadata.
|
||||
* @return Whether the channel assignment is valid.
|
||||
*/
|
||||
private static boolean checkChannelAssignment(
|
||||
int channelAssignmentKey, FlacStreamMetadata flacStreamMetadata) {
|
||||
if (channelAssignmentKey <= 7) {
|
||||
return channelAssignmentKey == flacStreamMetadata.channels - 1;
|
||||
} else if (channelAssignmentKey <= 10) {
|
||||
return flacStreamMetadata.channels == 2;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given number of bits per sample is valid.
|
||||
*
|
||||
* @param bitsPerSampleKey The bits per sample lookup key.
|
||||
* @param flacStreamMetadata The stream metadata.
|
||||
* @return Whether the number of bits per sample is valid.
|
||||
*/
|
||||
private static boolean checkBitsPerSample(
|
||||
int bitsPerSampleKey, FlacStreamMetadata flacStreamMetadata) {
|
||||
if (bitsPerSampleKey == 0) {
|
||||
return true;
|
||||
}
|
||||
return bitsPerSampleKey == flacStreamMetadata.bitsPerSampleLookupKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given UTF-8 data is valid and, if so, reads it.
|
||||
*
|
||||
* <p>If the UTF-8 data is valid, the position of {@code scratch} is moved to the byte following
|
||||
* it. Otherwise, there is no guarantee on the position.
|
||||
*
|
||||
* @param scratch The array to read the data from, whose position must correspond to the UTF-8
|
||||
* data.
|
||||
* @return Whether the UTF-8 data is valid.
|
||||
*/
|
||||
private static boolean checkAndReadUtf8Data(ParsableByteArray scratch) {
|
||||
try {
|
||||
scratch.readUtf8EncodedLong();
|
||||
} catch (NumberFormatException e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given frame block size key and block size bits are valid and, if so, reads
|
||||
* the block size bits and writes the block size in {@code blockSizeHolder}.
|
||||
*
|
||||
* <p>If the block size is valid, the position of {@code scratch} is moved to the byte following
|
||||
* the block size bits. Otherwise, there is no guarantee on the position.
|
||||
*
|
||||
* @param scratch The array to read the data from, whose position must correspond to the block
|
||||
* size bits.
|
||||
* @param flacStreamMetadata The stream metadata.
|
||||
* @param blockSizeKey The key in the block size lookup table.
|
||||
* @param blockSizeHolder The holder used to contain the block size.
|
||||
* @return Whether the block size is valid.
|
||||
*/
|
||||
private static boolean checkAndReadBlockSizeSamples(
|
||||
ParsableByteArray scratch,
|
||||
FlacStreamMetadata flacStreamMetadata,
|
||||
int blockSizeKey,
|
||||
BlockSizeHolder blockSizeHolder) {
|
||||
int blockSizeSamples = readFrameBlockSizeSamplesFromKey(scratch, blockSizeKey);
|
||||
if (blockSizeSamples == -1 || blockSizeSamples > flacStreamMetadata.maxBlockSizeSamples) {
|
||||
return false;
|
||||
}
|
||||
blockSizeHolder.blockSizeSamples = blockSizeSamples;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given sample rate key and sample rate bits are valid and, if so, reads the
|
||||
* sample rate bits.
|
||||
*
|
||||
* <p>If the sample rate is valid, the position of {@code scratch} is moved to the byte following
|
||||
* the sample rate bits. Otherwise, there is no guarantee on the position.
|
||||
*
|
||||
* @param scratch The array to read the data from, whose position must indicate the sample rate
|
||||
* bits.
|
||||
* @param flacStreamMetadata The stream metadata.
|
||||
* @param sampleRateKey The key in the sample rate lookup table.
|
||||
* @return Whether the sample rate is valid.
|
||||
*/
|
||||
private static boolean checkAndReadSampleRate(
|
||||
ParsableByteArray scratch, FlacStreamMetadata flacStreamMetadata, int sampleRateKey) {
|
||||
int expectedSampleRate = flacStreamMetadata.sampleRate;
|
||||
if (sampleRateKey == 0) {
|
||||
return true;
|
||||
} else if (sampleRateKey <= 11) {
|
||||
return sampleRateKey == flacStreamMetadata.sampleRateLookupKey;
|
||||
} else if (sampleRateKey == 12) {
|
||||
return scratch.readUnsignedByte() * 1000 == expectedSampleRate;
|
||||
} else if (sampleRateKey <= 14) {
|
||||
int sampleRate = scratch.readUnsignedShort();
|
||||
if (sampleRateKey == 14) {
|
||||
sampleRate *= 10;
|
||||
}
|
||||
return sampleRate == expectedSampleRate;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given CRC is valid and, if so, reads it.
|
||||
*
|
||||
* <p>If the CRC is valid, the position of {@code scratch} is moved to the byte following it.
|
||||
* Otherwise, there is no guarantee on the position.
|
||||
*
|
||||
* <p>The {@code scratch} array must contain the whole frame header.
|
||||
*
|
||||
* @param scratch The array to read the data from, whose position must indicate the CRC.
|
||||
* @param frameStartPosition The frame start offset in {@code scratch}.
|
||||
* @return Whether the CRC is valid.
|
||||
*/
|
||||
private static boolean checkAndReadCrc(ParsableByteArray scratch, int frameStartPosition) {
|
||||
int crc = scratch.readUnsignedByte();
|
||||
int frameEndPosition = scratch.getPosition();
|
||||
int expectedCrc =
|
||||
Util.crc8(scratch.data, frameStartPosition, frameEndPosition - 1, /* initialValue= */ 0);
|
||||
return crc == expectedCrc;
|
||||
}
|
||||
|
||||
private FlacFrameReader() {}
|
||||
}
|
@ -0,0 +1,208 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer2.util;
|
||||
|
||||
import com.google.android.exoplayer2.ParserException;
|
||||
import com.google.android.exoplayer2.extractor.ExtractorInput;
|
||||
import com.google.android.exoplayer2.extractor.Id3Peeker;
|
||||
import com.google.android.exoplayer2.metadata.id3.Id3Decoder;
|
||||
import java.io.IOException;
|
||||
|
||||
/** Reads and peeks FLAC stream metadata elements from an {@link ExtractorInput}. */
|
||||
public final class FlacMetadataReader {
|
||||
|
||||
/** Holds the metadata extracted from the first frame. */
|
||||
public static final class FirstFrameMetadata {
|
||||
/** The frame start marker, which should correspond to the 2 first bytes of each frame. */
|
||||
public final int frameStartMarker;
|
||||
/** The block size in samples. */
|
||||
public final int blockSizeSamples;
|
||||
|
||||
public FirstFrameMetadata(int frameStartMarker, int blockSizeSamples) {
|
||||
this.frameStartMarker = frameStartMarker;
|
||||
this.blockSizeSamples = blockSizeSamples;
|
||||
}
|
||||
}
|
||||
|
||||
private static final int STREAM_MARKER = 0x664C6143; // ASCII for "fLaC"
|
||||
private static final int SYNC_CODE = 0x3FFE;
|
||||
|
||||
/**
|
||||
* Peeks ID3 Data.
|
||||
*
|
||||
* @param input Input stream to peek the ID3 data from.
|
||||
* @throws IOException If peeking from the input fails. In this case, there is no guarantee on the
|
||||
* peek position.
|
||||
* @throws InterruptedException If interrupted while peeking from input. In this case, there is no
|
||||
* guarantee on the peek position.
|
||||
*/
|
||||
public static void peekId3Data(ExtractorInput input) throws IOException, InterruptedException {
|
||||
new Id3Peeker().peekId3Data(input, Id3Decoder.NO_FRAMES_PREDICATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Peeks the FLAC stream marker.
|
||||
*
|
||||
* @param input Input stream to peek the stream marker from.
|
||||
* @return Whether the data peeked is the FLAC stream marker.
|
||||
* @throws IOException If peeking from the input fails. In this case, the peek position is left
|
||||
* unchanged.
|
||||
* @throws InterruptedException If interrupted while peeking from input. In this case, the peek
|
||||
* position is left unchanged.
|
||||
*/
|
||||
public static boolean checkAndPeekStreamMarker(ExtractorInput input)
|
||||
throws IOException, InterruptedException {
|
||||
ParsableByteArray scratch = new ParsableByteArray(FlacConstants.STREAM_MARKER_SIZE);
|
||||
input.peekFully(scratch.data, /* offset= */ 0, FlacConstants.STREAM_MARKER_SIZE);
|
||||
return scratch.readUnsignedInt() == STREAM_MARKER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads ID3 Data.
|
||||
*
|
||||
* <p>If no exception is thrown, the peek position of {@code input} is aligned with the read
|
||||
* position.
|
||||
*
|
||||
* @param input Input stream to read the ID3 data from.
|
||||
* @throws IOException If reading from the input fails. In this case, the read position is left
|
||||
* unchanged and there is no guarantee on the peek position.
|
||||
* @throws InterruptedException If interrupted while reading from input. In this case, the read
|
||||
* position is left unchanged and there is no guarantee on the peek position.
|
||||
*/
|
||||
public static void readId3Data(ExtractorInput input) throws IOException, InterruptedException {
|
||||
input.resetPeekPosition();
|
||||
long startingPeekPosition = input.getPeekPosition();
|
||||
new Id3Peeker().peekId3Data(input, Id3Decoder.NO_FRAMES_PREDICATE);
|
||||
int peekedId3Bytes = (int) (input.getPeekPosition() - startingPeekPosition);
|
||||
input.skipFully(peekedId3Bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the FLAC stream marker.
|
||||
*
|
||||
* @param input Input stream to read the stream marker from.
|
||||
* @param scratchData The array in which the data read should be copied. This array must have size
|
||||
* at least {@code scratchWriteIndex} + {@link FlacConstants#STREAM_MARKER_SIZE}.
|
||||
* @param scratchWriteIndex The index of {@code scratchData} from which to write.
|
||||
* @throws ParserException If an error occurs parsing the stream marker. In this case, the
|
||||
* position of {@code input} is advanced by {@link FlacConstants#STREAM_MARKER_SIZE} bytes.
|
||||
* @throws IOException If reading from the input fails. In this case, the position is left
|
||||
* unchanged.
|
||||
* @throws InterruptedException If interrupted while reading from input. In this case, the
|
||||
* position is left unchanged.
|
||||
*/
|
||||
public static void readStreamMarker(
|
||||
ExtractorInput input, byte[] scratchData, int scratchWriteIndex)
|
||||
throws IOException, InterruptedException {
|
||||
ParsableByteArray scratch = new ParsableByteArray(scratchData);
|
||||
input.readFully(
|
||||
scratch.data,
|
||||
/* offset= */ scratchWriteIndex,
|
||||
/* length= */ FlacConstants.STREAM_MARKER_SIZE);
|
||||
scratch.setPosition(scratchWriteIndex);
|
||||
if (scratch.readUnsignedInt() != STREAM_MARKER) {
|
||||
throw new ParserException("Failed to read FLAC stream marker.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the stream info block.
|
||||
*
|
||||
* @param input Input stream to read the stream info block from.
|
||||
* @param scratchData The array in which the data read should be copied. This array must have size
|
||||
* at least {@code scratchWriteIndex} + {@link FlacConstants#STREAM_INFO_BLOCK_SIZE}.
|
||||
* @param scratchWriteIndex The index of {@code scratchData} from which to write.
|
||||
* @return A new {@link FlacStreamMetadata} read from {@code input}.
|
||||
* @throws IOException If reading from the input fails. In this case, the position is left
|
||||
* unchanged.
|
||||
* @throws InterruptedException If interrupted while reading from input. In this case, the
|
||||
* position is left unchanged.
|
||||
*/
|
||||
public static FlacStreamMetadata readStreamInfoBlock(
|
||||
ExtractorInput input, byte[] scratchData, int scratchWriteIndex)
|
||||
throws IOException, InterruptedException {
|
||||
input.readFully(
|
||||
scratchData,
|
||||
/* offset= */ scratchWriteIndex,
|
||||
/* length= */ FlacConstants.STREAM_INFO_BLOCK_SIZE);
|
||||
return new FlacStreamMetadata(
|
||||
scratchData, /* offset= */ scratchWriteIndex + FlacConstants.METADATA_BLOCK_HEADER_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Skips the stream metadata blocks.
|
||||
*
|
||||
* <p>If no exception is thrown, the peek position of {@code input} is aligned with the read
|
||||
* position.
|
||||
*
|
||||
* @param input Input stream to read the metadata blocks from.
|
||||
* @throws IOException If reading from the input fails. In this case, the read position will be at
|
||||
* the start of a metadata block and there is no guarantee on the peek position.
|
||||
* @throws InterruptedException If interrupted while reading from input. In this case, the read
|
||||
* position will be at the start of a metadata block and there is no guarantee on the peek
|
||||
* position.
|
||||
*/
|
||||
public static void skipMetadataBlocks(ExtractorInput input)
|
||||
throws IOException, InterruptedException {
|
||||
input.resetPeekPosition();
|
||||
ParsableBitArray scratch = new ParsableBitArray(new byte[4]);
|
||||
boolean lastMetadataBlock = false;
|
||||
while (!lastMetadataBlock) {
|
||||
input.peekFully(scratch.data, /* offset= */ 0, /* length= */ 4);
|
||||
scratch.setPosition(0);
|
||||
lastMetadataBlock = scratch.readBit();
|
||||
scratch.skipBits(7);
|
||||
int length = scratch.readBits(24);
|
||||
input.skipFully(4 + length);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns some metadata extracted from the first frame of a FLAC stream.
|
||||
*
|
||||
* <p>The read position of {@code input} is left unchanged and the peek position is aligned with
|
||||
* the read position.
|
||||
*
|
||||
* @param input Input stream to get the metadata from (starting from the read position).
|
||||
* @return A {@link FirstFrameMetadata} containing the frame start marker (which should be the
|
||||
* same for all the frames in the stream) and the block size of the frame.
|
||||
* @throws ParserException If an error occurs parsing the frame metadata.
|
||||
* @throws IOException If peeking from the input fails.
|
||||
* @throws InterruptedException If interrupted while peeking from input.
|
||||
*/
|
||||
public static FirstFrameMetadata getFirstFrameMetadata(ExtractorInput input)
|
||||
throws IOException, InterruptedException {
|
||||
input.resetPeekPosition();
|
||||
ParsableByteArray scratch =
|
||||
new ParsableByteArray(new byte[FlacConstants.MAX_FRAME_HEADER_SIZE]);
|
||||
input.peekFully(scratch.data, /* offset= */ 0, FlacConstants.MAX_FRAME_HEADER_SIZE);
|
||||
|
||||
int frameStartMarker = scratch.readUnsignedShort();
|
||||
int syncCode = frameStartMarker >> 2;
|
||||
if (syncCode != SYNC_CODE) {
|
||||
input.resetPeekPosition();
|
||||
throw new ParserException("First frame does not start with sync code.");
|
||||
}
|
||||
|
||||
scratch.setPosition(0);
|
||||
int firstFrameBlockSizeSamples = FlacFrameReader.getFrameBlockSizeSamples(scratch);
|
||||
|
||||
input.resetPeekPosition();
|
||||
return new FirstFrameMetadata(frameStartMarker, firstFrameBlockSizeSamples);
|
||||
}
|
||||
|
||||
private FlacMetadataReader() {}
|
||||
}
|
@ -17,10 +17,12 @@ package com.google.android.exoplayer2.util;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.metadata.Metadata;
|
||||
import com.google.android.exoplayer2.metadata.flac.PictureFrame;
|
||||
import com.google.android.exoplayer2.metadata.flac.VorbisComment;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/** Holder for FLAC metadata. */
|
||||
@ -28,14 +30,45 @@ public final class FlacStreamMetadata {
|
||||
|
||||
private static final String TAG = "FlacStreamMetadata";
|
||||
|
||||
public final int minBlockSize;
|
||||
public final int maxBlockSize;
|
||||
/** Indicates that a value is not in the corresponding lookup table. */
|
||||
public static final int NOT_IN_LOOKUP_TABLE = -1;
|
||||
|
||||
/** Minimum number of samples per block. */
|
||||
public final int minBlockSizeSamples;
|
||||
/** Maximum number of samples per block. */
|
||||
public final int maxBlockSizeSamples;
|
||||
/** Minimum frame size in bytes, or 0 if the value is unknown. */
|
||||
public final int minFrameSize;
|
||||
/** Maximum frame size in bytes, or 0 if the value is unknown. */
|
||||
public final int maxFrameSize;
|
||||
/** Sample rate in Hertz. */
|
||||
public final int sampleRate;
|
||||
/**
|
||||
* Lookup key corresponding to the stream sample rate, or {@link #NOT_IN_LOOKUP_TABLE} if it is
|
||||
* not in the lookup table.
|
||||
*
|
||||
* <p>This key is used to indicate the sample rate in the frame header for the most common values.
|
||||
*
|
||||
* <p>The sample rate lookup table is described in https://xiph.org/flac/format.html#frame_header.
|
||||
*/
|
||||
public final int sampleRateLookupKey;
|
||||
/** Number of audio channels. */
|
||||
public final int channels;
|
||||
/** Number of bits per sample. */
|
||||
public final int bitsPerSample;
|
||||
/**
|
||||
* Lookup key corresponding to the number of bits per sample of the stream, or {@link
|
||||
* #NOT_IN_LOOKUP_TABLE} if it is not in the lookup table.
|
||||
*
|
||||
* <p>This key is used to indicate the number of bits per sample in the frame header for the most
|
||||
* common values.
|
||||
*
|
||||
* <p>The sample size lookup table is described in https://xiph.org/flac/format.html#frame_header.
|
||||
*/
|
||||
public final int bitsPerSampleLookupKey;
|
||||
/** Total number of samples, or 0 if the value is unknown. */
|
||||
public final long totalSamples;
|
||||
/** Stream content metadata. */
|
||||
@Nullable public final Metadata metadata;
|
||||
|
||||
private static final String SEPARATOR = "=";
|
||||
@ -44,27 +77,29 @@ public final class FlacStreamMetadata {
|
||||
* Parses binary FLAC stream info metadata.
|
||||
*
|
||||
* @param data An array containing binary FLAC stream info metadata.
|
||||
* @param offset The offset of the stream info metadata in {@code data}.
|
||||
* @param offset The offset of the stream info block in {@code data} (header excluded).
|
||||
* @see <a href="https://xiph.org/flac/format.html#metadata_block_streaminfo">FLAC format
|
||||
* METADATA_BLOCK_STREAMINFO</a>
|
||||
*/
|
||||
public FlacStreamMetadata(byte[] data, int offset) {
|
||||
ParsableBitArray scratch = new ParsableBitArray(data);
|
||||
scratch.setPosition(offset * 8);
|
||||
this.minBlockSize = scratch.readBits(16);
|
||||
this.maxBlockSize = scratch.readBits(16);
|
||||
this.minBlockSizeSamples = scratch.readBits(16);
|
||||
this.maxBlockSizeSamples = scratch.readBits(16);
|
||||
this.minFrameSize = scratch.readBits(24);
|
||||
this.maxFrameSize = scratch.readBits(24);
|
||||
this.sampleRate = scratch.readBits(20);
|
||||
this.sampleRateLookupKey = getSampleRateLookupKey();
|
||||
this.channels = scratch.readBits(3) + 1;
|
||||
this.bitsPerSample = scratch.readBits(5) + 1;
|
||||
this.totalSamples = ((scratch.readBits(4) & 0xFL) << 32) | (scratch.readBits(32) & 0xFFFFFFFFL);
|
||||
this.bitsPerSampleLookupKey = getBitsPerSampleLookupKey();
|
||||
this.totalSamples = scratch.readBitsToLong(36);
|
||||
this.metadata = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param minBlockSize Minimum block size of the FLAC stream.
|
||||
* @param maxBlockSize Maximum block size of the FLAC stream.
|
||||
* @param minBlockSizeSamples Minimum block size of the FLAC stream.
|
||||
* @param maxBlockSizeSamples Maximum block size of the FLAC stream.
|
||||
* @param minFrameSize Minimum frame size of the FLAC stream.
|
||||
* @param maxFrameSize Maximum frame size of the FLAC stream.
|
||||
* @param sampleRate Sample rate of the FLAC stream.
|
||||
@ -81,8 +116,8 @@ public final class FlacStreamMetadata {
|
||||
* METADATA_BLOCK_PICTURE</a>
|
||||
*/
|
||||
public FlacStreamMetadata(
|
||||
int minBlockSize,
|
||||
int maxBlockSize,
|
||||
int minBlockSizeSamples,
|
||||
int maxBlockSizeSamples,
|
||||
int minFrameSize,
|
||||
int maxFrameSize,
|
||||
int sampleRate,
|
||||
@ -91,30 +126,35 @@ public final class FlacStreamMetadata {
|
||||
long totalSamples,
|
||||
List<String> vorbisComments,
|
||||
List<PictureFrame> pictureFrames) {
|
||||
this.minBlockSize = minBlockSize;
|
||||
this.maxBlockSize = maxBlockSize;
|
||||
this.minBlockSizeSamples = minBlockSizeSamples;
|
||||
this.maxBlockSizeSamples = maxBlockSizeSamples;
|
||||
this.minFrameSize = minFrameSize;
|
||||
this.maxFrameSize = maxFrameSize;
|
||||
this.sampleRate = sampleRate;
|
||||
this.sampleRateLookupKey = getSampleRateLookupKey();
|
||||
this.channels = channels;
|
||||
this.bitsPerSample = bitsPerSample;
|
||||
this.bitsPerSampleLookupKey = getBitsPerSampleLookupKey();
|
||||
this.totalSamples = totalSamples;
|
||||
this.metadata = buildMetadata(vorbisComments, pictureFrames);
|
||||
this.metadata = getMetadata(vorbisComments, pictureFrames);
|
||||
}
|
||||
|
||||
/** Returns the maximum size for a decoded frame from the FLAC stream. */
|
||||
public int maxDecodedFrameSize() {
|
||||
return maxBlockSize * channels * (bitsPerSample / 8);
|
||||
public int getMaxDecodedFrameSize() {
|
||||
return maxBlockSizeSamples * channels * (bitsPerSample / 8);
|
||||
}
|
||||
|
||||
/** Returns the bit-rate of the FLAC stream. */
|
||||
public int bitRate() {
|
||||
public int getBitRate() {
|
||||
return bitsPerSample * sampleRate * channels;
|
||||
}
|
||||
|
||||
/** Returns the duration of the FLAC stream in microseconds. */
|
||||
public long durationUs() {
|
||||
return (totalSamples * 1000000L) / sampleRate;
|
||||
/**
|
||||
* Returns the duration of the FLAC stream in microseconds, or {@link C#TIME_UNSET} if the total
|
||||
* number of samples if unknown.
|
||||
*/
|
||||
public long getDurationUs() {
|
||||
return totalSamples == 0 ? C.TIME_UNSET : totalSamples * C.MICROS_PER_SECOND / sampleRate;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -125,7 +165,7 @@ public final class FlacStreamMetadata {
|
||||
*/
|
||||
public long getSampleIndex(long timeUs) {
|
||||
long sampleIndex = (timeUs * sampleRate) / C.MICROS_PER_SECOND;
|
||||
return Util.constrainValue(sampleIndex, 0, totalSamples - 1);
|
||||
return Util.constrainValue(sampleIndex, /* min= */ 0, totalSamples - 1);
|
||||
}
|
||||
|
||||
/** Returns the approximate number of bytes per frame for the current FLAC stream. */
|
||||
@ -136,14 +176,91 @@ public final class FlacStreamMetadata {
|
||||
} else {
|
||||
// Uses the stream's block-size if it's a known fixed block-size stream, otherwise uses the
|
||||
// default value for FLAC block-size, which is 4096.
|
||||
long blockSize = (minBlockSize == maxBlockSize && minBlockSize > 0) ? minBlockSize : 4096;
|
||||
approxBytesPerFrame = (blockSize * channels * bitsPerSample) / 8 + 64;
|
||||
long blockSizeSamples =
|
||||
(minBlockSizeSamples == maxBlockSizeSamples && minBlockSizeSamples > 0)
|
||||
? minBlockSizeSamples
|
||||
: 4096;
|
||||
approxBytesPerFrame = (blockSizeSamples * channels * bitsPerSample) / 8 + 64;
|
||||
}
|
||||
return approxBytesPerFrame;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Format} extracted from the FLAC stream metadata.
|
||||
*
|
||||
* <p>{@code streamMarkerAndInfoBlock} is updated to set the bit corresponding to the stream info
|
||||
* last metadata block flag to true.
|
||||
*
|
||||
* @param streamMarkerAndInfoBlock An array containing the FLAC stream marker followed by the
|
||||
* stream info block.
|
||||
* @return The extracted {@link Format}.
|
||||
*/
|
||||
public Format getFormat(byte[] streamMarkerAndInfoBlock) {
|
||||
// Set the last metadata block flag, ignore the other blocks.
|
||||
streamMarkerAndInfoBlock[4] = (byte) 0x80;
|
||||
int maxInputSize = maxFrameSize > 0 ? maxFrameSize : Format.NO_VALUE;
|
||||
return Format.createAudioSampleFormat(
|
||||
/* id= */ null,
|
||||
MimeTypes.AUDIO_FLAC,
|
||||
/* codecs= */ null,
|
||||
getBitRate(),
|
||||
maxInputSize,
|
||||
channels,
|
||||
sampleRate,
|
||||
Collections.singletonList(streamMarkerAndInfoBlock),
|
||||
/* drmInitData= */ null,
|
||||
/* selectionFlags= */ 0,
|
||||
/* language= */ null);
|
||||
}
|
||||
|
||||
private int getSampleRateLookupKey() {
|
||||
switch (sampleRate) {
|
||||
case 88200:
|
||||
return 1;
|
||||
case 176400:
|
||||
return 2;
|
||||
case 192000:
|
||||
return 3;
|
||||
case 8000:
|
||||
return 4;
|
||||
case 16000:
|
||||
return 5;
|
||||
case 22050:
|
||||
return 6;
|
||||
case 24000:
|
||||
return 7;
|
||||
case 32000:
|
||||
return 8;
|
||||
case 44100:
|
||||
return 9;
|
||||
case 48000:
|
||||
return 10;
|
||||
case 96000:
|
||||
return 11;
|
||||
default:
|
||||
return NOT_IN_LOOKUP_TABLE;
|
||||
}
|
||||
}
|
||||
|
||||
private int getBitsPerSampleLookupKey() {
|
||||
switch (bitsPerSample) {
|
||||
case 8:
|
||||
return 1;
|
||||
case 12:
|
||||
return 2;
|
||||
case 16:
|
||||
return 4;
|
||||
case 20:
|
||||
return 5;
|
||||
case 24:
|
||||
return 6;
|
||||
default:
|
||||
return NOT_IN_LOOKUP_TABLE;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Metadata buildMetadata(
|
||||
private static Metadata getMetadata(
|
||||
List<String> vorbisComments, List<PictureFrame> pictureFrames) {
|
||||
if (vorbisComments.isEmpty() && pictureFrames.isEmpty()) {
|
||||
return null;
|
||||
|
BIN
library/core/src/test/assets/flac/bear.flac
Normal file
BIN
library/core/src/test/assets/flac/bear.flac
Normal file
Binary file not shown.
163
library/core/src/test/assets/flac/bear.flac.0.dump
Normal file
163
library/core/src/test/assets/flac/bear.flac.0.dump
Normal file
@ -0,0 +1,163 @@
|
||||
seekMap:
|
||||
isSeekable = false
|
||||
duration = 2741000
|
||||
getPosition(0) = [[timeUs=0, position=0]]
|
||||
numberOfTracks = 1
|
||||
track 0:
|
||||
format:
|
||||
bitrate = 1536000
|
||||
id = null
|
||||
containerMimeType = null
|
||||
sampleMimeType = audio/flac
|
||||
maxInputSize = 5776
|
||||
width = -1
|
||||
height = -1
|
||||
frameRate = -1.0
|
||||
rotationDegrees = 0
|
||||
pixelWidthHeightRatio = 1.0
|
||||
channelCount = 2
|
||||
sampleRate = 48000
|
||||
pcmEncoding = -1
|
||||
encoderDelay = 0
|
||||
encoderPadding = 0
|
||||
subsampleOffsetUs = 9223372036854775807
|
||||
selectionFlags = 0
|
||||
language = null
|
||||
drmInitData = -
|
||||
initializationData:
|
||||
data = length 42, hash 83F6895
|
||||
total output bytes = 164431
|
||||
sample count = 33
|
||||
sample 0:
|
||||
time = 0
|
||||
flags = 1
|
||||
data = length 5030, hash D2B60530
|
||||
sample 1:
|
||||
time = 85333
|
||||
flags = 1
|
||||
data = length 5066, hash 4C932A54
|
||||
sample 2:
|
||||
time = 170666
|
||||
flags = 1
|
||||
data = length 5112, hash 7E5A7B61
|
||||
sample 3:
|
||||
time = 256000
|
||||
flags = 1
|
||||
data = length 5044, hash 7EF93F13
|
||||
sample 4:
|
||||
time = 341333
|
||||
flags = 1
|
||||
data = length 4943, hash DE7E27F8
|
||||
sample 5:
|
||||
time = 426666
|
||||
flags = 1
|
||||
data = length 5121, hash 6D0D0B40
|
||||
sample 6:
|
||||
time = 512000
|
||||
flags = 1
|
||||
data = length 5068, hash 9924644F
|
||||
sample 7:
|
||||
time = 597333
|
||||
flags = 1
|
||||
data = length 5143, hash 6C34F0CE
|
||||
sample 8:
|
||||
time = 682666
|
||||
flags = 1
|
||||
data = length 5109, hash E3B7BEFB
|
||||
sample 9:
|
||||
time = 768000
|
||||
flags = 1
|
||||
data = length 5129, hash 44111D9B
|
||||
sample 10:
|
||||
time = 853333
|
||||
flags = 1
|
||||
data = length 5031, hash 9D55EA53
|
||||
sample 11:
|
||||
time = 938666
|
||||
flags = 1
|
||||
data = length 5119, hash E1CB9BA6
|
||||
sample 12:
|
||||
time = 1024000
|
||||
flags = 1
|
||||
data = length 5360, hash 17265C5D
|
||||
sample 13:
|
||||
time = 1109333
|
||||
flags = 1
|
||||
data = length 5340, hash A90FDDF1
|
||||
sample 14:
|
||||
time = 1194666
|
||||
flags = 1
|
||||
data = length 5162, hash 31F65AD5
|
||||
sample 15:
|
||||
time = 1280000
|
||||
flags = 1
|
||||
data = length 5168, hash F2394F2D
|
||||
sample 16:
|
||||
time = 1365333
|
||||
flags = 1
|
||||
data = length 5776, hash 58437AB3
|
||||
sample 17:
|
||||
time = 1450666
|
||||
flags = 1
|
||||
data = length 5394, hash EBAB20A8
|
||||
sample 18:
|
||||
time = 1536000
|
||||
flags = 1
|
||||
data = length 5168, hash BF37C7A5
|
||||
sample 19:
|
||||
time = 1621333
|
||||
flags = 1
|
||||
data = length 5324, hash 59546B7B
|
||||
sample 20:
|
||||
time = 1706666
|
||||
flags = 1
|
||||
data = length 5172, hash 6036EF0B
|
||||
sample 21:
|
||||
time = 1792000
|
||||
flags = 1
|
||||
data = length 5102, hash 5A131071
|
||||
sample 22:
|
||||
time = 1877333
|
||||
flags = 1
|
||||
data = length 5111, hash 3D9EBB3B
|
||||
sample 23:
|
||||
time = 1962666
|
||||
flags = 1
|
||||
data = length 5113, hash 61101D4F
|
||||
sample 24:
|
||||
time = 2048000
|
||||
flags = 1
|
||||
data = length 5229, hash D2E55742
|
||||
sample 25:
|
||||
time = 2133333
|
||||
flags = 1
|
||||
data = length 5162, hash 7F2E97FA
|
||||
sample 26:
|
||||
time = 2218666
|
||||
flags = 1
|
||||
data = length 5255, hash D92A782
|
||||
sample 27:
|
||||
time = 2304000
|
||||
flags = 1
|
||||
data = length 5196, hash 98FE5138
|
||||
sample 28:
|
||||
time = 2389333
|
||||
flags = 1
|
||||
data = length 5214, hash 3D35C38C
|
||||
sample 29:
|
||||
time = 2474666
|
||||
flags = 1
|
||||
data = length 5211, hash 7E25420F
|
||||
sample 30:
|
||||
time = 2560000
|
||||
flags = 1
|
||||
data = length 5230, hash 2AD96FBC
|
||||
sample 31:
|
||||
time = 2645333
|
||||
flags = 1
|
||||
data = length 3384, hash 938BCDD9
|
||||
sample 32:
|
||||
time = 2730666
|
||||
flags = 1
|
||||
data = length 445, hash A388E3D6
|
||||
tracksEnded = true
|
Binary file not shown.
@ -0,0 +1,163 @@
|
||||
seekMap:
|
||||
isSeekable = false
|
||||
duration = 2741000
|
||||
getPosition(0) = [[timeUs=0, position=0]]
|
||||
numberOfTracks = 1
|
||||
track 0:
|
||||
format:
|
||||
bitrate = 1536000
|
||||
id = null
|
||||
containerMimeType = null
|
||||
sampleMimeType = audio/flac
|
||||
maxInputSize = -1
|
||||
width = -1
|
||||
height = -1
|
||||
frameRate = -1.0
|
||||
rotationDegrees = 0
|
||||
pixelWidthHeightRatio = 1.0
|
||||
channelCount = 2
|
||||
sampleRate = 48000
|
||||
pcmEncoding = -1
|
||||
encoderDelay = 0
|
||||
encoderPadding = 0
|
||||
subsampleOffsetUs = 9223372036854775807
|
||||
selectionFlags = 0
|
||||
language = null
|
||||
drmInitData = -
|
||||
initializationData:
|
||||
data = length 42, hash 9218FDB7
|
||||
total output bytes = 164431
|
||||
sample count = 33
|
||||
sample 0:
|
||||
time = 0
|
||||
flags = 1
|
||||
data = length 5030, hash D2B60530
|
||||
sample 1:
|
||||
time = 85333
|
||||
flags = 1
|
||||
data = length 5066, hash 4C932A54
|
||||
sample 2:
|
||||
time = 170666
|
||||
flags = 1
|
||||
data = length 5112, hash 7E5A7B61
|
||||
sample 3:
|
||||
time = 256000
|
||||
flags = 1
|
||||
data = length 5044, hash 7EF93F13
|
||||
sample 4:
|
||||
time = 341333
|
||||
flags = 1
|
||||
data = length 4943, hash DE7E27F8
|
||||
sample 5:
|
||||
time = 426666
|
||||
flags = 1
|
||||
data = length 5121, hash 6D0D0B40
|
||||
sample 6:
|
||||
time = 512000
|
||||
flags = 1
|
||||
data = length 5068, hash 9924644F
|
||||
sample 7:
|
||||
time = 597333
|
||||
flags = 1
|
||||
data = length 5143, hash 6C34F0CE
|
||||
sample 8:
|
||||
time = 682666
|
||||
flags = 1
|
||||
data = length 5109, hash E3B7BEFB
|
||||
sample 9:
|
||||
time = 768000
|
||||
flags = 1
|
||||
data = length 5129, hash 44111D9B
|
||||
sample 10:
|
||||
time = 853333
|
||||
flags = 1
|
||||
data = length 5031, hash 9D55EA53
|
||||
sample 11:
|
||||
time = 938666
|
||||
flags = 1
|
||||
data = length 5119, hash E1CB9BA6
|
||||
sample 12:
|
||||
time = 1024000
|
||||
flags = 1
|
||||
data = length 5360, hash 17265C5D
|
||||
sample 13:
|
||||
time = 1109333
|
||||
flags = 1
|
||||
data = length 5340, hash A90FDDF1
|
||||
sample 14:
|
||||
time = 1194666
|
||||
flags = 1
|
||||
data = length 5162, hash 31F65AD5
|
||||
sample 15:
|
||||
time = 1280000
|
||||
flags = 1
|
||||
data = length 5168, hash F2394F2D
|
||||
sample 16:
|
||||
time = 1365333
|
||||
flags = 1
|
||||
data = length 5776, hash 58437AB3
|
||||
sample 17:
|
||||
time = 1450666
|
||||
flags = 1
|
||||
data = length 5394, hash EBAB20A8
|
||||
sample 18:
|
||||
time = 1536000
|
||||
flags = 1
|
||||
data = length 5168, hash BF37C7A5
|
||||
sample 19:
|
||||
time = 1621333
|
||||
flags = 1
|
||||
data = length 5324, hash 59546B7B
|
||||
sample 20:
|
||||
time = 1706666
|
||||
flags = 1
|
||||
data = length 5172, hash 6036EF0B
|
||||
sample 21:
|
||||
time = 1792000
|
||||
flags = 1
|
||||
data = length 5102, hash 5A131071
|
||||
sample 22:
|
||||
time = 1877333
|
||||
flags = 1
|
||||
data = length 5111, hash 3D9EBB3B
|
||||
sample 23:
|
||||
time = 1962666
|
||||
flags = 1
|
||||
data = length 5113, hash 61101D4F
|
||||
sample 24:
|
||||
time = 2048000
|
||||
flags = 1
|
||||
data = length 5229, hash D2E55742
|
||||
sample 25:
|
||||
time = 2133333
|
||||
flags = 1
|
||||
data = length 5162, hash 7F2E97FA
|
||||
sample 26:
|
||||
time = 2218666
|
||||
flags = 1
|
||||
data = length 5255, hash D92A782
|
||||
sample 27:
|
||||
time = 2304000
|
||||
flags = 1
|
||||
data = length 5196, hash 98FE5138
|
||||
sample 28:
|
||||
time = 2389333
|
||||
flags = 1
|
||||
data = length 5214, hash 3D35C38C
|
||||
sample 29:
|
||||
time = 2474666
|
||||
flags = 1
|
||||
data = length 5211, hash 7E25420F
|
||||
sample 30:
|
||||
time = 2560000
|
||||
flags = 1
|
||||
data = length 5230, hash 2AD96FBC
|
||||
sample 31:
|
||||
time = 2645333
|
||||
flags = 1
|
||||
data = length 3384, hash 938BCDD9
|
||||
sample 32:
|
||||
time = 2730666
|
||||
flags = 1
|
||||
data = length 445, hash A388E3D6
|
||||
tracksEnded = true
|
BIN
library/core/src/test/assets/flac/bear_no_num_samples.flac
Normal file
BIN
library/core/src/test/assets/flac/bear_no_num_samples.flac
Normal file
Binary file not shown.
@ -0,0 +1,163 @@
|
||||
seekMap:
|
||||
isSeekable = false
|
||||
duration = UNSET TIME
|
||||
getPosition(0) = [[timeUs=0, position=0]]
|
||||
numberOfTracks = 1
|
||||
track 0:
|
||||
format:
|
||||
bitrate = 1536000
|
||||
id = null
|
||||
containerMimeType = null
|
||||
sampleMimeType = audio/flac
|
||||
maxInputSize = 5776
|
||||
width = -1
|
||||
height = -1
|
||||
frameRate = -1.0
|
||||
rotationDegrees = 0
|
||||
pixelWidthHeightRatio = 1.0
|
||||
channelCount = 2
|
||||
sampleRate = 48000
|
||||
pcmEncoding = -1
|
||||
encoderDelay = 0
|
||||
encoderPadding = 0
|
||||
subsampleOffsetUs = 9223372036854775807
|
||||
selectionFlags = 0
|
||||
language = null
|
||||
drmInitData = -
|
||||
initializationData:
|
||||
data = length 42, hash 49FA2C21
|
||||
total output bytes = 164431
|
||||
sample count = 33
|
||||
sample 0:
|
||||
time = 0
|
||||
flags = 1
|
||||
data = length 5030, hash D2B60530
|
||||
sample 1:
|
||||
time = 85333
|
||||
flags = 1
|
||||
data = length 5066, hash 4C932A54
|
||||
sample 2:
|
||||
time = 170666
|
||||
flags = 1
|
||||
data = length 5112, hash 7E5A7B61
|
||||
sample 3:
|
||||
time = 256000
|
||||
flags = 1
|
||||
data = length 5044, hash 7EF93F13
|
||||
sample 4:
|
||||
time = 341333
|
||||
flags = 1
|
||||
data = length 4943, hash DE7E27F8
|
||||
sample 5:
|
||||
time = 426666
|
||||
flags = 1
|
||||
data = length 5121, hash 6D0D0B40
|
||||
sample 6:
|
||||
time = 512000
|
||||
flags = 1
|
||||
data = length 5068, hash 9924644F
|
||||
sample 7:
|
||||
time = 597333
|
||||
flags = 1
|
||||
data = length 5143, hash 6C34F0CE
|
||||
sample 8:
|
||||
time = 682666
|
||||
flags = 1
|
||||
data = length 5109, hash E3B7BEFB
|
||||
sample 9:
|
||||
time = 768000
|
||||
flags = 1
|
||||
data = length 5129, hash 44111D9B
|
||||
sample 10:
|
||||
time = 853333
|
||||
flags = 1
|
||||
data = length 5031, hash 9D55EA53
|
||||
sample 11:
|
||||
time = 938666
|
||||
flags = 1
|
||||
data = length 5119, hash E1CB9BA6
|
||||
sample 12:
|
||||
time = 1024000
|
||||
flags = 1
|
||||
data = length 5360, hash 17265C5D
|
||||
sample 13:
|
||||
time = 1109333
|
||||
flags = 1
|
||||
data = length 5340, hash A90FDDF1
|
||||
sample 14:
|
||||
time = 1194666
|
||||
flags = 1
|
||||
data = length 5162, hash 31F65AD5
|
||||
sample 15:
|
||||
time = 1280000
|
||||
flags = 1
|
||||
data = length 5168, hash F2394F2D
|
||||
sample 16:
|
||||
time = 1365333
|
||||
flags = 1
|
||||
data = length 5776, hash 58437AB3
|
||||
sample 17:
|
||||
time = 1450666
|
||||
flags = 1
|
||||
data = length 5394, hash EBAB20A8
|
||||
sample 18:
|
||||
time = 1536000
|
||||
flags = 1
|
||||
data = length 5168, hash BF37C7A5
|
||||
sample 19:
|
||||
time = 1621333
|
||||
flags = 1
|
||||
data = length 5324, hash 59546B7B
|
||||
sample 20:
|
||||
time = 1706666
|
||||
flags = 1
|
||||
data = length 5172, hash 6036EF0B
|
||||
sample 21:
|
||||
time = 1792000
|
||||
flags = 1
|
||||
data = length 5102, hash 5A131071
|
||||
sample 22:
|
||||
time = 1877333
|
||||
flags = 1
|
||||
data = length 5111, hash 3D9EBB3B
|
||||
sample 23:
|
||||
time = 1962666
|
||||
flags = 1
|
||||
data = length 5113, hash 61101D4F
|
||||
sample 24:
|
||||
time = 2048000
|
||||
flags = 1
|
||||
data = length 5229, hash D2E55742
|
||||
sample 25:
|
||||
time = 2133333
|
||||
flags = 1
|
||||
data = length 5162, hash 7F2E97FA
|
||||
sample 26:
|
||||
time = 2218666
|
||||
flags = 1
|
||||
data = length 5255, hash D92A782
|
||||
sample 27:
|
||||
time = 2304000
|
||||
flags = 1
|
||||
data = length 5196, hash 98FE5138
|
||||
sample 28:
|
||||
time = 2389333
|
||||
flags = 1
|
||||
data = length 5214, hash 3D35C38C
|
||||
sample 29:
|
||||
time = 2474666
|
||||
flags = 1
|
||||
data = length 5211, hash 7E25420F
|
||||
sample 30:
|
||||
time = 2560000
|
||||
flags = 1
|
||||
data = length 5230, hash 2AD96FBC
|
||||
sample 31:
|
||||
time = 2645333
|
||||
flags = 1
|
||||
data = length 3384, hash 938BCDD9
|
||||
sample 32:
|
||||
time = 2730666
|
||||
flags = 1
|
||||
data = length 445, hash A388E3D6
|
||||
tracksEnded = true
|
BIN
library/core/src/test/assets/flac/bear_one_metadata_block.flac
Normal file
BIN
library/core/src/test/assets/flac/bear_one_metadata_block.flac
Normal file
Binary file not shown.
@ -0,0 +1,163 @@
|
||||
seekMap:
|
||||
isSeekable = false
|
||||
duration = 2741000
|
||||
getPosition(0) = [[timeUs=0, position=0]]
|
||||
numberOfTracks = 1
|
||||
track 0:
|
||||
format:
|
||||
bitrate = 1536000
|
||||
id = null
|
||||
containerMimeType = null
|
||||
sampleMimeType = audio/flac
|
||||
maxInputSize = 5776
|
||||
width = -1
|
||||
height = -1
|
||||
frameRate = -1.0
|
||||
rotationDegrees = 0
|
||||
pixelWidthHeightRatio = 1.0
|
||||
channelCount = 2
|
||||
sampleRate = 48000
|
||||
pcmEncoding = -1
|
||||
encoderDelay = 0
|
||||
encoderPadding = 0
|
||||
subsampleOffsetUs = 9223372036854775807
|
||||
selectionFlags = 0
|
||||
language = null
|
||||
drmInitData = -
|
||||
initializationData:
|
||||
data = length 42, hash 83F6895
|
||||
total output bytes = 164431
|
||||
sample count = 33
|
||||
sample 0:
|
||||
time = 0
|
||||
flags = 1
|
||||
data = length 5030, hash D2B60530
|
||||
sample 1:
|
||||
time = 85333
|
||||
flags = 1
|
||||
data = length 5066, hash 4C932A54
|
||||
sample 2:
|
||||
time = 170666
|
||||
flags = 1
|
||||
data = length 5112, hash 7E5A7B61
|
||||
sample 3:
|
||||
time = 256000
|
||||
flags = 1
|
||||
data = length 5044, hash 7EF93F13
|
||||
sample 4:
|
||||
time = 341333
|
||||
flags = 1
|
||||
data = length 4943, hash DE7E27F8
|
||||
sample 5:
|
||||
time = 426666
|
||||
flags = 1
|
||||
data = length 5121, hash 6D0D0B40
|
||||
sample 6:
|
||||
time = 512000
|
||||
flags = 1
|
||||
data = length 5068, hash 9924644F
|
||||
sample 7:
|
||||
time = 597333
|
||||
flags = 1
|
||||
data = length 5143, hash 6C34F0CE
|
||||
sample 8:
|
||||
time = 682666
|
||||
flags = 1
|
||||
data = length 5109, hash E3B7BEFB
|
||||
sample 9:
|
||||
time = 768000
|
||||
flags = 1
|
||||
data = length 5129, hash 44111D9B
|
||||
sample 10:
|
||||
time = 853333
|
||||
flags = 1
|
||||
data = length 5031, hash 9D55EA53
|
||||
sample 11:
|
||||
time = 938666
|
||||
flags = 1
|
||||
data = length 5119, hash E1CB9BA6
|
||||
sample 12:
|
||||
time = 1024000
|
||||
flags = 1
|
||||
data = length 5360, hash 17265C5D
|
||||
sample 13:
|
||||
time = 1109333
|
||||
flags = 1
|
||||
data = length 5340, hash A90FDDF1
|
||||
sample 14:
|
||||
time = 1194666
|
||||
flags = 1
|
||||
data = length 5162, hash 31F65AD5
|
||||
sample 15:
|
||||
time = 1280000
|
||||
flags = 1
|
||||
data = length 5168, hash F2394F2D
|
||||
sample 16:
|
||||
time = 1365333
|
||||
flags = 1
|
||||
data = length 5776, hash 58437AB3
|
||||
sample 17:
|
||||
time = 1450666
|
||||
flags = 1
|
||||
data = length 5394, hash EBAB20A8
|
||||
sample 18:
|
||||
time = 1536000
|
||||
flags = 1
|
||||
data = length 5168, hash BF37C7A5
|
||||
sample 19:
|
||||
time = 1621333
|
||||
flags = 1
|
||||
data = length 5324, hash 59546B7B
|
||||
sample 20:
|
||||
time = 1706666
|
||||
flags = 1
|
||||
data = length 5172, hash 6036EF0B
|
||||
sample 21:
|
||||
time = 1792000
|
||||
flags = 1
|
||||
data = length 5102, hash 5A131071
|
||||
sample 22:
|
||||
time = 1877333
|
||||
flags = 1
|
||||
data = length 5111, hash 3D9EBB3B
|
||||
sample 23:
|
||||
time = 1962666
|
||||
flags = 1
|
||||
data = length 5113, hash 61101D4F
|
||||
sample 24:
|
||||
time = 2048000
|
||||
flags = 1
|
||||
data = length 5229, hash D2E55742
|
||||
sample 25:
|
||||
time = 2133333
|
||||
flags = 1
|
||||
data = length 5162, hash 7F2E97FA
|
||||
sample 26:
|
||||
time = 2218666
|
||||
flags = 1
|
||||
data = length 5255, hash D92A782
|
||||
sample 27:
|
||||
time = 2304000
|
||||
flags = 1
|
||||
data = length 5196, hash 98FE5138
|
||||
sample 28:
|
||||
time = 2389333
|
||||
flags = 1
|
||||
data = length 5214, hash 3D35C38C
|
||||
sample 29:
|
||||
time = 2474666
|
||||
flags = 1
|
||||
data = length 5211, hash 7E25420F
|
||||
sample 30:
|
||||
time = 2560000
|
||||
flags = 1
|
||||
data = length 5230, hash 2AD96FBC
|
||||
sample 31:
|
||||
time = 2645333
|
||||
flags = 1
|
||||
data = length 3384, hash 938BCDD9
|
||||
sample 32:
|
||||
time = 2730666
|
||||
flags = 1
|
||||
data = length 445, hash A388E3D6
|
||||
tracksEnded = true
|
BIN
library/core/src/test/assets/flac/bear_uncommon_sample_rate.flac
Normal file
BIN
library/core/src/test/assets/flac/bear_uncommon_sample_rate.flac
Normal file
Binary file not shown.
@ -0,0 +1,139 @@
|
||||
seekMap:
|
||||
isSeekable = false
|
||||
duration = 2741000
|
||||
getPosition(0) = [[timeUs=0, position=0]]
|
||||
numberOfTracks = 1
|
||||
track 0:
|
||||
format:
|
||||
bitrate = 1408000
|
||||
id = null
|
||||
containerMimeType = null
|
||||
sampleMimeType = audio/flac
|
||||
maxInputSize = 6456
|
||||
width = -1
|
||||
height = -1
|
||||
frameRate = -1.0
|
||||
rotationDegrees = 0
|
||||
pixelWidthHeightRatio = 1.0
|
||||
channelCount = 2
|
||||
sampleRate = 44000
|
||||
pcmEncoding = -1
|
||||
encoderDelay = 0
|
||||
encoderPadding = 0
|
||||
subsampleOffsetUs = 9223372036854775807
|
||||
selectionFlags = 0
|
||||
language = null
|
||||
drmInitData = -
|
||||
initializationData:
|
||||
data = length 42, hash 7249A1B8
|
||||
total output bytes = 144086
|
||||
sample count = 27
|
||||
sample 0:
|
||||
time = 0
|
||||
flags = 1
|
||||
data = length 5415, hash 915DBC66
|
||||
sample 1:
|
||||
time = 104727
|
||||
flags = 1
|
||||
data = length 5529, hash EFD564F7
|
||||
sample 2:
|
||||
time = 209454
|
||||
flags = 1
|
||||
data = length 5480, hash ADA922FB
|
||||
sample 3:
|
||||
time = 314181
|
||||
flags = 1
|
||||
data = length 5290, hash 7BCEA5FC
|
||||
sample 4:
|
||||
time = 418909
|
||||
flags = 1
|
||||
data = length 5579, hash DBB36F37
|
||||
sample 5:
|
||||
time = 523636
|
||||
flags = 1
|
||||
data = length 5423, hash AB53F799
|
||||
sample 6:
|
||||
time = 628363
|
||||
flags = 1
|
||||
data = length 5583, hash 7243C284
|
||||
sample 7:
|
||||
time = 733090
|
||||
flags = 1
|
||||
data = length 5547, hash 9DA9C99E
|
||||
sample 8:
|
||||
time = 837818
|
||||
flags = 1
|
||||
data = length 5414, hash 90768345
|
||||
sample 9:
|
||||
time = 942545
|
||||
flags = 1
|
||||
data = length 5531, hash 1CD2FF67
|
||||
sample 10:
|
||||
time = 1047272
|
||||
flags = 1
|
||||
data = length 5870, hash A9A5CAEE
|
||||
sample 11:
|
||||
time = 1152000
|
||||
flags = 1
|
||||
data = length 5667, hash 875566A1
|
||||
sample 12:
|
||||
time = 1256727
|
||||
flags = 1
|
||||
data = length 5614, hash 5573694C
|
||||
sample 13:
|
||||
time = 1361454
|
||||
flags = 1
|
||||
data = length 6456, hash 921F3DE7
|
||||
sample 14:
|
||||
time = 1466181
|
||||
flags = 1
|
||||
data = length 5817, hash EBECBD16
|
||||
sample 15:
|
||||
time = 1570909
|
||||
flags = 1
|
||||
data = length 5751, hash 4A7D4C6B
|
||||
sample 16:
|
||||
time = 1675636
|
||||
flags = 1
|
||||
data = length 5620, hash B78F8E8D
|
||||
sample 17:
|
||||
time = 1780363
|
||||
flags = 1
|
||||
data = length 5535, hash 8187C107
|
||||
sample 18:
|
||||
time = 1885090
|
||||
flags = 1
|
||||
data = length 5517, hash 79FF36CB
|
||||
sample 19:
|
||||
time = 1989818
|
||||
flags = 1
|
||||
data = length 5716, hash 349FC281
|
||||
sample 20:
|
||||
time = 2094545
|
||||
flags = 1
|
||||
data = length 5556, hash BE97B2CA
|
||||
sample 21:
|
||||
time = 2199272
|
||||
flags = 1
|
||||
data = length 5703, hash 531F9FE3
|
||||
sample 22:
|
||||
time = 2304000
|
||||
flags = 1
|
||||
data = length 5652, hash 1277485D
|
||||
sample 23:
|
||||
time = 2408727
|
||||
flags = 1
|
||||
data = length 5607, hash 14862CB6
|
||||
sample 24:
|
||||
time = 2513454
|
||||
flags = 1
|
||||
data = length 5829, hash FCAF2F1C
|
||||
sample 25:
|
||||
time = 2618181
|
||||
flags = 1
|
||||
data = length 2837, hash 10F1716E
|
||||
sample 26:
|
||||
time = 2722909
|
||||
flags = 1
|
||||
data = length 548, hash B46F603C
|
||||
tracksEnded = true
|
BIN
library/core/src/test/assets/flac/bear_with_id3.flac
Normal file
BIN
library/core/src/test/assets/flac/bear_with_id3.flac
Normal file
Binary file not shown.
163
library/core/src/test/assets/flac/bear_with_id3.flac.0.dump
Normal file
163
library/core/src/test/assets/flac/bear_with_id3.flac.0.dump
Normal file
@ -0,0 +1,163 @@
|
||||
seekMap:
|
||||
isSeekable = false
|
||||
duration = 2741000
|
||||
getPosition(0) = [[timeUs=0, position=0]]
|
||||
numberOfTracks = 1
|
||||
track 0:
|
||||
format:
|
||||
bitrate = 1536000
|
||||
id = null
|
||||
containerMimeType = null
|
||||
sampleMimeType = audio/flac
|
||||
maxInputSize = 5776
|
||||
width = -1
|
||||
height = -1
|
||||
frameRate = -1.0
|
||||
rotationDegrees = 0
|
||||
pixelWidthHeightRatio = 1.0
|
||||
channelCount = 2
|
||||
sampleRate = 48000
|
||||
pcmEncoding = -1
|
||||
encoderDelay = 0
|
||||
encoderPadding = 0
|
||||
subsampleOffsetUs = 9223372036854775807
|
||||
selectionFlags = 0
|
||||
language = null
|
||||
drmInitData = -
|
||||
initializationData:
|
||||
data = length 42, hash 83F6895
|
||||
total output bytes = 164431
|
||||
sample count = 33
|
||||
sample 0:
|
||||
time = 0
|
||||
flags = 1
|
||||
data = length 5030, hash D2B60530
|
||||
sample 1:
|
||||
time = 85333
|
||||
flags = 1
|
||||
data = length 5066, hash 4C932A54
|
||||
sample 2:
|
||||
time = 170666
|
||||
flags = 1
|
||||
data = length 5112, hash 7E5A7B61
|
||||
sample 3:
|
||||
time = 256000
|
||||
flags = 1
|
||||
data = length 5044, hash 7EF93F13
|
||||
sample 4:
|
||||
time = 341333
|
||||
flags = 1
|
||||
data = length 4943, hash DE7E27F8
|
||||
sample 5:
|
||||
time = 426666
|
||||
flags = 1
|
||||
data = length 5121, hash 6D0D0B40
|
||||
sample 6:
|
||||
time = 512000
|
||||
flags = 1
|
||||
data = length 5068, hash 9924644F
|
||||
sample 7:
|
||||
time = 597333
|
||||
flags = 1
|
||||
data = length 5143, hash 6C34F0CE
|
||||
sample 8:
|
||||
time = 682666
|
||||
flags = 1
|
||||
data = length 5109, hash E3B7BEFB
|
||||
sample 9:
|
||||
time = 768000
|
||||
flags = 1
|
||||
data = length 5129, hash 44111D9B
|
||||
sample 10:
|
||||
time = 853333
|
||||
flags = 1
|
||||
data = length 5031, hash 9D55EA53
|
||||
sample 11:
|
||||
time = 938666
|
||||
flags = 1
|
||||
data = length 5119, hash E1CB9BA6
|
||||
sample 12:
|
||||
time = 1024000
|
||||
flags = 1
|
||||
data = length 5360, hash 17265C5D
|
||||
sample 13:
|
||||
time = 1109333
|
||||
flags = 1
|
||||
data = length 5340, hash A90FDDF1
|
||||
sample 14:
|
||||
time = 1194666
|
||||
flags = 1
|
||||
data = length 5162, hash 31F65AD5
|
||||
sample 15:
|
||||
time = 1280000
|
||||
flags = 1
|
||||
data = length 5168, hash F2394F2D
|
||||
sample 16:
|
||||
time = 1365333
|
||||
flags = 1
|
||||
data = length 5776, hash 58437AB3
|
||||
sample 17:
|
||||
time = 1450666
|
||||
flags = 1
|
||||
data = length 5394, hash EBAB20A8
|
||||
sample 18:
|
||||
time = 1536000
|
||||
flags = 1
|
||||
data = length 5168, hash BF37C7A5
|
||||
sample 19:
|
||||
time = 1621333
|
||||
flags = 1
|
||||
data = length 5324, hash 59546B7B
|
||||
sample 20:
|
||||
time = 1706666
|
||||
flags = 1
|
||||
data = length 5172, hash 6036EF0B
|
||||
sample 21:
|
||||
time = 1792000
|
||||
flags = 1
|
||||
data = length 5102, hash 5A131071
|
||||
sample 22:
|
||||
time = 1877333
|
||||
flags = 1
|
||||
data = length 5111, hash 3D9EBB3B
|
||||
sample 23:
|
||||
time = 1962666
|
||||
flags = 1
|
||||
data = length 5113, hash 61101D4F
|
||||
sample 24:
|
||||
time = 2048000
|
||||
flags = 1
|
||||
data = length 5229, hash D2E55742
|
||||
sample 25:
|
||||
time = 2133333
|
||||
flags = 1
|
||||
data = length 5162, hash 7F2E97FA
|
||||
sample 26:
|
||||
time = 2218666
|
||||
flags = 1
|
||||
data = length 5255, hash D92A782
|
||||
sample 27:
|
||||
time = 2304000
|
||||
flags = 1
|
||||
data = length 5196, hash 98FE5138
|
||||
sample 28:
|
||||
time = 2389333
|
||||
flags = 1
|
||||
data = length 5214, hash 3D35C38C
|
||||
sample 29:
|
||||
time = 2474666
|
||||
flags = 1
|
||||
data = length 5211, hash 7E25420F
|
||||
sample 30:
|
||||
time = 2560000
|
||||
flags = 1
|
||||
data = length 5230, hash 2AD96FBC
|
||||
sample 31:
|
||||
time = 2645333
|
||||
flags = 1
|
||||
data = length 3384, hash 938BCDD9
|
||||
sample 32:
|
||||
time = 2730666
|
||||
flags = 1
|
||||
data = length 445, hash A388E3D6
|
||||
tracksEnded = true
|
@ -19,6 +19,7 @@ import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.android.exoplayer2.extractor.amr.AmrExtractor;
|
||||
import com.google.android.exoplayer2.extractor.flac.FlacExtractor;
|
||||
import com.google.android.exoplayer2.extractor.flv.FlvExtractor;
|
||||
import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor;
|
||||
import com.google.android.exoplayer2.extractor.mp3.Mp3Extractor;
|
||||
@ -64,7 +65,8 @@ public final class DefaultExtractorsFactoryTest {
|
||||
PsExtractor.class,
|
||||
WavExtractor.class,
|
||||
AmrExtractor.class,
|
||||
Ac4Extractor.class
|
||||
Ac4Extractor.class,
|
||||
FlacExtractor.class
|
||||
};
|
||||
|
||||
assertThat(listCreatedExtractorClasses).containsNoDuplicates();
|
||||
|
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer2.extractor.flac;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.android.exoplayer2.testutil.ExtractorAsserts;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
/** Unit tests for {@link FlacExtractor}. */
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class FlacExtractorTest {
|
||||
|
||||
@Test
|
||||
public void testSample() throws Exception {
|
||||
ExtractorAsserts.assertBehavior(FlacExtractor::new, "flac/bear.flac");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSampleWithId3() throws Exception {
|
||||
ExtractorAsserts.assertBehavior(FlacExtractor::new, "flac/bear_with_id3.flac");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOneMetadataBlock() throws Exception {
|
||||
ExtractorAsserts.assertBehavior(FlacExtractor::new, "flac/bear_one_metadata_block.flac");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoMinMaxFrameSize() throws Exception {
|
||||
ExtractorAsserts.assertBehavior(FlacExtractor::new, "flac/bear_no_min_max_frame_size.flac");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoNumSamples() throws Exception {
|
||||
ExtractorAsserts.assertBehavior(FlacExtractor::new, "flac/bear_no_num_samples.flac");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUncommonSampleRate() throws Exception {
|
||||
ExtractorAsserts.assertBehavior(FlacExtractor::new, "flac/bear_uncommon_sample_rate.flac");
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user