blob: 5e2b743442c0b2aa8325d59213342a79e6b63ad1 [file] [log] [blame]
/*
* Copyright (C) 2018 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 static java.lang.Math.max;
import static java.lang.Math.min;
import com.google.android.exoplayer2.C;
/**
* A {@link SeekMap} implementation that assumes the stream has a constant bitrate and consists of
* multiple independent frames of the same size. Seek points are calculated to be at frame
* boundaries.
*/
public class ConstantBitrateSeekMap implements SeekMap {
private final long inputLength;
private final long firstFrameBytePosition;
private final int frameSize;
private final long dataSize;
private final int bitrate;
private final long durationUs;
private final boolean allowSeeksIfLengthUnknown;
/**
* Creates an instance with {@code allowSeeksIfLengthUnknown} set to {@code false}.
*
* @param inputLength The length of the stream in bytes, or {@link C#LENGTH_UNSET} if unknown.
* @param firstFrameBytePosition The byte-position of the first frame in the stream.
* @param bitrate The bitrate (which is assumed to be constant in the stream).
* @param frameSize The size of each frame in the stream in bytes. May be {@link C#LENGTH_UNSET}
* if unknown.
*/
public ConstantBitrateSeekMap(
long inputLength, long firstFrameBytePosition, int bitrate, int frameSize) {
this(
inputLength,
firstFrameBytePosition,
bitrate,
frameSize,
/* allowSeeksIfLengthUnknown= */ false);
}
/**
* Creates an instance.
*
* @param inputLength The length of the stream in bytes, or {@link C#LENGTH_UNSET} if unknown.
* @param firstFrameBytePosition The byte-position of the first frame in the stream.
* @param bitrate The bitrate (which is assumed to be constant in the stream).
* @param frameSize The size of each frame in the stream in bytes. May be {@link C#LENGTH_UNSET}
* if unknown.
* @param allowSeeksIfLengthUnknown Whether to allow seeking even if the length of the content is
* unknown.
*/
public ConstantBitrateSeekMap(
long inputLength,
long firstFrameBytePosition,
int bitrate,
int frameSize,
boolean allowSeeksIfLengthUnknown) {
this.inputLength = inputLength;
this.firstFrameBytePosition = firstFrameBytePosition;
this.frameSize = frameSize == C.LENGTH_UNSET ? 1 : frameSize;
this.bitrate = bitrate;
this.allowSeeksIfLengthUnknown = allowSeeksIfLengthUnknown;
if (inputLength == C.LENGTH_UNSET) {
dataSize = C.LENGTH_UNSET;
durationUs = C.TIME_UNSET;
} else {
dataSize = inputLength - firstFrameBytePosition;
durationUs = getTimeUsAtPosition(inputLength, firstFrameBytePosition, bitrate);
}
}
@Override
public boolean isSeekable() {
return dataSize != C.LENGTH_UNSET || allowSeeksIfLengthUnknown;
}
@Override
public SeekPoints getSeekPoints(long timeUs) {
if (dataSize == C.LENGTH_UNSET && !allowSeeksIfLengthUnknown) {
return new SeekPoints(new SeekPoint(0, firstFrameBytePosition));
}
long seekFramePosition = getFramePositionForTimeUs(timeUs);
long seekTimeUs = getTimeUsAtPosition(seekFramePosition);
SeekPoint seekPoint = new SeekPoint(seekTimeUs, seekFramePosition);
// We only return a single seek point if the length is unknown, to avoid generating a second
// seek point beyond the end of the data in the case that the requested seek position is valid,
// but very close to the end of the content.
if (dataSize == C.LENGTH_UNSET
|| seekTimeUs >= timeUs
|| seekFramePosition + frameSize >= inputLength) {
return new SeekPoints(seekPoint);
} else {
long secondSeekPosition = seekFramePosition + frameSize;
long secondSeekTimeUs = getTimeUsAtPosition(secondSeekPosition);
SeekPoint secondSeekPoint = new SeekPoint(secondSeekTimeUs, secondSeekPosition);
return new SeekPoints(seekPoint, secondSeekPoint);
}
}
@Override
public long getDurationUs() {
return durationUs;
}
/**
* Returns the stream time in microseconds for a given position.
*
* @param position The stream byte-position.
* @return The stream time in microseconds for the given position.
*/
public long getTimeUsAtPosition(long position) {
return getTimeUsAtPosition(position, firstFrameBytePosition, bitrate);
}
// Internal methods
/**
* Returns the stream time in microseconds for a given stream position.
*
* @param position The stream byte-position.
* @param firstFrameBytePosition The position of the first frame in the stream.
* @param bitrate The bitrate (which is assumed to be constant in the stream).
* @return The stream time in microseconds for the given stream position.
*/
private static long getTimeUsAtPosition(long position, long firstFrameBytePosition, int bitrate) {
return max(0, position - firstFrameBytePosition)
* C.BITS_PER_BYTE
* C.MICROS_PER_SECOND
/ bitrate;
}
private long getFramePositionForTimeUs(long timeUs) {
long positionOffset = (timeUs * bitrate) / (C.MICROS_PER_SECOND * C.BITS_PER_BYTE);
// Constrain to nearest preceding frame offset.
positionOffset = (positionOffset / frameSize) * frameSize;
if (dataSize != C.LENGTH_UNSET) {
positionOffset = min(positionOffset, dataSize - frameSize);
}
positionOffset = max(positionOffset, 0);
return firstFrameBytePosition + positionOffset;
}
}