Implement seeking from seektable in FLAC extractor

PiperOrigin-RevId: 285799995
This commit is contained in:
kimvde 2019-12-16 18:29:27 +00:00 committed by Oliver Woodman
parent 2fe3e188a9
commit 3e4baa101c
33 changed files with 383 additions and 1531 deletions

View File

@ -22,7 +22,10 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
/** Reads and peeks FLAC frame elements. */
/**
* Reads and peeks FLAC frame elements according to the <a
* href="https://xiph.org/flac/format.html">FLAC format specification</a>.
*/
public final class FlacFrameReader {
/** Holds a sample number. */
@ -35,10 +38,10 @@ public final class FlacFrameReader {
* Checks whether the given FLAC frame header is valid and, if so, reads it and writes the frame
* first sample number in {@code sampleNumberHolder}.
*
* <p>If the header is valid, the position of {@code scratch} is moved to the byte following it.
* <p>If the header is valid, the position of {@code data} 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
* @param data 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.
@ -46,13 +49,13 @@ public final class FlacFrameReader {
* @return Whether the frame header is valid.
*/
public static boolean checkAndReadFrameHeader(
ParsableByteArray scratch,
ParsableByteArray data,
FlacStreamMetadata flacStreamMetadata,
int frameStartMarker,
SampleNumberHolder sampleNumberHolder) {
int frameStartPosition = scratch.getPosition();
int frameStartPosition = data.getPosition();
long frameHeaderBytes = scratch.readUnsignedInt();
long frameHeaderBytes = data.readUnsignedInt();
if (frameHeaderBytes >>> 16 != frameStartMarker) {
return false;
}
@ -67,10 +70,10 @@ public final class FlacFrameReader {
&& checkBitsPerSample(bitsPerSampleKey, flacStreamMetadata)
&& !reservedBit
&& checkAndReadFirstSampleNumber(
scratch, flacStreamMetadata, isBlockSizeVariable, sampleNumberHolder)
&& checkAndReadBlockSizeSamples(scratch, flacStreamMetadata, blockSizeKey)
&& checkAndReadSampleRate(scratch, flacStreamMetadata, sampleRateKey)
&& checkAndReadCrc(scratch, frameStartPosition);
data, flacStreamMetadata, isBlockSizeVariable, sampleNumberHolder)
&& checkAndReadBlockSizeSamples(data, flacStreamMetadata, blockSizeKey)
&& checkAndReadSampleRate(data, flacStreamMetadata, sampleRateKey)
&& checkAndReadCrc(data, frameStartPosition);
}
/**
@ -161,12 +164,12 @@ public final class FlacFrameReader {
/**
* Reads the given block size.
*
* @param scratch The array to read the data from, whose position must correspond to the block
* size bits.
* @param data 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) {
public static int readFrameBlockSizeSamplesFromKey(ParsableByteArray data, int blockSizeKey) {
switch (blockSizeKey) {
case 1:
return 192;
@ -176,9 +179,9 @@ public final class FlacFrameReader {
case 5:
return 576 << (blockSizeKey - 2);
case 6:
return scratch.readUnsignedByte() + 1;
return data.readUnsignedByte() + 1;
case 7:
return scratch.readUnsignedShort() + 1;
return data.readUnsignedShort() + 1;
case 8:
case 9:
case 10:
@ -230,10 +233,10 @@ public final class FlacFrameReader {
* Checks whether the given sample number is valid and, if so, reads it and writes it in {@code
* sampleNumberHolder}.
*
* <p>If the sample number is valid, the position of {@code scratch} is moved to the byte
* following it. Otherwise, there is no guarantee on the position.
* <p>If the sample number is valid, the position of {@code data} 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 sample
* @param data The array to read the data from, whose position must correspond to the sample
* number data.
* @param flacStreamMetadata The stream metadata.
* @param isBlockSizeVariable Whether the stream blocking strategy is variable block size or fixed
@ -242,13 +245,13 @@ public final class FlacFrameReader {
* @return Whether the sample number is valid.
*/
private static boolean checkAndReadFirstSampleNumber(
ParsableByteArray scratch,
ParsableByteArray data,
FlacStreamMetadata flacStreamMetadata,
boolean isBlockSizeVariable,
SampleNumberHolder sampleNumberHolder) {
long utf8Value;
try {
utf8Value = scratch.readUtf8EncodedLong();
utf8Value = data.readUtf8EncodedLong();
} catch (NumberFormatException e) {
return false;
}
@ -262,18 +265,18 @@ public final class FlacFrameReader {
* Checks whether the given frame block size key and block size bits are valid and, if so, reads
* the block size bits.
*
* <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.
* <p>If the block size is valid, the position of {@code data} 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 data 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.
* @return Whether the block size is valid.
*/
private static boolean checkAndReadBlockSizeSamples(
ParsableByteArray scratch, FlacStreamMetadata flacStreamMetadata, int blockSizeKey) {
int blockSizeSamples = readFrameBlockSizeSamplesFromKey(scratch, blockSizeKey);
ParsableByteArray data, FlacStreamMetadata flacStreamMetadata, int blockSizeKey) {
int blockSizeSamples = readFrameBlockSizeSamplesFromKey(data, blockSizeKey);
return blockSizeSamples != -1 && blockSizeSamples <= flacStreamMetadata.maxBlockSizeSamples;
}
@ -281,26 +284,25 @@ public final class FlacFrameReader {
* 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.
* <p>If the sample rate is valid, the position of {@code data} 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 data 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) {
ParsableByteArray data, 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;
return data.readUnsignedByte() * 1000 == expectedSampleRate;
} else if (sampleRateKey <= 14) {
int sampleRate = scratch.readUnsignedShort();
int sampleRate = data.readUnsignedShort();
if (sampleRateKey == 14) {
sampleRate *= 10;
}
@ -313,20 +315,20 @@ public final class FlacFrameReader {
/**
* 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.
* <p>If the CRC is valid, the position of {@code data} 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.
* <p>The {@code data} 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}.
* @param data The array to read the data from, whose position must indicate the CRC.
* @param frameStartPosition The frame start offset in {@code data}.
* @return Whether the CRC is valid.
*/
private static boolean checkAndReadCrc(ParsableByteArray scratch, int frameStartPosition) {
int crc = scratch.readUnsignedByte();
int frameEndPosition = scratch.getPosition();
private static boolean checkAndReadCrc(ParsableByteArray data, int frameStartPosition) {
int crc = data.readUnsignedByte();
int frameEndPosition = data.getPosition();
int expectedCrc =
Util.crc8(scratch.data, frameStartPosition, frameEndPosition - 1, /* initialValue= */ 0);
Util.crc8(data.data, frameStartPosition, frameEndPosition - 1, /* initialValue= */ 0);
return crc == expectedCrc;
}

View File

@ -32,7 +32,10 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/** Reads and peeks FLAC stream metadata elements from an {@link ExtractorInput}. */
/**
* Reads and peeks FLAC stream metadata elements according to the <a
* href="https://xiph.org/flac/format.html">FLAC format specification</a>.
*/
public final class FlacMetadataReader {
/** Holds a {@link FlacStreamMetadata}. */
@ -47,9 +50,7 @@ public final class FlacMetadataReader {
private static final int STREAM_MARKER = 0x664C6143; // ASCII for "fLaC"
private static final int SYNC_CODE = 0x3FFE;
private static final int STREAM_INFO_TYPE = 0;
private static final int VORBIS_COMMENT_TYPE = 4;
private static final int PICTURE_TYPE = 6;
private static final int SEEK_POINT_SIZE = 18;
/**
* Peeks ID3 Data.
@ -167,18 +168,21 @@ public final class FlacMetadataReader {
boolean isLastMetadataBlock = scratch.readBit();
int type = scratch.readBits(7);
int length = FlacConstants.METADATA_BLOCK_HEADER_SIZE + scratch.readBits(24);
if (type == STREAM_INFO_TYPE) {
if (type == FlacConstants.METADATA_TYPE_STREAM_INFO) {
metadataHolder.flacStreamMetadata = readStreamInfoBlock(input);
} else {
FlacStreamMetadata flacStreamMetadata = metadataHolder.flacStreamMetadata;
if (flacStreamMetadata == null) {
throw new IllegalArgumentException();
}
if (type == VORBIS_COMMENT_TYPE) {
if (type == FlacConstants.METADATA_TYPE_SEEK_TABLE) {
FlacStreamMetadata.SeekTable seekTable = readSeekTableMetadataBlock(input, length);
metadataHolder.flacStreamMetadata = flacStreamMetadata.copyWithSeekTable(seekTable);
} else if (type == FlacConstants.METADATA_TYPE_VORBIS_COMMENT) {
List<String> vorbisComments = readVorbisCommentMetadataBlock(input, length);
metadataHolder.flacStreamMetadata =
flacStreamMetadata.copyWithVorbisComments(vorbisComments);
} else if (type == PICTURE_TYPE) {
} else if (type == FlacConstants.METADATA_TYPE_PICTURE) {
PictureFrame pictureFrame = readPictureMetadataBlock(input, length);
metadataHolder.flacStreamMetadata =
flacStreamMetadata.copyWithPictureFrames(Collections.singletonList(pictureFrame));
@ -190,6 +194,42 @@ public final class FlacMetadataReader {
return isLastMetadataBlock;
}
/**
* Reads a FLAC seek table metadata block.
*
* <p>The position of {@code data} is moved to the byte following the seek table metadata block
* (placeholder points included).
*
* @param data The array to read the data from, whose position must correspond to the seek table
* metadata block (header included).
* @return The seek table, without the placeholder points.
*/
public static FlacStreamMetadata.SeekTable readSeekTableMetadataBlock(ParsableByteArray data) {
data.skipBytes(1);
int length = data.readUnsignedInt24();
long seekTableEndPosition = data.getPosition() + length;
int seekPointCount = length / SEEK_POINT_SIZE;
long[] pointSampleNumbers = new long[seekPointCount];
long[] pointOffsets = new long[seekPointCount];
for (int i = 0; i < seekPointCount; i++) {
// The sample number is expected to fit in a signed long, except if it is a placeholder, in
// which case its value is -1.
long sampleNumber = data.readLong();
if (sampleNumber == -1) {
pointSampleNumbers = Arrays.copyOf(pointSampleNumbers, i);
pointOffsets = Arrays.copyOf(pointOffsets, i);
break;
}
pointSampleNumbers[i] = sampleNumber;
pointOffsets[i] = data.readLong();
data.skipBytes(2);
}
data.skipBytes((int) (seekTableEndPosition - data.getPosition()));
return new FlacStreamMetadata.SeekTable(pointSampleNumbers, pointOffsets);
}
/**
* Returns the frame start marker, consisting of the 2 first bytes of the first frame.
*
@ -227,6 +267,13 @@ public final class FlacMetadataReader {
scratchData, /* offset= */ FlacConstants.METADATA_BLOCK_HEADER_SIZE);
}
private static FlacStreamMetadata.SeekTable readSeekTableMetadataBlock(
ExtractorInput input, int length) throws IOException, InterruptedException {
ParsableByteArray scratch = new ParsableByteArray(length);
input.readFully(scratch.data, 0, length);
return readSeekTableMetadataBlock(scratch);
}
private static List<String> readVorbisCommentMetadataBlock(ExtractorInput input, int length)
throws IOException, InterruptedException {
ParsableByteArray scratch = new ParsableByteArray(length);

View File

@ -0,0 +1,84 @@
/*
* 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;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.FlacStreamMetadata;
import com.google.android.exoplayer2.util.Util;
/**
* A {@link SeekMap} implementation for FLAC streams that contain a <a
* href="https://xiph.org/flac/format.html#metadata_block_seektable">seek table</a>.
*/
public final class FlacSeekTableSeekMap implements SeekMap {
private final FlacStreamMetadata flacStreamMetadata;
private final long firstFrameOffset;
/**
* Creates a seek map from the FLAC stream seek table.
*
* @param flacStreamMetadata The stream metadata.
* @param firstFrameOffset The byte offset of the first frame in the stream.
*/
public FlacSeekTableSeekMap(FlacStreamMetadata flacStreamMetadata, long firstFrameOffset) {
this.flacStreamMetadata = flacStreamMetadata;
this.firstFrameOffset = firstFrameOffset;
}
@Override
public boolean isSeekable() {
return true;
}
@Override
public long getDurationUs() {
return flacStreamMetadata.getDurationUs();
}
@Override
public SeekPoints getSeekPoints(long timeUs) {
Assertions.checkNotNull(flacStreamMetadata.seekTable);
long[] pointSampleNumbers = flacStreamMetadata.seekTable.pointSampleNumbers;
long[] pointOffsets = flacStreamMetadata.seekTable.pointOffsets;
long targetSampleNumber = flacStreamMetadata.getSampleNumber(timeUs);
int index =
Util.binarySearchFloor(
pointSampleNumbers,
targetSampleNumber,
/* inclusive= */ true,
/* stayInBounds= */ false);
long seekPointSampleNumber = index == -1 ? 0 : pointSampleNumbers[index];
long seekPointOffsetFromFirstFrame = index == -1 ? 0 : pointOffsets[index];
SeekPoint seekPoint = getSeekPoint(seekPointSampleNumber, seekPointOffsetFromFirstFrame);
if (seekPoint.timeUs == timeUs || index == pointSampleNumbers.length - 1) {
return new SeekPoints(seekPoint);
} else {
SeekPoint secondSeekPoint =
getSeekPoint(pointSampleNumbers[index + 1], pointOffsets[index + 1]);
return new SeekPoints(seekPoint, secondSeekPoint);
}
}
private SeekPoint getSeekPoint(long sampleNumber, long offsetFromFirstFrame) {
long seekTimeUs = sampleNumber * C.MICROS_PER_SECOND / flacStreamMetadata.sampleRate;
long seekPosition = firstFrameOffset + offsetFromFirstFrame;
return new SeekPoint(seekTimeUs, seekPosition);
}
}

View File

@ -27,6 +27,7 @@ import com.google.android.exoplayer2.extractor.ExtractorsFactory;
import com.google.android.exoplayer2.extractor.FlacFrameReader;
import com.google.android.exoplayer2.extractor.FlacFrameReader.SampleNumberHolder;
import com.google.android.exoplayer2.extractor.FlacMetadataReader;
import com.google.android.exoplayer2.extractor.FlacSeekTableSeekMap;
import com.google.android.exoplayer2.extractor.PositionHolder;
import com.google.android.exoplayer2.extractor.SeekMap;
import com.google.android.exoplayer2.extractor.TrackOutput;
@ -41,7 +42,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
// TODO: implement seeking using the optional seek table.
/**
* Extracts data from FLAC container format.
*
@ -175,11 +175,10 @@ public final class FlacExtractor implements Extractor {
public void seek(long position, long timeUs) {
if (position == 0) {
state = STATE_READ_ID3_METADATA;
currentFrameFirstSampleNumber = 0;
} else if (binarySearchSeeker != null) {
currentFrameFirstSampleNumber = SAMPLE_NUMBER_UNKNOWN;
binarySearchSeeker.setSeekTargetUs(timeUs);
}
currentFrameFirstSampleNumber = timeUs == 0 ? 0 : SAMPLE_NUMBER_UNKNOWN;
currentFrameBytesWritten = 0;
scratch.reset();
}
@ -231,7 +230,7 @@ public final class FlacExtractor implements Extractor {
castNonNull(extractorOutput)
.seekMap(
getSeekMap(
/* firstFramePosition= */ (int) input.getPosition(),
/* firstFramePosition= */ input.getPosition(),
/* streamLength= */ input.getLength()));
state = STATE_READ_FRAMES;
@ -242,7 +241,7 @@ public final class FlacExtractor implements Extractor {
Assertions.checkNotNull(trackOutput);
Assertions.checkNotNull(flacStreamMetadata);
// Handle pending seek if necessary.
// Handle pending binary search seek if necessary.
if (binarySearchSeeker != null && binarySearchSeeker.isSeeking()) {
return binarySearchSeeker.handlePendingSeek(input, seekPosition);
}
@ -299,39 +298,42 @@ public final class FlacExtractor implements Extractor {
return Extractor.RESULT_CONTINUE;
}
private SeekMap getSeekMap(int firstFramePosition, long streamLength) {
private SeekMap getSeekMap(long firstFramePosition, long streamLength) {
Assertions.checkNotNull(flacStreamMetadata);
if (streamLength == C.LENGTH_UNSET || flacStreamMetadata.totalSamples == 0) {
if (flacStreamMetadata.seekTable != null) {
return new FlacSeekTableSeekMap(flacStreamMetadata, firstFramePosition);
} else if (streamLength != C.LENGTH_UNSET && flacStreamMetadata.totalSamples > 0) {
binarySearchSeeker =
new FlacBinarySearchSeeker(
flacStreamMetadata, frameStartMarker, firstFramePosition, streamLength);
return binarySearchSeeker.getSeekMap();
} else {
return new SeekMap.Unseekable(flacStreamMetadata.getDurationUs());
}
binarySearchSeeker =
new FlacBinarySearchSeeker(
flacStreamMetadata, frameStartMarker, firstFramePosition, streamLength);
return binarySearchSeeker.getSeekMap();
}
/**
* Searches for the start of a frame in {@code scratch}.
* Searches for the start of a frame in {@code data}.
*
* <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}.
* @param data The array to be searched.
* @param foundEndOfInput If the end of input was met when filling in the {@code data}.
* @return The number of the first sample in the frame found, or {@code SAMPLE_NUMBER_UNKNOWN} if
* the search was not successful.
*/
private long findFrame(ParsableByteArray scratch, boolean foundEndOfInput) {
private long findFrame(ParsableByteArray data, boolean foundEndOfInput) {
Assertions.checkNotNull(flacStreamMetadata);
int frameOffset = scratch.getPosition();
while (frameOffset <= scratch.limit() - FlacConstants.MAX_FRAME_HEADER_SIZE) {
scratch.setPosition(frameOffset);
int frameOffset = data.getPosition();
while (frameOffset <= data.limit() - FlacConstants.MAX_FRAME_HEADER_SIZE) {
data.setPosition(frameOffset);
if (FlacFrameReader.checkAndReadFrameHeader(
scratch, flacStreamMetadata, frameStartMarker, sampleNumberHolder)) {
scratch.setPosition(frameOffset);
data, flacStreamMetadata, frameStartMarker, sampleNumberHolder)) {
data.setPosition(frameOffset);
return sampleNumberHolder.sampleNumber;
}
frameOffset++;
@ -339,9 +341,9 @@ public final class FlacExtractor implements Extractor {
if (foundEndOfInput) {
// Reached the end of the file. Assume it's the end of the frame.
scratch.setPosition(scratch.limit());
data.setPosition(data.limit());
} else {
scratch.setPosition(frameOffset);
data.setPosition(frameOffset);
}
return SAMPLE_NUMBER_UNKNOWN;

View File

@ -17,8 +17,11 @@ package com.google.android.exoplayer2.extractor.ogg;
import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.extractor.FlacFrameReader;
import com.google.android.exoplayer2.extractor.FlacMetadataReader;
import com.google.android.exoplayer2.extractor.FlacSeekTableSeekMap;
import com.google.android.exoplayer2.extractor.SeekMap;
import com.google.android.exoplayer2.extractor.SeekPoint;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.FlacConstants;
import com.google.android.exoplayer2.util.FlacStreamMetadata;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.Util;
@ -31,7 +34,6 @@ import java.util.Arrays;
/* package */ final class FlacReader extends StreamReader {
private static final byte AUDIO_PACKET_TYPE = (byte) 0xFF;
private static final byte SEEKTABLE_PACKET_TYPE = 0x03;
private static final int FRAME_HEADER_SAMPLE_NUMBER_OFFSET = 4;
@ -71,9 +73,11 @@ import java.util.Arrays;
streamMetadata = new FlacStreamMetadata(data, 17);
byte[] metadata = Arrays.copyOfRange(data, 9, packet.limit());
setupData.format = streamMetadata.getFormat(metadata, /* id3Metadata= */ null);
} else if ((data[0] & 0x7F) == SEEKTABLE_PACKET_TYPE) {
} else if ((data[0] & 0x7F) == FlacConstants.METADATA_TYPE_SEEK_TABLE) {
flacOggSeeker = new FlacOggSeeker();
flacOggSeeker.parseSeekTable(packet);
FlacStreamMetadata.SeekTable seekTable =
FlacMetadataReader.readSeekTableMetadataBlock(packet);
streamMetadata = streamMetadata.copyWithSeekTable(seekTable);
} else if (isAudioPacket(data)) {
if (flacOggSeeker != null) {
flacOggSeeker.setFirstFrameOffset(position);
@ -96,13 +100,8 @@ import java.util.Arrays;
return result;
}
private class FlacOggSeeker implements OggSeeker, SeekMap {
private class FlacOggSeeker implements OggSeeker {
private static final int METADATA_LENGTH_OFFSET = 1;
private static final int SEEK_POINT_SIZE = 18;
private long[] seekPointGranules;
private long[] seekPointOffsets;
private long firstFrameOffset;
private long pendingSeekGranule;
@ -115,27 +114,6 @@ import java.util.Arrays;
this.firstFrameOffset = firstFrameOffset;
}
/**
* Parses a FLAC file seek table metadata structure and initializes internal fields.
*
* @param data A {@link ParsableByteArray} including whole seek table metadata block. Its
* position should be set to the beginning of the block.
* @see <a href="https://xiph.org/flac/format.html#metadata_block_seektable">FLAC format
* METADATA_BLOCK_SEEKTABLE</a>
*/
public void parseSeekTable(ParsableByteArray data) {
data.skipBytes(METADATA_LENGTH_OFFSET);
int length = data.readUnsignedInt24();
int numberOfSeekPoints = length / SEEK_POINT_SIZE;
seekPointGranules = new long[numberOfSeekPoints];
seekPointOffsets = new long[numberOfSeekPoints];
for (int i = 0; i < numberOfSeekPoints; i++) {
seekPointGranules[i] = data.readLong();
seekPointOffsets[i] = data.readLong();
data.skipBytes(2); // Skip "Number of samples in the target frame."
}
}
@Override
public long read(ExtractorInput input) throws IOException, InterruptedException {
if (pendingSeekGranule >= 0) {
@ -148,40 +126,16 @@ import java.util.Arrays;
@Override
public void startSeek(long targetGranule) {
Assertions.checkNotNull(streamMetadata.seekTable);
long[] seekPointGranules = streamMetadata.seekTable.pointSampleNumbers;
int index = Util.binarySearchFloor(seekPointGranules, targetGranule, true, true);
pendingSeekGranule = seekPointGranules[index];
}
@Override
public SeekMap createSeekMap() {
return this;
}
@Override
public boolean isSeekable() {
return true;
}
@Override
public SeekPoints getSeekPoints(long timeUs) {
long granule = convertTimeToGranule(timeUs);
int index = Util.binarySearchFloor(seekPointGranules, granule, true, true);
long seekTimeUs = convertGranuleToTime(seekPointGranules[index]);
long seekPosition = firstFrameOffset + seekPointOffsets[index];
SeekPoint seekPoint = new SeekPoint(seekTimeUs, seekPosition);
if (seekTimeUs >= timeUs || index == seekPointGranules.length - 1) {
return new SeekPoints(seekPoint);
} else {
long secondSeekTimeUs = convertGranuleToTime(seekPointGranules[index + 1]);
long secondSeekPosition = firstFrameOffset + seekPointOffsets[index + 1];
SeekPoint secondSeekPoint = new SeekPoint(secondSeekTimeUs, secondSeekPosition);
return new SeekPoints(seekPoint, secondSeekPoint);
}
}
@Override
public long getDurationUs() {
return streamMetadata.getDurationUs();
Assertions.checkState(firstFrameOffset != -1);
return new FlacSeekTableSeekMap(streamMetadata, firstFrameOffset);
}
}

View File

@ -29,5 +29,14 @@ public final class FlacConstants {
/** Maximum size of a FLAC frame header in bytes. */
public static final int MAX_FRAME_HEADER_SIZE = 16;
/** Stream info metadata block type. */
public static final int METADATA_TYPE_STREAM_INFO = 0;
/** Seek table metadata block type. */
public static final int METADATA_TYPE_SEEK_TABLE = 3;
/** Vorbis comment metadata block type. */
public static final int METADATA_TYPE_VORBIS_COMMENT = 4;
/** Picture metadata block type. */
public static final int METADATA_TYPE_PICTURE = 6;
private FlacConstants() {}
}

View File

@ -30,6 +30,8 @@ import java.util.List;
*
* @see <a href="https://xiph.org/flac/format.html#metadata_block_streaminfo">FLAC format
* METADATA_BLOCK_STREAMINFO</a>
* @see <a href="https://xiph.org/flac/format.html#metadata_block_seektable">FLAC format
* METADATA_BLOCK_SEEKTABLE</a>
* @see <a href="https://xiph.org/flac/format.html#metadata_block_vorbis_comment">FLAC format
* METADATA_BLOCK_VORBIS_COMMENT</a>
* @see <a href="https://xiph.org/flac/format.html#metadata_block_picture">FLAC format
@ -37,6 +39,19 @@ import java.util.List;
*/
public final class FlacStreamMetadata {
/** A FLAC seek table. */
public static class SeekTable {
/** Seek points sample numbers. */
public final long[] pointSampleNumbers;
/** Seek points byte offsets from the first frame. */
public final long[] pointOffsets;
public SeekTable(long[] pointSampleNumbers, long[] pointOffsets) {
this.pointSampleNumbers = pointSampleNumbers;
this.pointOffsets = pointOffsets;
}
}
private static final String TAG = "FlacStreamMetadata";
/** Indicates that a value is not in the corresponding lookup table. */
@ -79,7 +94,8 @@ public final class FlacStreamMetadata {
public final int bitsPerSampleLookupKey;
/** Total number of samples, or 0 if the value is unknown. */
public final long totalSamples;
/** Seek table, or {@code null} if it is not provided. */
@Nullable public final SeekTable seekTable;
/** Content metadata, or {@code null} if it is not provided. */
@Nullable private final Metadata metadata;
@ -102,6 +118,7 @@ public final class FlacStreamMetadata {
bitsPerSample = scratch.readBits(5) + 1;
bitsPerSampleLookupKey = getBitsPerSampleLookupKey(bitsPerSample);
totalSamples = scratch.readBitsToLong(36);
seekTable = null;
metadata = null;
}
@ -126,6 +143,7 @@ public final class FlacStreamMetadata {
channels,
bitsPerSample,
totalSamples,
/* seekTable= */ null,
buildMetadata(vorbisComments, pictureFrames));
}
@ -138,6 +156,7 @@ public final class FlacStreamMetadata {
int channels,
int bitsPerSample,
long totalSamples,
@Nullable SeekTable seekTable,
@Nullable Metadata metadata) {
this.minBlockSizeSamples = minBlockSizeSamples;
this.maxBlockSizeSamples = maxBlockSizeSamples;
@ -149,6 +168,7 @@ public final class FlacStreamMetadata {
this.bitsPerSample = bitsPerSample;
this.bitsPerSampleLookupKey = getBitsPerSampleLookupKey(bitsPerSample);
this.totalSamples = totalSamples;
this.seekTable = seekTable;
this.metadata = metadata;
}
@ -239,6 +259,21 @@ public final class FlacStreamMetadata {
return metadata == null ? other : metadata.copyWithAppendedEntriesFrom(other);
}
/** Returns a copy of {@code this} with the seek table replaced by the one given. */
public FlacStreamMetadata copyWithSeekTable(@Nullable SeekTable seekTable) {
return new FlacStreamMetadata(
minBlockSizeSamples,
maxBlockSizeSamples,
minFrameSize,
maxFrameSize,
sampleRate,
channels,
bitsPerSample,
totalSamples,
seekTable,
metadata);
}
/** Returns a copy of {@code this} with the given Vorbis comments added to the metadata. */
public FlacStreamMetadata copyWithVorbisComments(List<String> vorbisComments) {
@Nullable
@ -254,6 +289,7 @@ public final class FlacStreamMetadata {
channels,
bitsPerSample,
totalSamples,
seekTable,
appendedMetadata);
}
@ -272,6 +308,7 @@ public final class FlacStreamMetadata {
channels,
bitsPerSample,
totalSamples,
seekTable,
appendedMetadata);
}

View File

@ -27,9 +27,13 @@ track 0:
metadata = null
initializationData:
data = length 42, hash 83F6895
total output bytes = 445
sample count = 1
total output bytes = 3829
sample count = 2
sample 0:
time = 2645333
flags = 1
data = length 3384, hash 938BCDD9
sample 1:
time = 2730666
flags = 1
data = length 445, hash A388E3D6

View File

@ -27,9 +27,13 @@ track 0:
metadata = null
initializationData:
data = length 42, hash 9218FDB7
total output bytes = 445
sample count = 1
total output bytes = 3829
sample count = 2
sample 0:
time = 2645333
flags = 1
data = length 3384, hash 938BCDD9
sample 1:
time = 2730666
flags = 1
data = length 445, hash A388E3D6

View File

@ -1,164 +0,0 @@
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 = -
metadata = null
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

View File

@ -1,7 +1,7 @@
seekMap:
isSeekable = false
isSeekable = true
duration = UNSET TIME
getPosition(0) = [[timeUs=0, position=0]]
getPosition(0) = [[timeUs=0, position=8880]]
numberOfTracks = 1
track 0:
format:

View File

@ -1,6 +1,6 @@
seekMap:
isSeekable = false
duration = 2741000
duration = UNSET TIME
getPosition(0) = [[timeUs=0, position=0]]
numberOfTracks = 1
track 0:
@ -26,7 +26,7 @@ track 0:
drmInitData = -
metadata = null
initializationData:
data = length 42, hash 83F6895
data = length 42, hash 49FA2C21
total output bytes = 164431
sample count = 33
sample 0:

View File

@ -1,7 +1,7 @@
seekMap:
isSeekable = true
duration = 2741000
getPosition(0) = [[timeUs=0, position=8288]]
getPosition(0) = [[timeUs=0, position=8230]]
numberOfTracks = 1
track 0:
format:

View File

@ -1,7 +1,7 @@
seekMap:
isSeekable = true
duration = 2741000
getPosition(0) = [[timeUs=0, position=8288]]
getPosition(0) = [[timeUs=0, position=8230]]
numberOfTracks = 1
track 0:
format:

View File

@ -1,7 +1,7 @@
seekMap:
isSeekable = true
duration = 2741000
getPosition(0) = [[timeUs=0, position=8288]]
getPosition(0) = [[timeUs=0, position=8230]]
numberOfTracks = 1
track 0:
format:

View File

@ -1,7 +1,7 @@
seekMap:
isSeekable = true
duration = 2741000
getPosition(0) = [[timeUs=0, position=8288]]
getPosition(0) = [[timeUs=0, position=8230]]
numberOfTracks = 1
track 0:
format:
@ -27,9 +27,13 @@ track 0:
metadata = null
initializationData:
data = length 42, hash 7249A1B8
total output bytes = 548
sample count = 1
total output bytes = 3385
sample count = 2
sample 0:
time = 2618181
flags = 1
data = length 2837, hash 10F1716E
sample 1:
time = 2722909
flags = 1
data = length 548, hash B46F603C

View File

@ -1,140 +0,0 @@
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 = -
metadata = null
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

View File

@ -1,123 +0,0 @@
seekMap:
isSeekable = true
duration = 2741000
getPosition(0) = [[timeUs=0, position=55284]]
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 = 113666
sample count = 23
sample 0:
time = 853333
flags = 1
data = length 5031, hash 9D55EA53
sample 1:
time = 938666
flags = 1
data = length 5119, hash E1CB9BA6
sample 2:
time = 1024000
flags = 1
data = length 5360, hash 17265C5D
sample 3:
time = 1109333
flags = 1
data = length 5340, hash A90FDDF1
sample 4:
time = 1194666
flags = 1
data = length 5162, hash 31F65AD5
sample 5:
time = 1280000
flags = 1
data = length 5168, hash F2394F2D
sample 6:
time = 1365333
flags = 1
data = length 5776, hash 58437AB3
sample 7:
time = 1450666
flags = 1
data = length 5394, hash EBAB20A8
sample 8:
time = 1536000
flags = 1
data = length 5168, hash BF37C7A5
sample 9:
time = 1621333
flags = 1
data = length 5324, hash 59546B7B
sample 10:
time = 1706666
flags = 1
data = length 5172, hash 6036EF0B
sample 11:
time = 1792000
flags = 1
data = length 5102, hash 5A131071
sample 12:
time = 1877333
flags = 1
data = length 5111, hash 3D9EBB3B
sample 13:
time = 1962666
flags = 1
data = length 5113, hash 61101D4F
sample 14:
time = 2048000
flags = 1
data = length 5229, hash D2E55742
sample 15:
time = 2133333
flags = 1
data = length 5162, hash 7F2E97FA
sample 16:
time = 2218666
flags = 1
data = length 5255, hash D92A782
sample 17:
time = 2304000
flags = 1
data = length 5196, hash 98FE5138
sample 18:
time = 2389333
flags = 1
data = length 5214, hash 3D35C38C
sample 19:
time = 2474666
flags = 1
data = length 5211, hash 7E25420F
sample 20:
time = 2560000
flags = 1
data = length 5230, hash 2AD96FBC
sample 21:
time = 2645333
flags = 1
data = length 3384, hash 938BCDD9
sample 22:
time = 2730666
flags = 1
data = length 445, hash A388E3D6
tracksEnded = true

View File

@ -1,79 +0,0 @@
seekMap:
isSeekable = true
duration = 2741000
getPosition(0) = [[timeUs=0, position=55284]]
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 = 55652
sample count = 12
sample 0:
time = 1792000
flags = 1
data = length 5102, hash 5A131071
sample 1:
time = 1877333
flags = 1
data = length 5111, hash 3D9EBB3B
sample 2:
time = 1962666
flags = 1
data = length 5113, hash 61101D4F
sample 3:
time = 2048000
flags = 1
data = length 5229, hash D2E55742
sample 4:
time = 2133333
flags = 1
data = length 5162, hash 7F2E97FA
sample 5:
time = 2218666
flags = 1
data = length 5255, hash D92A782
sample 6:
time = 2304000
flags = 1
data = length 5196, hash 98FE5138
sample 7:
time = 2389333
flags = 1
data = length 5214, hash 3D35C38C
sample 8:
time = 2474666
flags = 1
data = length 5211, hash 7E25420F
sample 9:
time = 2560000
flags = 1
data = length 5230, hash 2AD96FBC
sample 10:
time = 2645333
flags = 1
data = length 3384, hash 938BCDD9
sample 11:
time = 2730666
flags = 1
data = length 445, hash A388E3D6
tracksEnded = true

View File

@ -1,35 +0,0 @@
seekMap:
isSeekable = true
duration = 2741000
getPosition(0) = [[timeUs=0, position=55284]]
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 = 445
sample count = 1
sample 0:
time = 2730666
flags = 1
data = length 445, hash A388E3D6
tracksEnded = true

View File

@ -1,163 +0,0 @@
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

View File

@ -27,9 +27,13 @@ track 0:
metadata = null
initializationData:
data = length 42, hash 83F6895
total output bytes = 445
sample count = 1
total output bytes = 3829
sample count = 2
sample 0:
time = 2645333
flags = 1
data = length 3384, hash 938BCDD9
sample 1:
time = 2730666
flags = 1
data = length 445, hash A388E3D6

View File

@ -1,164 +0,0 @@
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 = -
metadata = null
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

View File

@ -27,9 +27,13 @@ track 0:
metadata = entries=[TXXX: description=ID: value=105519843, TIT2: description=null: value=那么爱你为什么, TPE1: description=null: value=阿强, TALB: description=null: value=华丽的外衣, TXXX: description=ID: value=105519843, APIC: mimeType=image/jpeg, description=]
initializationData:
data = length 42, hash 83F6895
total output bytes = 445
sample count = 1
total output bytes = 3829
sample count = 2
sample 0:
time = 2645333
flags = 1
data = length 3384, hash 938BCDD9
sample 1:
time = 2730666
flags = 1
data = length 445, hash A388E3D6

View File

@ -1,164 +0,0 @@
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 = -
metadata = entries=[TXXX: description=ID: value=105519843, TIT2: description=null: value=那么爱你为什么, TPE1: description=null: value=阿强, TALB: description=null: value=华丽的外衣, TXXX: description=ID: value=105519843, APIC: mimeType=image/jpeg, description=]
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

View File

@ -27,9 +27,13 @@ track 0:
metadata = entries=[Picture: mimeType=image/png, description=]
initializationData:
data = length 42, hash 83F6895
total output bytes = 445
sample count = 1
total output bytes = 3829
sample count = 2
sample 0:
time = 2645333
flags = 1
data = length 3384, hash 938BCDD9
sample 1:
time = 2730666
flags = 1
data = length 445, hash A388E3D6

View File

@ -1,164 +0,0 @@
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 = -
metadata = entries=[Picture: mimeType=image/png, description=]
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

View File

@ -27,9 +27,13 @@ track 0:
metadata = entries=[VC: TITLE=test title, VC: ARTIST=test artist]
initializationData:
data = length 42, hash 83F6895
total output bytes = 445
sample count = 1
total output bytes = 3829
sample count = 2
sample 0:
time = 2645333
flags = 1
data = length 3384, hash 938BCDD9
sample 1:
time = 2730666
flags = 1
data = length 445, hash A388E3D6

View File

@ -1,164 +0,0 @@
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 = -
metadata = entries=[VC: TITLE=test title, VC: ARTIST=test artist]
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

View File

@ -20,7 +20,6 @@ import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.testutil.ExtractorAsserts;
import com.google.android.exoplayer2.testutil.ExtractorAsserts.ExtractorFactory;
import com.google.android.exoplayer2.testutil.TestUtil;
import java.io.IOException;
import org.junit.Test;
@ -32,41 +31,49 @@ public class FlacExtractorTest {
@Test
public void testSample() throws Exception {
assertBehavior(FlacExtractor::new, "flac/bear.flac");
ExtractorAsserts.assertBehavior(FlacExtractor::new, "flac/bear.flac");
}
@Test
public void testSampleWithId3HeaderAndId3Enabled() throws Exception {
assertBehavior(FlacExtractor::new, "flac/bear_with_id3_enabled.flac");
ExtractorAsserts.assertBehavior(FlacExtractor::new, "flac/bear_with_id3_enabled.flac");
}
@Test
public void testSampleWithId3HeaderAndId3Disabled() throws Exception {
// bear_with_id3_disabled.flac is identical to bear_with_id3_enabled.flac, but the dump file is
// different due to setting FLAG_DISABLE_ID3_METADATA.
assertBehavior(
ExtractorAsserts.assertBehavior(
() -> new FlacExtractor(FlacExtractor.FLAG_DISABLE_ID3_METADATA),
"flac/bear_with_id3_disabled.flac");
}
@Test
public void testSampleUnseekable() throws Exception {
ExtractorAsserts.assertBehavior(
FlacExtractor::new, "flac/bear_no_seek_table_no_num_samples.flac");
}
@Test
public void testSampleWithVorbisComments() throws Exception {
assertBehavior(FlacExtractor::new, "flac/bear_with_vorbis_comments.flac");
ExtractorAsserts.assertBehavior(FlacExtractor::new, "flac/bear_with_vorbis_comments.flac");
}
@Test
public void testSampleWithPicture() throws Exception {
assertBehavior(FlacExtractor::new, "flac/bear_with_picture.flac");
ExtractorAsserts.assertBehavior(FlacExtractor::new, "flac/bear_with_picture.flac");
}
@Test
public void testOneMetadataBlock() throws Exception {
assertBehavior(FlacExtractor::new, "flac/bear_one_metadata_block.flac");
// Don't simulate IO errors as it is too slow when using the binary search seek map (see
// [Internal: b/145994869]).
assertBehaviorWithoutSimulatingIOErrors("flac/bear_one_metadata_block.flac");
}
@Test
public void testNoMinMaxFrameSize() throws Exception {
assertBehavior(FlacExtractor::new, "flac/bear_no_min_max_frame_size.flac");
ExtractorAsserts.assertBehavior(FlacExtractor::new, "flac/bear_no_min_max_frame_size.flac");
}
@Test
@ -76,24 +83,63 @@ public class FlacExtractorTest {
@Test
public void testUncommonSampleRate() throws Exception {
assertBehavior(FlacExtractor::new, "flac/bear_uncommon_sample_rate.flac");
ExtractorAsserts.assertBehavior(FlacExtractor::new, "flac/bear_uncommon_sample_rate.flac");
}
private void assertBehavior(ExtractorFactory factory, String file)
private static void assertBehaviorWithoutSimulatingIOErrors(String file)
throws IOException, InterruptedException {
// Check behavior prior to initialization.
Extractor extractor = factory.create();
Extractor extractor = new FlacExtractor();
extractor.seek(0, 0);
extractor.release();
// Assert output.
Context context = ApplicationProvider.getApplicationContext();
byte[] data = TestUtil.getByteArray(context, file);
// Don't simulate IO errors as it is too slow (see b/145994869).
ExtractorAsserts.assertOutput(factory.create(), file, data, context, true, false, false, false);
ExtractorAsserts.assertOutput(factory.create(), file, data, context, true, false, false, true);
ExtractorAsserts.assertOutput(factory.create(), file, data, context, true, false, true, false);
ExtractorAsserts.assertOutput(factory.create(), file, data, context, true, false, true, true);
ExtractorAsserts.assertOutput(
factory.create(), file, data, context, false, false, false, false);
new FlacExtractor(),
file,
data,
context,
/* sniffFirst= */ true,
/* simulateIOErrors= */ false,
/* simulateUnknownLength= */ false,
/* simulatePartialReads= */ false);
ExtractorAsserts.assertOutput(
new FlacExtractor(),
file,
data,
context,
/* sniffFirst= */ true,
/* simulateIOErrors= */ false,
/* simulateUnknownLength= */ false,
/* simulatePartialReads= */ true);
ExtractorAsserts.assertOutput(
new FlacExtractor(),
file,
data,
context,
/* sniffFirst= */ true,
/* simulateIOErrors= */ false,
/* simulateUnknownLength= */ true,
/* simulatePartialReads= */ false);
ExtractorAsserts.assertOutput(
new FlacExtractor(),
file,
data,
context,
/* sniffFirst= */ true,
/* simulateIOErrors= */ false,
/* simulateUnknownLength= */ true,
/* simulatePartialReads= */ true);
ExtractorAsserts.assertOutput(
new FlacExtractor(),
file,
data,
context,
/* sniffFirst= */ false,
/* simulateIOErrors= */ false,
/* simulateUnknownLength= */ false,
/* simulatePartialReads= */ false);
}
}

View File

@ -208,18 +208,21 @@ public final class ExtractorAsserts {
extractorOutput.assertOutput(context, file + ".0" + DUMP_EXTENSION);
}
// If the SeekMap is seekable, test seeking to 4 positions in the stream.
// If the SeekMap is seekable, test seeking in the stream.
SeekMap seekMap = extractorOutput.seekMap;
if (seekMap.isSeekable()) {
long durationUs = seekMap.getDurationUs();
for (int j = 0; j < 4; j++) {
extractorOutput.clearTrackOutputs();
long timeUs = (durationUs * j) / 3;
long timeUs = durationUs == C.TIME_UNSET ? 0 : (durationUs * j) / 3;
long position = seekMap.getSeekPoints(timeUs).first.position;
input.reset();
input.setPosition((int) position);
consumeTestData(extractor, input, timeUs, extractorOutput, false);
extractorOutput.assertOutput(context, file + '.' + j + DUMP_EXTENSION);
if (durationUs == C.TIME_UNSET) {
break;
}
}
}