mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Add VFP.queueInputBitmap api accepting timestamps
PiperOrigin-RevId: 555970312
This commit is contained in:
parent
418edda285
commit
48de9fa916
6 changed files with 95 additions and 7 deletions
|
|
@ -104,6 +104,8 @@
|
||||||
* Metadata:
|
* Metadata:
|
||||||
* DRM:
|
* DRM:
|
||||||
* Effect:
|
* Effect:
|
||||||
|
* Add `VideoFrameProcessor.queueInputBitmap(Bitmap, Iterator<Long>)`
|
||||||
|
queuing bitmap input by timestamp.
|
||||||
* Muxers:
|
* Muxers:
|
||||||
* IMA extension:
|
* IMA extension:
|
||||||
* Session:
|
* Session:
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ 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;
|
||||||
|
|
||||||
|
|
@ -150,7 +151,11 @@ public interface VideoFrameProcessor {
|
||||||
long DROP_OUTPUT_FRAME = -2;
|
long DROP_OUTPUT_FRAME = -2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides an input {@link Bitmap} to the {@link VideoFrameProcessor}.
|
* Provides an input {@link Bitmap} to the {@link VideoFrameProcessor} to generate an input stream
|
||||||
|
* of frames.
|
||||||
|
*
|
||||||
|
* <p>Each call must be made after {@linkplain #registerInputStream registering a new input
|
||||||
|
* stream}.
|
||||||
*
|
*
|
||||||
* <p>Can be called on any thread.
|
* <p>Can be called on any thread.
|
||||||
*
|
*
|
||||||
|
|
@ -161,10 +166,26 @@ 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}.
|
||||||
*/
|
*/
|
||||||
// TODO(b/262693274): Remove duration and frameRate parameters when EditedMediaItem can be
|
// TODO(b/262693274): Delete this method and usages in favor of the one below (Note it is not
|
||||||
// signalled down to the processors.
|
// deprecated because transformer still relies on this method for frame duplication).
|
||||||
void queueInputBitmap(Bitmap inputBitmap, long durationUs, float frameRate);
|
void queueInputBitmap(Bitmap inputBitmap, long durationUs, float frameRate);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides an input {@link Bitmap} to the {@link VideoFrameProcessor}.
|
||||||
|
*
|
||||||
|
* <p>Can be called many times after {@link #registerInputStream(int, List, FrameInfo) registering
|
||||||
|
* the input stream} to put multiple frames in the same input stream.
|
||||||
|
*
|
||||||
|
* <p>Can be called on any thread.
|
||||||
|
*
|
||||||
|
* @param inputBitmap The {@link Bitmap} queued to the {@code VideoFrameProcessor}.
|
||||||
|
* @param inStreamOffsetsUs The times within the current stream that the bitmap should be shown
|
||||||
|
* at. The timestamps should be monotonically increasing.
|
||||||
|
* @throws UnsupportedOperationException If the {@code VideoFrameProcessor} does not accept
|
||||||
|
* {@linkplain #INPUT_TYPE_BITMAP bitmap input}.
|
||||||
|
*/
|
||||||
|
void queueInputBitmap(Bitmap inputBitmap, Iterator<Long> inStreamOffsetsUs);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides an input texture ID to the {@code VideoFrameProcessor}.
|
* Provides an input texture ID to the {@code VideoFrameProcessor}.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -19,10 +19,13 @@ import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||||
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;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.util.Pair;
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
import androidx.media3.common.ColorInfo;
|
import androidx.media3.common.ColorInfo;
|
||||||
import androidx.media3.test.utils.VideoFrameProcessorTestRunner;
|
import androidx.media3.test.utils.VideoFrameProcessorTestRunner;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
@ -112,8 +115,6 @@ public class DefaultVideoFrameProcessorImageFrameOutputTest {
|
||||||
@RequiresNonNull({"framesProduced", "testId"})
|
@RequiresNonNull({"framesProduced", "testId"})
|
||||||
public void imageInput_queueOneWithStartOffset_outputsFramesAtTheCorrectPresentationTimesUs()
|
public void imageInput_queueOneWithStartOffset_outputsFramesAtTheCorrectPresentationTimesUs()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
String testId =
|
|
||||||
"imageInput_queueOneWithStartOffset_outputsFramesAtTheCorrectPresentationTimesUs";
|
|
||||||
Queue<Long> actualPresentationTimesUs = new ConcurrentLinkedQueue<>();
|
Queue<Long> actualPresentationTimesUs = new ConcurrentLinkedQueue<>();
|
||||||
videoFrameProcessorTestRunner =
|
videoFrameProcessorTestRunner =
|
||||||
getDefaultFrameProcessorTestRunnerBuilder(testId)
|
getDefaultFrameProcessorTestRunnerBuilder(testId)
|
||||||
|
|
@ -169,8 +170,6 @@ public class DefaultVideoFrameProcessorImageFrameOutputTest {
|
||||||
public void
|
public void
|
||||||
imageInput_queueEndAndQueueAgain_outputsFirstSetOfFramesOnlyAtTheCorrectPresentationTimesUs()
|
imageInput_queueEndAndQueueAgain_outputsFirstSetOfFramesOnlyAtTheCorrectPresentationTimesUs()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
String testId =
|
|
||||||
"imageInput_queueEndAndQueueAgain_outputsFirstSetOfFramesOnlyAtTheCorrectPresentationTimesUs";
|
|
||||||
Queue<Long> actualPresentationTimesUs = new ConcurrentLinkedQueue<>();
|
Queue<Long> actualPresentationTimesUs = new ConcurrentLinkedQueue<>();
|
||||||
videoFrameProcessorTestRunner =
|
videoFrameProcessorTestRunner =
|
||||||
getDefaultFrameProcessorTestRunnerBuilder(testId)
|
getDefaultFrameProcessorTestRunnerBuilder(testId)
|
||||||
|
|
@ -192,6 +191,31 @@ public class DefaultVideoFrameProcessorImageFrameOutputTest {
|
||||||
assertThat(actualPresentationTimesUs).containsExactly(0L, C.MICROS_PER_SECOND / 2).inOrder();
|
assertThat(actualPresentationTimesUs).containsExactly(0L, C.MICROS_PER_SECOND / 2).inOrder();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@RequiresNonNull({"framesProduced", "testId"})
|
||||||
|
public void queueBitmapsWithTimestamps_outputsFramesAtTheCorrectPresentationTimesUs()
|
||||||
|
throws Exception {
|
||||||
|
Queue<Long> actualPresentationTimesUs = new ConcurrentLinkedQueue<>();
|
||||||
|
videoFrameProcessorTestRunner =
|
||||||
|
getDefaultFrameProcessorTestRunnerBuilder(testId)
|
||||||
|
.setOnOutputFrameAvailableForRenderingListener(actualPresentationTimesUs::add)
|
||||||
|
.build();
|
||||||
|
Bitmap bitmap1 = readBitmap(ORIGINAL_PNG_ASSET_PATH);
|
||||||
|
Bitmap bitmap2 = readBitmap(BITMAP_OVERLAY_PNG_ASSET_PATH);
|
||||||
|
Long offset1 = 0L;
|
||||||
|
Long offset2 = C.MICROS_PER_SECOND;
|
||||||
|
Long offset3 = 2 * C.MICROS_PER_SECOND;
|
||||||
|
|
||||||
|
videoFrameProcessorTestRunner.queueInputBitmaps(
|
||||||
|
bitmap1.getWidth(),
|
||||||
|
bitmap1.getHeight(),
|
||||||
|
Pair.create(bitmap1, ImmutableList.of(offset1).iterator()),
|
||||||
|
Pair.create(bitmap2, ImmutableList.of(offset2, offset3).iterator()));
|
||||||
|
videoFrameProcessorTestRunner.endFrameProcessing();
|
||||||
|
|
||||||
|
assertThat(actualPresentationTimesUs).containsExactly(offset1, offset2, offset3).inOrder();
|
||||||
|
}
|
||||||
|
|
||||||
private VideoFrameProcessorTestRunner.Builder getDefaultFrameProcessorTestRunnerBuilder(
|
private VideoFrameProcessorTestRunner.Builder getDefaultFrameProcessorTestRunnerBuilder(
|
||||||
String testId) {
|
String testId) {
|
||||||
return new VideoFrameProcessorTestRunner.Builder()
|
return new VideoFrameProcessorTestRunner.Builder()
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package androidx.media3.effect;
|
package androidx.media3.effect;
|
||||||
|
|
||||||
|
import static androidx.media3.common.util.Assertions.checkArgument;
|
||||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||||
import static androidx.media3.common.util.Assertions.checkState;
|
import static androidx.media3.common.util.Assertions.checkState;
|
||||||
import static java.lang.Math.round;
|
import static java.lang.Math.round;
|
||||||
|
|
@ -148,7 +149,12 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.useHdr = useHdr;
|
this.useHdr = useHdr;
|
||||||
|
// TODO(b/262693274): move frame duplication logic out of the texture manager. Note this will
|
||||||
|
// involve removing the BitmapFrameSequenceInfo queue and using the FrameConsumptionManager
|
||||||
|
// instead. It will also remove the framesToAdd variable
|
||||||
int framesToAdd = round(frameRate * (durationUs / (float) C.MICROS_PER_SECOND));
|
int framesToAdd = round(frameRate * (durationUs / (float) C.MICROS_PER_SECOND));
|
||||||
|
// framestoAdd > 0 otherwise the VFP will hang.
|
||||||
|
checkArgument(framesToAdd > 0);
|
||||||
double frameDurationUs = C.MICROS_PER_SECOND / frameRate;
|
double frameDurationUs = C.MICROS_PER_SECOND / frameRate;
|
||||||
pendingBitmaps.add(
|
pendingBitmaps.add(
|
||||||
new BitmapFrameSequenceInfo(bitmap, frameInfo, frameDurationUs, framesToAdd));
|
new BitmapFrameSequenceInfo(bitmap, frameInfo, frameDurationUs, framesToAdd));
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@ 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;
|
||||||
|
|
@ -436,6 +437,26 @@ public final class DefaultVideoFrameProcessor implements VideoFrameProcessor {
|
||||||
hasRefreshedNextInputFrameInfo = false;
|
hasRefreshedNextInputFrameInfo = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void queueInputBitmap(Bitmap inputBitmap, Iterator<Long> 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.
|
||||||
|
while (inStreamOffsetsUs.hasNext()) {
|
||||||
|
long inStreamOffsetUs = inStreamOffsetsUs.next();
|
||||||
|
inputSwitcher
|
||||||
|
.activeTextureManager()
|
||||||
|
.queueInputBitmap(
|
||||||
|
inputBitmap,
|
||||||
|
/* durationUs= */ C.MICROS_PER_SECOND,
|
||||||
|
new FrameInfo.Builder(frameInfo)
|
||||||
|
.setOffsetToAddUs(frameInfo.offsetToAddUs + inStreamOffsetUs)
|
||||||
|
.build(),
|
||||||
|
/* frameRate= */ 1,
|
||||||
|
/* useHdr= */ false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void queueInputTexture(int textureId, long presentationTimeUs) {
|
public void queueInputTexture(int textureId, long presentationTimeUs) {
|
||||||
inputSwitcher.activeTextureManager().queueInputTexture(textureId, presentationTimeUs);
|
inputSwitcher.activeTextureManager().queueInputTexture(textureId, presentationTimeUs);
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ import android.graphics.PixelFormat;
|
||||||
import android.media.Image;
|
import android.media.Image;
|
||||||
import android.media.ImageReader;
|
import android.media.ImageReader;
|
||||||
import android.media.MediaFormat;
|
import android.media.MediaFormat;
|
||||||
|
import android.util.Pair;
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.RequiresApi;
|
import androidx.annotation.RequiresApi;
|
||||||
|
|
@ -48,6 +49,7 @@ 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;
|
||||||
|
|
@ -357,6 +359,18 @@ 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) {
|
||||||
|
videoFrameProcessor.registerInputStream(
|
||||||
|
INPUT_TYPE_BITMAP,
|
||||||
|
effects,
|
||||||
|
new FrameInfo.Builder(width, height)
|
||||||
|
.setPixelWidthHeightRatio(pixelWidthHeightRatio)
|
||||||
|
.build());
|
||||||
|
for (Pair<Bitmap, Iterator<Long>> frame : frames) {
|
||||||
|
videoFrameProcessor.queueInputBitmap(frame.first, frame.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void queueInputTexture(GlTextureInfo inputTexture, long pts) {
|
public void queueInputTexture(GlTextureInfo inputTexture, long pts) {
|
||||||
videoFrameProcessor.registerInputStream(
|
videoFrameProcessor.registerInputStream(
|
||||||
INPUT_TYPE_TEXTURE_ID,
|
INPUT_TYPE_TEXTURE_ID,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue