Create Timestamp iterator

PiperOrigin-RevId: 558738035
This commit is contained in:
tofunmi 2023-08-21 11:39:45 +01:00 committed by Julia Bibik
parent 352916b182
commit 521c210fd1
10 changed files with 216 additions and 24 deletions

View File

@ -23,12 +23,12 @@ import android.opengl.EGLExt;
import android.view.Surface;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import androidx.media3.common.util.TimestampIterator;
import androidx.media3.common.util.UnstableApi;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Executor;
@ -184,7 +184,7 @@ public interface VideoFrameProcessor {
* @throws UnsupportedOperationException If the {@code VideoFrameProcessor} does not accept
* {@linkplain #INPUT_TYPE_BITMAP bitmap input}.
*/
void queueInputBitmap(Bitmap inputBitmap, Iterator<Long> inStreamOffsetsUs);
void queueInputBitmap(Bitmap inputBitmap, TimestampIterator inStreamOffsetsUs);
/**
* Provides an input texture ID to the {@code VideoFrameProcessor}.

View File

@ -0,0 +1,65 @@
/*
* Copyright 2023 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 androidx.media3.common.util;
import static androidx.media3.common.util.Assertions.checkArgument;
import static androidx.media3.common.util.Assertions.checkState;
import static java.lang.Math.round;
import androidx.annotation.FloatRange;
import androidx.annotation.IntRange;
import androidx.media3.common.C;
/**
* A {@link TimestampIterator} that generates monotonically increasing timestamps (in microseconds)
* distributed evenly over the given {@code durationUs} based on the given {@code frameRate}.
*/
@UnstableApi
public final class ConstantRateTimestampIterator implements TimestampIterator {
private final double framesDurationUs;
private double currentTimestampUs;
private int framesToAdd;
/**
* Creates an instance.
*
* @param durationUs The duration the timestamps should span over, in microseconds.
* @param frameRate The frame rate in frames per second.
*/
public ConstantRateTimestampIterator(
@IntRange(from = 1) long durationUs,
@FloatRange(from = 0, fromInclusive = false) float frameRate) {
checkArgument(durationUs > 0);
checkArgument(frameRate > 0);
framesToAdd = round(frameRate * (durationUs / (float) C.MICROS_PER_SECOND));
framesDurationUs = C.MICROS_PER_SECOND / frameRate;
}
@Override
public boolean hasNext() {
return framesToAdd != 0;
}
@Override
public long next() {
checkState(hasNext());
framesToAdd--;
long next = round(currentTimestampUs);
currentTimestampUs += framesDurationUs;
return next;
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright 2023 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 androidx.media3.common.util;
import java.util.Iterator;
/** A primitive long iterator used for generating sequences of timestamps. */
@UnstableApi
public interface TimestampIterator {
/** Returns whether there is another element. */
boolean hasNext();
/** Returns the next timestamp. */
long next();
/** Creates TimestampIterator */
static TimestampIterator createFromLongIterator(Iterator<Long> iterator) {
return new TimestampIterator() {
@Override
public boolean hasNext() {
return iterator.hasNext();
}
@Override
public long next() {
return iterator.next();
}
};
}
}

View File

@ -0,0 +1,82 @@
/*
* Copyright 2023 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 androidx.media3.common.util;
import static com.google.common.truth.Truth.assertThat;
import androidx.media3.common.C;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Unit tests for {@link ConstantRateTimestampIterator}. */
@RunWith(AndroidJUnit4.class)
public class ConstantRateTimestampIteratorTest {
@Test
public void timestampIterator_validArguments_generatesCorrectTimestamps() throws Exception {
ConstantRateTimestampIterator constantRateTimestampIterator =
new ConstantRateTimestampIterator(C.MICROS_PER_SECOND, /* frameRate= */ 2);
assertThat(generateList(constantRateTimestampIterator))
.containsExactly(0L, C.MICROS_PER_SECOND / 2);
}
@Test
public void timestampIterator_realisticArguments_generatesCorrectNumberOfTimestamps()
throws Exception {
ConstantRateTimestampIterator constantRateTimestampIterator =
new ConstantRateTimestampIterator((long) (2.5 * C.MICROS_PER_SECOND), /* frameRate= */ 30);
assertThat(generateList(constantRateTimestampIterator)).hasSize(75);
}
@Test
public void timestampIterator_realisticArguments_generatesTimestampsInStrictOrder()
throws Exception {
ConstantRateTimestampIterator constantRateTimestampIterator =
new ConstantRateTimestampIterator((long) (2.5 * C.MICROS_PER_SECOND), /* frameRate= */ 30);
assertThat(generateList(constantRateTimestampIterator)).isInStrictOrder();
}
@Test
public void timestampIterator_realisticArguments_doesNotGenerateDuplicates() throws Exception {
ConstantRateTimestampIterator constantRateTimestampIterator =
new ConstantRateTimestampIterator((long) (2.5 * C.MICROS_PER_SECOND), /* frameRate= */ 30);
assertThat(generateList(constantRateTimestampIterator)).containsNoDuplicates();
}
@Test
public void timestampIterator_smallDuration_generatesEmptyIterator() throws Exception {
ConstantRateTimestampIterator constantRateTimestampIterator =
new ConstantRateTimestampIterator(/* durationUs= */ 1, /* frameRate= */ 2);
assertThat(generateList(constantRateTimestampIterator)).isEmpty();
}
private static List<Long> generateList(TimestampIterator iterator) {
ArrayList<Long> list = new ArrayList<>();
while (iterator.hasNext()) {
list.add(iterator.next());
}
return list;
}
}

View File

@ -16,6 +16,7 @@
package androidx.media3.effect;
import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.TimestampIterator.createFromLongIterator;
import static androidx.media3.test.utils.BitmapPixelTestUtil.readBitmap;
import static com.google.common.truth.Truth.assertThat;
@ -209,8 +210,9 @@ public class DefaultVideoFrameProcessorImageFrameOutputTest {
videoFrameProcessorTestRunner.queueInputBitmaps(
bitmap1.getWidth(),
bitmap1.getHeight(),
Pair.create(bitmap1, ImmutableList.of(offset1).iterator()),
Pair.create(bitmap2, ImmutableList.of(offset2, offset3).iterator()));
Pair.create(bitmap1, createFromLongIterator(ImmutableList.of(offset1).iterator())),
Pair.create(
bitmap2, createFromLongIterator(ImmutableList.of(offset2, offset3).iterator())));
videoFrameProcessorTestRunner.endFrameProcessing();
assertThat(actualPresentationTimesUs).containsExactly(offset1, offset2, offset3).inOrder();

View File

@ -52,13 +52,13 @@ import androidx.media3.common.VideoFrameProcessingException;
import androidx.media3.common.VideoFrameProcessor;
import androidx.media3.common.util.GlUtil;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.TimestampIterator;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
@ -438,7 +438,7 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
}
@Override
public void queueInputBitmap(Bitmap inputBitmap, Iterator<Long> inStreamOffsetsUs) {
public void queueInputBitmap(Bitmap inputBitmap, TimestampIterator inStreamOffsetsUs) {
FrameInfo frameInfo = checkNotNull(this.nextInputFrameInfo);
// TODO(b/262693274): move frame duplication logic out of the texture manager so
// textureManager.queueInputBitmap() frame rate and duration parameters be removed.

View File

@ -45,11 +45,11 @@ import androidx.media3.common.SurfaceInfo;
import androidx.media3.common.VideoFrameProcessingException;
import androidx.media3.common.VideoFrameProcessor;
import androidx.media3.common.util.GlUtil;
import androidx.media3.common.util.TimestampIterator;
import androidx.media3.common.util.UnstableApi;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
@ -359,14 +359,14 @@ public final class VideoFrameProcessorTestRunner {
videoFrameProcessor.queueInputBitmap(inputBitmap, durationUs, frameRate);
}
public void queueInputBitmaps(int width, int height, Pair<Bitmap, Iterator<Long>>... frames) {
public void queueInputBitmaps(int width, int height, Pair<Bitmap, TimestampIterator>... frames) {
videoFrameProcessor.registerInputStream(
INPUT_TYPE_BITMAP,
effects,
new FrameInfo.Builder(width, height)
.setPixelWidthHeightRatio(pixelWidthHeightRatio)
.build());
for (Pair<Bitmap, Iterator<Long>> frame : frames) {
for (Pair<Bitmap, TimestampIterator> frame : frames) {
videoFrameProcessor.queueInputBitmap(frame.first, frame.second);
}
}

View File

@ -20,9 +20,9 @@ import android.view.Surface;
import androidx.annotation.Nullable;
import androidx.media3.common.ColorInfo;
import androidx.media3.common.OnInputFrameProcessedListener;
import androidx.media3.common.util.TimestampIterator;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.decoder.DecoderInputBuffer;
import java.util.Iterator;
/** Consumer of encoded media samples, raw audio or raw video frames. */
@UnstableApi
@ -93,7 +93,7 @@ public interface SampleConsumer {
* @param inStreamOffsetsUs The times within the current stream that the bitmap should be
* displayed at. The timestamps should be monotonically increasing.
*/
default boolean queueInputBitmap(Bitmap inputBitmap, Iterator<Long> inStreamOffsetsUs) {
default boolean queueInputBitmap(Bitmap inputBitmap, TimestampIterator inStreamOffsetsUs) {
throw new UnsupportedOperationException();
}

View File

@ -35,11 +35,11 @@ import androidx.media3.common.MimeTypes;
import androidx.media3.common.OnInputFrameProcessedListener;
import androidx.media3.common.util.Clock;
import androidx.media3.common.util.HandlerWrapper;
import androidx.media3.common.util.TimestampIterator;
import androidx.media3.decoder.DecoderInputBuffer;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
@ -349,17 +349,16 @@ import java.util.concurrent.atomic.AtomicInteger;
}
/**
* Given an {@link Iterator}, creates an iterator that includes all the values in the original
* iterator (in the same order) up to and including the first occurrence of the {@code
* clippingValue}.
* Wraps a {@link TimestampIterator}, providing all the values in the original timestamp iterator
* (in the same order) up to and including the first occurrence of the {@code clippingValue}.
*/
private static final class ClippingIterator implements Iterator<Long> {
private static final class ClippingIterator implements TimestampIterator {
private final Iterator<Long> iterator;
private final TimestampIterator iterator;
private final long clippingValue;
private boolean hasReachedClippingValue;
public ClippingIterator(Iterator<Long> iterator, long clippingValue) {
public ClippingIterator(TimestampIterator iterator, long clippingValue) {
this.iterator = iterator;
this.clippingValue = clippingValue;
}
@ -370,9 +369,9 @@ import java.util.concurrent.atomic.AtomicInteger;
}
@Override
public Long next() {
public long next() {
checkState(hasNext());
Long next = iterator.next();
long next = iterator.next();
if (clippingValue == next) {
hasReachedClippingValue = true;
}
@ -450,8 +449,8 @@ import java.util.concurrent.atomic.AtomicInteger;
}
@Override
public boolean queueInputBitmap(Bitmap inputBitmap, Iterator<Long> inStreamOffsetsUs) {
Iterator<Long> iteratorToUse = inStreamOffsetsUs;
public boolean queueInputBitmap(Bitmap inputBitmap, TimestampIterator inStreamOffsetsUs) {
TimestampIterator iteratorToUse = inStreamOffsetsUs;
if (isLooping) {
long durationLeftUs = maxSequenceDurationUs - totalDurationUs;
if (durationLeftUs <= 0) {

View File

@ -37,9 +37,9 @@ import androidx.media3.common.VideoFrameProcessingException;
import androidx.media3.common.VideoFrameProcessor;
import androidx.media3.common.util.Consumer;
import androidx.media3.common.util.Size;
import androidx.media3.common.util.TimestampIterator;
import androidx.media3.effect.Presentation;
import com.google.common.collect.ImmutableList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicLong;
@ -175,7 +175,7 @@ import java.util.concurrent.atomic.AtomicLong;
}
@Override
public boolean queueInputBitmap(Bitmap inputBitmap, Iterator<Long> inStreamOffsetsUs) {
public boolean queueInputBitmap(Bitmap inputBitmap, TimestampIterator inStreamOffsetsUs) {
videoFrameProcessor.queueInputBitmap(inputBitmap, inStreamOffsetsUs);
return true;
}