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 android.view.Surface;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.media3.common.util.TimestampIterator;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@ -184,7 +184,7 @@ public interface VideoFrameProcessor {
* @throws UnsupportedOperationException If the {@code VideoFrameProcessor} does not accept * @throws UnsupportedOperationException If the {@code VideoFrameProcessor} does not accept
* {@linkplain #INPUT_TYPE_BITMAP bitmap input}. * {@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}. * 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; package androidx.media3.effect;
import static androidx.media3.common.util.Assertions.checkNotNull; 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 androidx.media3.test.utils.BitmapPixelTestUtil.readBitmap;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
@ -209,8 +210,9 @@ public class DefaultVideoFrameProcessorImageFrameOutputTest {
videoFrameProcessorTestRunner.queueInputBitmaps( videoFrameProcessorTestRunner.queueInputBitmaps(
bitmap1.getWidth(), bitmap1.getWidth(),
bitmap1.getHeight(), bitmap1.getHeight(),
Pair.create(bitmap1, ImmutableList.of(offset1).iterator()), Pair.create(bitmap1, createFromLongIterator(ImmutableList.of(offset1).iterator())),
Pair.create(bitmap2, ImmutableList.of(offset2, offset3).iterator())); Pair.create(
bitmap2, createFromLongIterator(ImmutableList.of(offset2, offset3).iterator())));
videoFrameProcessorTestRunner.endFrameProcessing(); videoFrameProcessorTestRunner.endFrameProcessing();
assertThat(actualPresentationTimesUs).containsExactly(offset1, offset2, offset3).inOrder(); 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.VideoFrameProcessor;
import androidx.media3.common.util.GlUtil; import androidx.media3.common.util.GlUtil;
import androidx.media3.common.util.Log; import androidx.media3.common.util.Log;
import androidx.media3.common.util.TimestampIterator;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
@ -438,7 +438,7 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
} }
@Override @Override
public void queueInputBitmap(Bitmap inputBitmap, Iterator<Long> inStreamOffsetsUs) { public void queueInputBitmap(Bitmap inputBitmap, TimestampIterator inStreamOffsetsUs) {
FrameInfo frameInfo = checkNotNull(this.nextInputFrameInfo); FrameInfo frameInfo = checkNotNull(this.nextInputFrameInfo);
// TODO(b/262693274): move frame duplication logic out of the texture manager so // TODO(b/262693274): move frame duplication logic out of the texture manager so
// textureManager.queueInputBitmap() frame rate and duration parameters be removed. // 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.VideoFrameProcessingException;
import androidx.media3.common.VideoFrameProcessor; import androidx.media3.common.VideoFrameProcessor;
import androidx.media3.common.util.GlUtil; import androidx.media3.common.util.GlUtil;
import androidx.media3.common.util.TimestampIterator;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
@ -359,14 +359,14 @@ public final class VideoFrameProcessorTestRunner {
videoFrameProcessor.queueInputBitmap(inputBitmap, durationUs, frameRate); 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( videoFrameProcessor.registerInputStream(
INPUT_TYPE_BITMAP, INPUT_TYPE_BITMAP,
effects, effects,
new FrameInfo.Builder(width, height) new FrameInfo.Builder(width, height)
.setPixelWidthHeightRatio(pixelWidthHeightRatio) .setPixelWidthHeightRatio(pixelWidthHeightRatio)
.build()); .build());
for (Pair<Bitmap, Iterator<Long>> frame : frames) { for (Pair<Bitmap, TimestampIterator> frame : frames) {
videoFrameProcessor.queueInputBitmap(frame.first, frame.second); videoFrameProcessor.queueInputBitmap(frame.first, frame.second);
} }
} }

View File

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

View File

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

View File

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