Skip to content

Commit

Permalink
Add IndexSeeker to MP3 extractor
Browse files Browse the repository at this point in the history
This seeker is only seeking to frames that have already been read by the
extractor for playback. Seeking to not-yet-read frames will be
implemented in another change.

Issue: #6787
PiperOrigin-RevId: 291888899
  • Loading branch information
kim-vde authored and icbaker committed Jan 28, 2020
1 parent bb9b3fd commit 103bd41
Show file tree
Hide file tree
Showing 8 changed files with 1,241 additions and 7 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Copyright (C) 2020 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.mp3;

import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.extractor.SeekPoint;
import com.google.android.exoplayer2.util.LongArray;
import com.google.android.exoplayer2.util.Util;

/** MP3 seeker that builds a time-to-byte mapping as the stream is read. */
/* package */ final class IndexSeeker implements Seeker {

private static final long MIN_TIME_BETWEEN_POINTS_US = C.MICROS_PER_SECOND / 10;

private final long durationUs;
private final long dataEndPosition;
private final LongArray timesUs;
private final LongArray positions;

public IndexSeeker(long durationUs, long dataStartPosition, long dataEndPosition) {
this.durationUs = durationUs;
this.dataEndPosition = dataEndPosition;
timesUs = new LongArray();
positions = new LongArray();
timesUs.add(0L);
positions.add(dataStartPosition);
}

@Override
public long getTimeUs(long position) {
int targetIndex =
Util.binarySearchFloor(
positions, position, /* inclusive= */ true, /* stayInBounds= */ true);
return timesUs.get(targetIndex);
}

@Override
public long getDataEndPosition() {
return dataEndPosition;
}

@Override
public boolean isSeekable() {
return true;
}

@Override
public long getDurationUs() {
return durationUs;
}

@Override
public SeekPoints getSeekPoints(long timeUs) {
int targetIndex =
Util.binarySearchFloor(timesUs, timeUs, /* inclusive= */ true, /* stayInBounds= */ true);
SeekPoint seekPoint = new SeekPoint(timesUs.get(targetIndex), positions.get(targetIndex));
if (seekPoint.timeUs >= timeUs || targetIndex == timesUs.size() - 1) {
return new SeekPoints(seekPoint);
} else {
SeekPoint nextSeekPoint =
new SeekPoint(timesUs.get(targetIndex + 1), positions.get(targetIndex + 1));
return new SeekPoints(seekPoint, nextSeekPoint);
}
}

/**
* Adds a seek point to the index if it is sufficiently distant from the other points.
*
* <p>Seek points must be added in order.
*
* @param timeUs The time corresponding to the seek point to add in microseconds.
* @param position The position corresponding to the seek point to add in bytes.
*/
public void maybeAddSeekPoint(long timeUs, long position) {
long lastTimeUs = timesUs.get(timesUs.size() - 1);
if (timeUs - lastTimeUs < MIN_TIME_BETWEEN_POINTS_US) {
return;
}
timesUs.add(timeUs);
positions.add(position);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,24 +56,35 @@ public final class Mp3Extractor implements Extractor {

/**
* Flags controlling the behavior of the extractor. Possible flag values are {@link
* #FLAG_ENABLE_CONSTANT_BITRATE_SEEKING} and {@link #FLAG_DISABLE_ID3_METADATA}.
* #FLAG_ENABLE_CONSTANT_BITRATE_SEEKING}, {@link #FLAG_ENABLE_INDEX_SEEKING} and {@link
* #FLAG_DISABLE_ID3_METADATA}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef(
flag = true,
value = {FLAG_ENABLE_CONSTANT_BITRATE_SEEKING, FLAG_DISABLE_ID3_METADATA})
value = {
FLAG_ENABLE_CONSTANT_BITRATE_SEEKING,
FLAG_ENABLE_INDEX_SEEKING,
FLAG_DISABLE_ID3_METADATA
})
public @interface Flags {}
/**
* Flag to force enable seeking using a constant bitrate assumption in cases where seeking would
* otherwise not be possible.
*
* <p>This flag is ignored if {@link #FLAG_ENABLE_INDEX_SEEKING} is set.
*/
public static final int FLAG_ENABLE_CONSTANT_BITRATE_SEEKING = 1;
/**
* Flag to force index seeking, consisting in building a time-to-byte mapping as the file is read.
*/
public static final int FLAG_ENABLE_INDEX_SEEKING = 1 << 1;
/**
* Flag to disable parsing of ID3 metadata. Can be set to save memory if ID3 metadata is not
* required.
*/
public static final int FLAG_DISABLE_ID3_METADATA = 2;
public static final int FLAG_DISABLE_ID3_METADATA = 1 << 2;

/** Predicate that matches ID3 frames containing only required gapless/seeking metadata. */
private static final FramePredicate REQUIRED_ID3_FRAME_PREDICATE =
Expand Down Expand Up @@ -256,6 +267,9 @@ private int readSample(ExtractorInput extractorInput) throws IOException, Interr
}
}
sampleBytesRemaining = synchronizedHeader.frameSize;
maybeAddSeekPointToIndexSeeker(
computeTimeUs(samplesRead + synchronizedHeader.samplesPerFrame),
extractorInput.getPosition() + synchronizedHeader.frameSize);
}
int bytesAppended = trackOutput.sampleData(extractorInput, sampleBytesRemaining, true);
if (bytesAppended == C.RESULT_END_OF_INPUT) {
Expand All @@ -265,14 +279,24 @@ private int readSample(ExtractorInput extractorInput) throws IOException, Interr
if (sampleBytesRemaining > 0) {
return RESULT_CONTINUE;
}
long timeUs = basisTimeUs + (samplesRead * C.MICROS_PER_SECOND / synchronizedHeader.sampleRate);
trackOutput.sampleMetadata(timeUs, C.BUFFER_FLAG_KEY_FRAME, synchronizedHeader.frameSize, 0,
null);
trackOutput.sampleMetadata(
computeTimeUs(samplesRead), C.BUFFER_FLAG_KEY_FRAME, synchronizedHeader.frameSize, 0, null);
samplesRead += synchronizedHeader.samplesPerFrame;
sampleBytesRemaining = 0;
return RESULT_CONTINUE;
}

