blob: 4359bbba26c0973f959db071df7b1c36f7775866 [file] [log] [blame]
/*
* 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 java.lang.Math.max;
import com.google.android.exoplayer2.extractor.BinarySearchSeeker;
import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.extractor.FlacFrameReader;
import com.google.android.exoplayer2.extractor.FlacFrameReader.SampleNumberHolder;
import com.google.android.exoplayer2.extractor.FlacStreamMetadata;
import com.google.android.exoplayer2.extractor.SeekMap;
import java.io.IOException;
/**
* A {@link SeekMap} implementation for FLAC stream using binary search.
*
* <p>This seeker performs seeking by using binary search within the stream, until it finds the
* frame that contains the target sample.
*/
/* package */ final class FlacBinarySearchSeeker extends BinarySearchSeeker {
/**
* Creates a {@link FlacBinarySearchSeeker}.
*
* @param flacStreamMetadata The stream metadata.
* @param frameStartMarker The frame start marker, consisting of the 2 bytes by which every frame
* in the stream must start.
* @param firstFramePosition The byte offset of the first frame in the stream.
* @param inputLength The length of the stream in bytes.
*/
public FlacBinarySearchSeeker(
FlacStreamMetadata flacStreamMetadata,
int frameStartMarker,
long firstFramePosition,
long inputLength) {
super(
/* seekTimestampConverter= */ flacStreamMetadata::getSampleNumber,
new FlacTimestampSeeker(flacStreamMetadata, frameStartMarker),
flacStreamMetadata.getDurationUs(),
/* floorTimePosition= */ 0,
/* ceilingTimePosition= */ flacStreamMetadata.totalSamples,
/* floorBytePosition= */ firstFramePosition,
/* ceilingBytePosition= */ inputLength,
/* approxBytesPerFrame= */ flacStreamMetadata.getApproxBytesPerFrame(),
/* minimumSearchRange= */ max(
FlacConstants.MIN_FRAME_HEADER_SIZE, flacStreamMetadata.minFrameSize));
}
private static final class FlacTimestampSeeker implements TimestampSeeker {
private final FlacStreamMetadata flacStreamMetadata;
private final int frameStartMarker;
private final SampleNumberHolder sampleNumberHolder;
private FlacTimestampSeeker(FlacStreamMetadata flacStreamMetadata, int frameStartMarker) {
this.flacStreamMetadata = flacStreamMetadata;
this.frameStartMarker = frameStartMarker;
sampleNumberHolder = new SampleNumberHolder();
}
@Override
public TimestampSearchResult searchForTimestamp(ExtractorInput input, long targetSampleNumber)
throws IOException {
long searchPosition = input.getPosition();
// Find left frame.
long leftFrameFirstSampleNumber = findNextFrame(input);
long leftFramePosition = input.getPeekPosition();
input.advancePeekPosition(
max(FlacConstants.MIN_FRAME_HEADER_SIZE, flacStreamMetadata.minFrameSize));
// Find right frame.
long rightFrameFirstSampleNumber = findNextFrame(input);
long rightFramePosition = input.getPeekPosition();
if (leftFrameFirstSampleNumber <= targetSampleNumber
&& rightFrameFirstSampleNumber > targetSampleNumber) {
return TimestampSearchResult.targetFoundResult(leftFramePosition);
} else if (rightFrameFirstSampleNumber <= targetSampleNumber) {
return TimestampSearchResult.underestimatedResult(
rightFrameFirstSampleNumber, rightFramePosition);
} else {
return TimestampSearchResult.overestimatedResult(
leftFrameFirstSampleNumber, searchPosition);
}
}
/**
* Searches for the next frame in {@code input}.
*
* <p>The peek position is advanced to the start of the found frame, or at the end of the stream
* if no frame was found.
*
* @param input The input from which to search (starting from the peek position).
* @return The number of the first sample in the found frame, or the total number of samples in
* the stream if no frame was found.
* @throws IOException If peeking from the input fails. In this case, there is no guarantee on
* the peek position.
*/
private long findNextFrame(ExtractorInput input) throws IOException {
while (input.getPeekPosition() < input.getLength() - FlacConstants.MIN_FRAME_HEADER_SIZE
&& !FlacFrameReader.checkFrameHeaderFromPeek(
input, flacStreamMetadata, frameStartMarker, sampleNumberHolder)) {
input.advancePeekPosition(1);
}
if (input.getPeekPosition() >= input.getLength() - FlacConstants.MIN_FRAME_HEADER_SIZE) {
input.advancePeekPosition((int) (input.getLength() - input.getPeekPosition()));
return flacStreamMetadata.totalSamples;
}
return sampleNumberHolder.sampleNumber;
}
}
}