private long computeTimeUs(long samplesRead) {
return basisTimeUs + samplesRead * C.MICROS_PER_SECOND / synchronizedHeader.sampleRate;
}

private void maybeAddSeekPointToIndexSeeker(long timeUs, long position) {
if (!(seeker instanceof IndexSeeker)) {
return;
}
((IndexSeeker) seeker).maybeAddSeekPoint(timeUs, position);
}

private boolean synchronize(ExtractorInput input, boolean sniffing)
throws IOException, InterruptedException {
int validFrameCount = 0;
Expand Down Expand Up @@ -379,7 +403,20 @@ private Seeker computeSeeker(ExtractorInput input) throws IOException, Interrupt
}

@Nullable Seeker resultSeeker = null;
if (metadataSeeker != null) {
if ((flags & FLAG_ENABLE_INDEX_SEEKING) != 0) {
long durationUs = C.TIME_UNSET;
long dataEndPosition = C.POSITION_UNSET;
if (metadataSeeker != null) {
durationUs = metadataSeeker.getDurationUs();
dataEndPosition = metadataSeeker.getDataEndPosition();
} else if (seekFrameSeeker != null) {
durationUs = seekFrameSeeker.getDurationUs();
dataEndPosition = seekFrameSeeker.getDataEndPosition();
}
resultSeeker =
new IndexSeeker(
durationUs, /* dataStartPosition= */ input.getPosition(), dataEndPosition);
} else if (metadataSeeker != null) {
resultSeeker = metadataSeeker;
} else if (seekFrameSeeker != null) {
resultSeeker = seekFrameSeeker;
Expand Down
Binary file not shown.
Loading

0 comments on commit 103bd41

Please sign in to comment.