Support segment-specific speed changes in SpeedChangeEffect
PiperOrigin-RevId: 606204479
This commit is contained in:
parent
0b0c419c73
commit
1a5eb4eecd
@ -18,6 +18,8 @@
|
|||||||
* DRM:
|
* DRM:
|
||||||
* Effect:
|
* Effect:
|
||||||
* Improved PQ to SDR tone-mapping by converting color spaces.
|
* Improved PQ to SDR tone-mapping by converting color spaces.
|
||||||
|
* Support multiple speed changes within the same `EditedMediaItem` or
|
||||||
|
`Composition` in `SpeedChangeEffect`.
|
||||||
* Muxers:
|
* Muxers:
|
||||||
* IMA extension:
|
* IMA extension:
|
||||||
* Session:
|
* Session:
|
||||||
|
@ -16,11 +16,10 @@
|
|||||||
package androidx.media3.common.audio;
|
package androidx.media3.common.audio;
|
||||||
|
|
||||||
import static androidx.media3.common.audio.AudioProcessor.EMPTY_BUFFER;
|
import static androidx.media3.common.audio.AudioProcessor.EMPTY_BUFFER;
|
||||||
import static androidx.media3.common.util.Assertions.checkArgument;
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.test.utils.TestSpeedProvider;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
@ -39,7 +38,7 @@ public class SpeedChangingAudioProcessorTest {
|
|||||||
public void queueInput_noSpeedChange_doesNotOverwriteInput() throws Exception {
|
public void queueInput_noSpeedChange_doesNotOverwriteInput() throws Exception {
|
||||||
SpeedProvider speedProvider =
|
SpeedProvider speedProvider =
|
||||||
TestSpeedProvider.createWithFrameCounts(
|
TestSpeedProvider.createWithFrameCounts(
|
||||||
/* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {1});
|
AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {1});
|
||||||
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
||||||
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
||||||
ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5);
|
ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5);
|
||||||
@ -54,7 +53,7 @@ public class SpeedChangingAudioProcessorTest {
|
|||||||
public void queueInput_speedChange_doesNotOverwriteInput() throws Exception {
|
public void queueInput_speedChange_doesNotOverwriteInput() throws Exception {
|
||||||
SpeedProvider speedProvider =
|
SpeedProvider speedProvider =
|
||||||
TestSpeedProvider.createWithFrameCounts(
|
TestSpeedProvider.createWithFrameCounts(
|
||||||
/* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2});
|
AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2});
|
||||||
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
||||||
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
||||||
ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5);
|
ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5);
|
||||||
@ -69,7 +68,7 @@ public class SpeedChangingAudioProcessorTest {
|
|||||||
public void queueInput_noSpeedChange_copiesSamples() throws Exception {
|
public void queueInput_noSpeedChange_copiesSamples() throws Exception {
|
||||||
SpeedProvider speedProvider =
|
SpeedProvider speedProvider =
|
||||||
TestSpeedProvider.createWithFrameCounts(
|
TestSpeedProvider.createWithFrameCounts(
|
||||||
/* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {1});
|
AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {1});
|
||||||
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
||||||
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
||||||
ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5);
|
ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5);
|
||||||
@ -86,7 +85,7 @@ public class SpeedChangingAudioProcessorTest {
|
|||||||
public void queueInput_speedChange_modifiesSamples() throws Exception {
|
public void queueInput_speedChange_modifiesSamples() throws Exception {
|
||||||
SpeedProvider speedProvider =
|
SpeedProvider speedProvider =
|
||||||
TestSpeedProvider.createWithFrameCounts(
|
TestSpeedProvider.createWithFrameCounts(
|
||||||
/* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2});
|
AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2});
|
||||||
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
||||||
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
||||||
ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5);
|
ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5);
|
||||||
@ -104,7 +103,7 @@ public class SpeedChangingAudioProcessorTest {
|
|||||||
public void queueInput_noSpeedChangeAfterSpeedChange_copiesSamples() throws Exception {
|
public void queueInput_noSpeedChangeAfterSpeedChange_copiesSamples() throws Exception {
|
||||||
SpeedProvider speedProvider =
|
SpeedProvider speedProvider =
|
||||||
TestSpeedProvider.createWithFrameCounts(
|
TestSpeedProvider.createWithFrameCounts(
|
||||||
/* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {2, 1});
|
AUDIO_FORMAT, /* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {2, 1});
|
||||||
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
||||||
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
||||||
ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5);
|
ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5);
|
||||||
@ -124,7 +123,7 @@ public class SpeedChangingAudioProcessorTest {
|
|||||||
throws Exception {
|
throws Exception {
|
||||||
SpeedProvider speedProvider =
|
SpeedProvider speedProvider =
|
||||||
TestSpeedProvider.createWithFrameCounts(
|
TestSpeedProvider.createWithFrameCounts(
|
||||||
/* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {1, 2});
|
AUDIO_FORMAT, /* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {1, 2});
|
||||||
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
||||||
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
||||||
ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5);
|
ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5);
|
||||||
@ -137,7 +136,7 @@ public class SpeedChangingAudioProcessorTest {
|
|||||||
|
|
||||||
speedProvider =
|
speedProvider =
|
||||||
TestSpeedProvider.createWithFrameCounts(
|
TestSpeedProvider.createWithFrameCounts(
|
||||||
/* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2});
|
AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2});
|
||||||
speedChangingAudioProcessor = getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
speedChangingAudioProcessor = getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
||||||
inputBuffer.rewind();
|
inputBuffer.rewind();
|
||||||
speedChangingAudioProcessor.queueInput(inputBuffer);
|
speedChangingAudioProcessor.queueInput(inputBuffer);
|
||||||
@ -152,7 +151,7 @@ public class SpeedChangingAudioProcessorTest {
|
|||||||
throws Exception {
|
throws Exception {
|
||||||
SpeedProvider speedProvider =
|
SpeedProvider speedProvider =
|
||||||
TestSpeedProvider.createWithFrameCounts(
|
TestSpeedProvider.createWithFrameCounts(
|
||||||
/* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {3, 2});
|
AUDIO_FORMAT, /* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {3, 2});
|
||||||
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
||||||
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
||||||
ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5);
|
ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5);
|
||||||
@ -165,7 +164,7 @@ public class SpeedChangingAudioProcessorTest {
|
|||||||
|
|
||||||
speedProvider =
|
speedProvider =
|
||||||
TestSpeedProvider.createWithFrameCounts(
|
TestSpeedProvider.createWithFrameCounts(
|
||||||
/* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2});
|
AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2});
|
||||||
speedChangingAudioProcessor = getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
speedChangingAudioProcessor = getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
||||||
inputBuffer.rewind();
|
inputBuffer.rewind();
|
||||||
speedChangingAudioProcessor.queueInput(inputBuffer);
|
speedChangingAudioProcessor.queueInput(inputBuffer);
|
||||||
@ -180,7 +179,7 @@ public class SpeedChangingAudioProcessorTest {
|
|||||||
throws Exception {
|
throws Exception {
|
||||||
SpeedProvider speedProvider =
|
SpeedProvider speedProvider =
|
||||||
TestSpeedProvider.createWithFrameCounts(
|
TestSpeedProvider.createWithFrameCounts(
|
||||||
/* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {2, 3});
|
AUDIO_FORMAT, /* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {2, 3});
|
||||||
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
||||||
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
||||||
ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5);
|
ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5);
|
||||||
@ -190,7 +189,7 @@ public class SpeedChangingAudioProcessorTest {
|
|||||||
|
|
||||||
speedProvider =
|
speedProvider =
|
||||||
TestSpeedProvider.createWithFrameCounts(
|
TestSpeedProvider.createWithFrameCounts(
|
||||||
/* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2});
|
AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2});
|
||||||
speedChangingAudioProcessor = getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
speedChangingAudioProcessor = getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
||||||
inputBuffer.rewind();
|
inputBuffer.rewind();
|
||||||
speedChangingAudioProcessor.queueInput(inputBuffer);
|
speedChangingAudioProcessor.queueInput(inputBuffer);
|
||||||
@ -243,7 +242,7 @@ public class SpeedChangingAudioProcessorTest {
|
|||||||
throws Exception {
|
throws Exception {
|
||||||
SpeedProvider speedProvider =
|
SpeedProvider speedProvider =
|
||||||
TestSpeedProvider.createWithFrameCounts(
|
TestSpeedProvider.createWithFrameCounts(
|
||||||
/* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {2, 1});
|
AUDIO_FORMAT, /* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {2, 1});
|
||||||
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
||||||
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
||||||
ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5);
|
ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5);
|
||||||
@ -262,7 +261,7 @@ public class SpeedChangingAudioProcessorTest {
|
|||||||
throws Exception {
|
throws Exception {
|
||||||
SpeedProvider speedProvider =
|
SpeedProvider speedProvider =
|
||||||
TestSpeedProvider.createWithFrameCounts(
|
TestSpeedProvider.createWithFrameCounts(
|
||||||
/* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {1, 2});
|
AUDIO_FORMAT, /* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {1, 2});
|
||||||
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
||||||
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
||||||
ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5);
|
ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5);
|
||||||
@ -281,7 +280,7 @@ public class SpeedChangingAudioProcessorTest {
|
|||||||
throws Exception {
|
throws Exception {
|
||||||
SpeedProvider speedProvider =
|
SpeedProvider speedProvider =
|
||||||
TestSpeedProvider.createWithFrameCounts(
|
TestSpeedProvider.createWithFrameCounts(
|
||||||
/* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {1});
|
AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {1});
|
||||||
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
||||||
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
||||||
ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5);
|
ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5);
|
||||||
@ -297,7 +296,7 @@ public class SpeedChangingAudioProcessorTest {
|
|||||||
throws Exception {
|
throws Exception {
|
||||||
SpeedProvider speedProvider =
|
SpeedProvider speedProvider =
|
||||||
TestSpeedProvider.createWithFrameCounts(
|
TestSpeedProvider.createWithFrameCounts(
|
||||||
/* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2});
|
AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2});
|
||||||
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
||||||
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
||||||
ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5);
|
ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5);
|
||||||
@ -312,7 +311,7 @@ public class SpeedChangingAudioProcessorTest {
|
|||||||
public void queueEndOfStream_noInputQueued_endsProcessor() throws Exception {
|
public void queueEndOfStream_noInputQueued_endsProcessor() throws Exception {
|
||||||
SpeedProvider speedProvider =
|
SpeedProvider speedProvider =
|
||||||
TestSpeedProvider.createWithFrameCounts(
|
TestSpeedProvider.createWithFrameCounts(
|
||||||
/* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2});
|
AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2});
|
||||||
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
||||||
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
||||||
|
|
||||||
@ -325,7 +324,7 @@ public class SpeedChangingAudioProcessorTest {
|
|||||||
public void isEnded_afterNoSpeedChangeAndOutputRetrieved_isFalse() throws Exception {
|
public void isEnded_afterNoSpeedChangeAndOutputRetrieved_isFalse() throws Exception {
|
||||||
SpeedProvider speedProvider =
|
SpeedProvider speedProvider =
|
||||||
TestSpeedProvider.createWithFrameCounts(
|
TestSpeedProvider.createWithFrameCounts(
|
||||||
/* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {1});
|
AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {1});
|
||||||
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
||||||
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
||||||
ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5);
|
ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5);
|
||||||
@ -340,7 +339,7 @@ public class SpeedChangingAudioProcessorTest {
|
|||||||
public void isEnded_afterSpeedChangeAndOutputRetrieved_isFalse() throws Exception {
|
public void isEnded_afterSpeedChangeAndOutputRetrieved_isFalse() throws Exception {
|
||||||
SpeedProvider speedProvider =
|
SpeedProvider speedProvider =
|
||||||
TestSpeedProvider.createWithFrameCounts(
|
TestSpeedProvider.createWithFrameCounts(
|
||||||
/* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2});
|
AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2});
|
||||||
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
SpeedChangingAudioProcessor speedChangingAudioProcessor =
|
||||||
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
getConfiguredSpeedChangingAudioProcessor(speedProvider);
|
||||||
ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5);
|
ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5);
|
||||||
@ -388,62 +387,4 @@ public class SpeedChangingAudioProcessorTest {
|
|||||||
}
|
}
|
||||||
return concatenatedOutputBuffers;
|
return concatenatedOutputBuffers;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class TestSpeedProvider implements SpeedProvider {
|
|
||||||
|
|
||||||
private final long[] startTimesUs;
|
|
||||||
private final float[] speeds;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a {@code TestSpeedProvider} instance.
|
|
||||||
*
|
|
||||||
* @param startTimesUs The speed change start times, in microseconds. The values must be
|
|
||||||
* distinct and in increasing order.
|
|
||||||
* @param speeds The speeds corresponding to each start time. Consecutive values must be
|
|
||||||
* distinct.
|
|
||||||
* @return A {@code TestSpeedProvider}.
|
|
||||||
*/
|
|
||||||
public static TestSpeedProvider createWithStartTimes(long[] startTimesUs, float[] speeds) {
|
|
||||||
return new TestSpeedProvider(startTimesUs, speeds);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a {@code TestSpeedProvider} instance.
|
|
||||||
*
|
|
||||||
* @param frameCounts The frame counts for which the same speed should be applied.
|
|
||||||
* @param speeds The speeds corresponding to each frame count. The values must be distinct.
|
|
||||||
* @return A {@code TestSpeedProvider}.
|
|
||||||
*/
|
|
||||||
public static TestSpeedProvider createWithFrameCounts(int[] frameCounts, float[] speeds) {
|
|
||||||
long[] startTimesUs = new long[frameCounts.length];
|
|
||||||
int totalFrameCount = 0;
|
|
||||||
for (int i = 0; i < frameCounts.length; i++) {
|
|
||||||
startTimesUs[i] = totalFrameCount * C.MICROS_PER_SECOND / AUDIO_FORMAT.sampleRate;
|
|
||||||
totalFrameCount += frameCounts[i];
|
|
||||||
}
|
|
||||||
return new TestSpeedProvider(startTimesUs, speeds);
|
|
||||||
}
|
|
||||||
|
|
||||||
private TestSpeedProvider(long[] startTimesUs, float[] speeds) {
|
|
||||||
checkArgument(startTimesUs.length == speeds.length);
|
|
||||||
this.startTimesUs = startTimesUs;
|
|
||||||
this.speeds = speeds;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public float getSpeed(long timeUs) {
|
|
||||||
int index =
|
|
||||||
Util.binarySearchFloor(
|
|
||||||
startTimesUs, timeUs, /* inclusive= */ true, /* stayInBounds= */ true);
|
|
||||||
return speeds[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getNextSpeedChangeTimeUs(long timeUs) {
|
|
||||||
int index =
|
|
||||||
Util.binarySearchCeil(
|
|
||||||
startTimesUs, timeUs, /* inclusive= */ false, /* stayInBounds= */ false);
|
|
||||||
return index < startTimesUs.length ? startTimesUs[index] : C.TIME_UNSET;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -16,12 +16,19 @@
|
|||||||
package androidx.media3.effect;
|
package androidx.media3.effect;
|
||||||
|
|
||||||
import static androidx.media3.test.utils.BitmapPixelTestUtil.readBitmap;
|
import static androidx.media3.test.utils.BitmapPixelTestUtil.readBitmap;
|
||||||
|
import static androidx.media3.test.utils.VideoFrameProcessorTestRunner.createTimestampIterator;
|
||||||
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.Effect;
|
import androidx.media3.common.Effect;
|
||||||
|
import androidx.media3.common.VideoFrameProcessingException;
|
||||||
import androidx.media3.common.VideoFrameProcessor;
|
import androidx.media3.common.VideoFrameProcessor;
|
||||||
|
import androidx.media3.common.audio.SpeedProvider;
|
||||||
|
import androidx.media3.test.utils.TestSpeedProvider;
|
||||||
import androidx.media3.test.utils.VideoFrameProcessorTestRunner;
|
import androidx.media3.test.utils.VideoFrameProcessorTestRunner;
|
||||||
|
import androidx.media3.test.utils.VideoFrameProcessorTestRunner.OnOutputFrameAvailableForRenderingListener;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -41,29 +48,121 @@ public class SpeedChangeEffectTest {
|
|||||||
"media/bitmap/sample_mp4_first_frame/electrical_colors/original.png";
|
"media/bitmap/sample_mp4_first_frame/electrical_colors/original.png";
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void changeSpeed_outputsFramesAtTheCorrectPresentationTimesUs() throws Exception {
|
public void increaseSpeed_outputsFramesAtTheCorrectPresentationTimesUs() throws Exception {
|
||||||
String testId = testName.getMethodName();
|
|
||||||
VideoFrameProcessor.Factory videoFrameProcessorFactory =
|
|
||||||
new DefaultVideoFrameProcessor.Factory.Builder().build();
|
|
||||||
ImmutableList<Effect> effects = ImmutableList.of(new SpeedChangeEffect(2f));
|
|
||||||
List<Long> outputPresentationTimesUs = new ArrayList<>();
|
List<Long> outputPresentationTimesUs = new ArrayList<>();
|
||||||
VideoFrameProcessorTestRunner.OnOutputFrameAvailableForRenderingListener
|
|
||||||
onOutputFrameAvailableForRenderingListener = outputPresentationTimesUs::add;
|
|
||||||
VideoFrameProcessorTestRunner videoFrameProcessorTestRunner =
|
VideoFrameProcessorTestRunner videoFrameProcessorTestRunner =
|
||||||
new VideoFrameProcessorTestRunner.Builder()
|
getVideoFrameProcessorTestRunner(
|
||||||
.setTestId(testId)
|
testName.getMethodName(), new SpeedChangeEffect(2), outputPresentationTimesUs::add);
|
||||||
.setVideoFrameProcessorFactory(videoFrameProcessorFactory)
|
|
||||||
.setEffects(effects)
|
|
||||||
.setOnOutputFrameAvailableForRenderingListener(
|
|
||||||
onOutputFrameAvailableForRenderingListener)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
videoFrameProcessorTestRunner.queueInputBitmap(
|
videoFrameProcessorTestRunner.queueInputBitmap(
|
||||||
readBitmap(IMAGE_PATH), C.MICROS_PER_SECOND, /* offsetToAddUs= */ 0L, /* frameRate= */ 5);
|
readBitmap(IMAGE_PATH),
|
||||||
|
/* durationUs= */ C.MICROS_PER_SECOND,
|
||||||
|
/* offsetToAddUs= */ 0L,
|
||||||
|
/* frameRate= */ 5);
|
||||||
videoFrameProcessorTestRunner.endFrameProcessing();
|
videoFrameProcessorTestRunner.endFrameProcessing();
|
||||||
|
|
||||||
assertThat(outputPresentationTimesUs)
|
assertThat(outputPresentationTimesUs)
|
||||||
.containsExactly(0L, 100_000L, 200_000L, 300_000L, 400_000L)
|
.containsExactly(0L, 100_000L, 200_000L, 300_000L, 400_000L)
|
||||||
.inOrder();
|
.inOrder();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void decreaseSpeed_outputsFramesAtTheCorrectPresentationTimesUs() throws Exception {
|
||||||
|
List<Long> outputPresentationTimesUs = new ArrayList<>();
|
||||||
|
VideoFrameProcessorTestRunner videoFrameProcessorTestRunner =
|
||||||
|
getVideoFrameProcessorTestRunner(
|
||||||
|
testName.getMethodName(), new SpeedChangeEffect(0.5f), outputPresentationTimesUs::add);
|
||||||
|
|
||||||
|
videoFrameProcessorTestRunner.queueInputBitmap(
|
||||||
|
readBitmap(IMAGE_PATH),
|
||||||
|
/* durationUs= */ C.MICROS_PER_SECOND,
|
||||||
|
/* offsetToAddUs= */ 0L,
|
||||||
|
/* frameRate= */ 5);
|
||||||
|
videoFrameProcessorTestRunner.endFrameProcessing();
|
||||||
|
|
||||||
|
assertThat(outputPresentationTimesUs)
|
||||||
|
.containsExactly(0L, 400_000L, 800_000L, 1_200_000L, 1_600_000L)
|
||||||
|
.inOrder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void variableSpeedChange_outputsFramesAtTheCorrectPresentationTimesUs() throws Exception {
|
||||||
|
SpeedProvider speedProvider =
|
||||||
|
TestSpeedProvider.createWithStartTimes(
|
||||||
|
/* startTimesUs= */ new long[] {0, 1_500_000, 3_000_000},
|
||||||
|
/* speeds= */ new float[] {1, 2, 1});
|
||||||
|
List<Long> outputPresentationTimesUs = new ArrayList<>();
|
||||||
|
VideoFrameProcessorTestRunner videoFrameProcessorTestRunner =
|
||||||
|
getVideoFrameProcessorTestRunner(
|
||||||
|
testName.getMethodName(),
|
||||||
|
new SpeedChangeEffect(speedProvider),
|
||||||
|
outputPresentationTimesUs::add);
|
||||||
|
|
||||||
|
videoFrameProcessorTestRunner.queueInputBitmap(
|
||||||
|
readBitmap(IMAGE_PATH),
|
||||||
|
/* durationUs= */ 5_000_000L,
|
||||||
|
/* offsetToAddUs= */ 0L,
|
||||||
|
/* frameRate= */ 1);
|
||||||
|
videoFrameProcessorTestRunner.queueInputBitmap(
|
||||||
|
readBitmap(IMAGE_PATH),
|
||||||
|
/* durationUs= */ 5_000_000L,
|
||||||
|
/* offsetToAddUs= */ 5_000_000L,
|
||||||
|
/* frameRate= */ 1);
|
||||||
|
videoFrameProcessorTestRunner.endFrameProcessing();
|
||||||
|
|
||||||
|
ImmutableList<Long> firstStreamExpectedTimestamps =
|
||||||
|
ImmutableList.of(0L, 1_000_000L, 1_750_000L, 2_250_000L, 3_250_000L);
|
||||||
|
ImmutableList<Long> secondStreamExpectedTimestamps =
|
||||||
|
ImmutableList.of(5_000_000L, 6_000_000L, 6_750_000L, 7_250_000L, 8_250_000L);
|
||||||
|
ImmutableList<Long> allExpectedTimestamps =
|
||||||
|
new ImmutableList.Builder<Long>()
|
||||||
|
.addAll(firstStreamExpectedTimestamps)
|
||||||
|
.addAll(secondStreamExpectedTimestamps)
|
||||||
|
.build();
|
||||||
|
assertThat(outputPresentationTimesUs)
|
||||||
|
.containsExactlyElementsIn(allExpectedTimestamps)
|
||||||
|
.inOrder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void
|
||||||
|
variableSpeedChange_multipleSpeedChangesBetweenFrames_outputsFramesAtTheCorrectPresentationTimesUs()
|
||||||
|
throws Exception {
|
||||||
|
SpeedProvider speedProvider =
|
||||||
|
TestSpeedProvider.createWithStartTimes(
|
||||||
|
/* startTimesUs= */ new long[] {0, 1_000_000, 2_000_000},
|
||||||
|
/* speeds= */ new float[] {4, 2, 1});
|
||||||
|
List<Long> outputPresentationTimesUs = new ArrayList<>();
|
||||||
|
VideoFrameProcessorTestRunner videoFrameProcessorTestRunner =
|
||||||
|
getVideoFrameProcessorTestRunner(
|
||||||
|
testName.getMethodName(),
|
||||||
|
new SpeedChangeEffect(speedProvider),
|
||||||
|
outputPresentationTimesUs::add);
|
||||||
|
Bitmap bitmap = readBitmap(IMAGE_PATH);
|
||||||
|
ImmutableList<Long> inputTimestamps = ImmutableList.of(0L, 4_000_000L, 5_000_000L);
|
||||||
|
|
||||||
|
videoFrameProcessorTestRunner.queueInputBitmaps(
|
||||||
|
bitmap.getWidth(),
|
||||||
|
bitmap.getHeight(),
|
||||||
|
Pair.create(bitmap, createTimestampIterator(inputTimestamps)));
|
||||||
|
videoFrameProcessorTestRunner.endFrameProcessing();
|
||||||
|
|
||||||
|
assertThat(outputPresentationTimesUs).containsExactly(0L, 2_750_000L, 3_750_000L).inOrder();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static VideoFrameProcessorTestRunner getVideoFrameProcessorTestRunner(
|
||||||
|
String testId,
|
||||||
|
GlEffect speedChangeEffect,
|
||||||
|
OnOutputFrameAvailableForRenderingListener onOutputFrameAvailableForRenderingListener)
|
||||||
|
throws VideoFrameProcessingException {
|
||||||
|
VideoFrameProcessor.Factory videoFrameProcessorFactory =
|
||||||
|
new DefaultVideoFrameProcessor.Factory.Builder().build();
|
||||||
|
ImmutableList<Effect> effects = ImmutableList.of(speedChangeEffect);
|
||||||
|
return new VideoFrameProcessorTestRunner.Builder()
|
||||||
|
.setTestId(testId)
|
||||||
|
.setVideoFrameProcessorFactory(videoFrameProcessorFactory)
|
||||||
|
.setEffects(effects)
|
||||||
|
.setOnOutputFrameAvailableForRenderingListener(onOutputFrameAvailableForRenderingListener)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,33 +19,57 @@ import static androidx.media3.common.util.Assertions.checkArgument;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import androidx.annotation.FloatRange;
|
import androidx.annotation.FloatRange;
|
||||||
import androidx.media3.common.VideoFrameProcessingException;
|
import androidx.media3.common.C;
|
||||||
|
import androidx.media3.common.audio.SpeedProvider;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies a speed change by updating the frame timestamps.
|
* Applies a speed change by updating the frame timestamps.
|
||||||
*
|
*
|
||||||
* <p>This effect doesn't drop any frames.
|
* <p>This effect doesn't drop any frames.
|
||||||
|
*
|
||||||
|
* <p>This effect is not supported for effects previewing.
|
||||||
*/
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public final class SpeedChangeEffect implements GlEffect {
|
public final class SpeedChangeEffect implements GlEffect {
|
||||||
|
|
||||||
private final float speed;
|
private final SpeedProvider speedProvider;
|
||||||
|
|
||||||
/** Creates an instance. */
|
/** Creates an instance that applies the same {@code speed} change to all the timestamps. */
|
||||||
public SpeedChangeEffect(@FloatRange(from = 0, fromInclusive = false) float speed) {
|
public SpeedChangeEffect(@FloatRange(from = 0, fromInclusive = false) float speed) {
|
||||||
checkArgument(speed > 0f);
|
checkArgument(speed > 0f);
|
||||||
this.speed = speed;
|
speedProvider =
|
||||||
|
new SpeedProvider() {
|
||||||
|
@Override
|
||||||
|
public float getSpeed(long timeUs) {
|
||||||
|
return speed;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getNextSpeedChangeTimeUs(long timeUs) {
|
||||||
|
return C.TIME_UNSET;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance.
|
||||||
|
*
|
||||||
|
* @param speedProvider The {@link SpeedProvider} specifying the speed changes. Applied on each
|
||||||
|
* stream assuming the first frame timestamp of the input media is 0.
|
||||||
|
*/
|
||||||
|
public SpeedChangeEffect(SpeedProvider speedProvider) {
|
||||||
|
this.speedProvider = speedProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GlShaderProgram toGlShaderProgram(Context context, boolean useHdr)
|
public GlShaderProgram toGlShaderProgram(Context context, boolean useHdr) {
|
||||||
throws VideoFrameProcessingException {
|
return new SpeedChangeShaderProgram(speedProvider);
|
||||||
return new SpeedChangeShaderProgram(speed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isNoOp(int inputWidth, int inputHeight) {
|
public boolean isNoOp(int inputWidth, int inputHeight) {
|
||||||
return speed == 1f;
|
return speedProvider.getSpeed(/* timeUs= */ 0) == 1
|
||||||
|
&& speedProvider.getNextSpeedChangeTimeUs(/* timeUs= */ 0) == C.TIME_UNSET;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,24 +15,95 @@
|
|||||||
*/
|
*/
|
||||||
package androidx.media3.effect;
|
package androidx.media3.effect;
|
||||||
|
|
||||||
|
import androidx.media3.common.C;
|
||||||
import androidx.media3.common.GlObjectsProvider;
|
import androidx.media3.common.GlObjectsProvider;
|
||||||
import androidx.media3.common.GlTextureInfo;
|
import androidx.media3.common.GlTextureInfo;
|
||||||
|
import androidx.media3.common.audio.SpeedProvider;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
|
||||||
/** Applies a speed change by updating the frame timestamps. */
|
/**
|
||||||
|
* Applies the speed changes specified in a {@link SpeedProvider} change by updating the frame
|
||||||
|
* timestamps.
|
||||||
|
*
|
||||||
|
* <p>Does not support seeking in effects previewing.
|
||||||
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
/* package */ final class SpeedChangeShaderProgram extends PassthroughShaderProgram {
|
/* package */ final class SpeedChangeShaderProgram extends PassthroughShaderProgram {
|
||||||
|
|
||||||
private final float speed;
|
private final OffsetSpeedProvider speedProvider;
|
||||||
|
|
||||||
public SpeedChangeShaderProgram(float speed) {
|
private long lastSpeedChangeInputTimeUs;
|
||||||
|
private long lastSpeedChangeOutputTimeUs;
|
||||||
|
|
||||||
|
public SpeedChangeShaderProgram(SpeedProvider speedProvider) {
|
||||||
super();
|
super();
|
||||||
this.speed = speed;
|
this.speedProvider = new OffsetSpeedProvider(speedProvider);
|
||||||
|
lastSpeedChangeInputTimeUs = C.TIME_UNSET;
|
||||||
|
lastSpeedChangeOutputTimeUs = C.TIME_UNSET;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void queueInputFrame(
|
public void queueInputFrame(
|
||||||
GlObjectsProvider glObjectsProvider, GlTextureInfo inputTexture, long presentationTimeUs) {
|
GlObjectsProvider glObjectsProvider, GlTextureInfo inputTexture, long presentationTimeUs) {
|
||||||
super.queueInputFrame(glObjectsProvider, inputTexture, (long) (presentationTimeUs / speed));
|
long outputPresentationTimeUs;
|
||||||
|
if (lastSpeedChangeInputTimeUs == C.TIME_UNSET) {
|
||||||
|
outputPresentationTimeUs = presentationTimeUs;
|
||||||
|
lastSpeedChangeInputTimeUs = presentationTimeUs;
|
||||||
|
lastSpeedChangeOutputTimeUs = outputPresentationTimeUs;
|
||||||
|
speedProvider.setOffset(presentationTimeUs);
|
||||||
|
} else {
|
||||||
|
long nextSpeedChangeInputTimeUs =
|
||||||
|
speedProvider.getNextSpeedChangeTimeUs(lastSpeedChangeInputTimeUs);
|
||||||
|
while (nextSpeedChangeInputTimeUs != C.TIME_UNSET
|
||||||
|
&& nextSpeedChangeInputTimeUs <= presentationTimeUs) {
|
||||||
|
lastSpeedChangeOutputTimeUs =
|
||||||
|
getOutputTimeUs(
|
||||||
|
nextSpeedChangeInputTimeUs, speedProvider.getSpeed(lastSpeedChangeInputTimeUs));
|
||||||
|
lastSpeedChangeInputTimeUs = nextSpeedChangeInputTimeUs;
|
||||||
|
nextSpeedChangeInputTimeUs =
|
||||||
|
speedProvider.getNextSpeedChangeTimeUs(lastSpeedChangeInputTimeUs);
|
||||||
|
}
|
||||||
|
outputPresentationTimeUs =
|
||||||
|
getOutputTimeUs(presentationTimeUs, speedProvider.getSpeed(presentationTimeUs));
|
||||||
|
}
|
||||||
|
super.queueInputFrame(glObjectsProvider, inputTexture, outputPresentationTimeUs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void signalEndOfCurrentInputStream() {
|
||||||
|
super.signalEndOfCurrentInputStream();
|
||||||
|
lastSpeedChangeInputTimeUs = C.TIME_UNSET;
|
||||||
|
lastSpeedChangeOutputTimeUs = C.TIME_UNSET;
|
||||||
|
}
|
||||||
|
|
||||||
|
private long getOutputTimeUs(long inputTimeUs, float speed) {
|
||||||
|
return (long)
|
||||||
|
(lastSpeedChangeOutputTimeUs + (inputTimeUs - lastSpeedChangeInputTimeUs) / speed);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class OffsetSpeedProvider implements SpeedProvider {
|
||||||
|
|
||||||
|
private final SpeedProvider speedProvider;
|
||||||
|
|
||||||
|
private long offset;
|
||||||
|
|
||||||
|
public OffsetSpeedProvider(SpeedProvider speedProvider) {
|
||||||
|
this.speedProvider = speedProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOffset(long offset) {
|
||||||
|
this.offset = offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getSpeed(long timeUs) {
|
||||||
|
return speedProvider.getSpeed(timeUs - offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getNextSpeedChangeTimeUs(long timeUs) {
|
||||||
|
long nextSpeedChangeTimeUs = speedProvider.getNextSpeedChangeTimeUs(timeUs - offset);
|
||||||
|
return nextSpeedChangeTimeUs == C.TIME_UNSET ? C.TIME_UNSET : offset + nextSpeedChangeTimeUs;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 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.test.utils;
|
||||||
|
|
||||||
|
import static androidx.media3.common.util.Assertions.checkArgument;
|
||||||
|
|
||||||
|
import androidx.media3.common.C;
|
||||||
|
import androidx.media3.common.audio.AudioProcessor.AudioFormat;
|
||||||
|
import androidx.media3.common.audio.SpeedProvider;
|
||||||
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
import androidx.media3.common.util.Util;
|
||||||
|
|
||||||
|
/** {@link SpeedProvider} for tests */
|
||||||
|
@UnstableApi
|
||||||
|
public final class TestSpeedProvider implements SpeedProvider {
|
||||||
|
|
||||||
|
private final long[] startTimesUs;
|
||||||
|
private final float[] speeds;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@code TestSpeedProvider} instance.
|
||||||
|
*
|
||||||
|
* @param startTimesUs The speed change start times, in microseconds. The values must be distinct
|
||||||
|
* and in increasing order.
|
||||||
|
* @param speeds The speeds corresponding to each start time. Consecutive values must be distinct.
|
||||||
|
* @return A {@code TestSpeedProvider}.
|
||||||
|
*/
|
||||||
|
public static TestSpeedProvider createWithStartTimes(long[] startTimesUs, float[] speeds) {
|
||||||
|
return new TestSpeedProvider(startTimesUs, speeds);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@code TestSpeedProvider} instance.
|
||||||
|
*
|
||||||
|
* @param audioFormat the {@link AudioFormat}.
|
||||||
|
* @param frameCounts The frame counts for which the same speed should be applied.
|
||||||
|
* @param speeds The speeds corresponding to each frame count. Consecutive values must be
|
||||||
|
* distinct.
|
||||||
|
* @return A {@code TestSpeedProvider}.
|
||||||
|
*/
|
||||||
|
public static TestSpeedProvider createWithFrameCounts(
|
||||||
|
AudioFormat audioFormat, int[] frameCounts, float[] speeds) {
|
||||||
|
long[] startTimesUs = new long[frameCounts.length];
|
||||||
|
int totalFrameCount = 0;
|
||||||
|
for (int i = 0; i < frameCounts.length; i++) {
|
||||||
|
startTimesUs[i] = totalFrameCount * C.MICROS_PER_SECOND / audioFormat.sampleRate;
|
||||||
|
totalFrameCount += frameCounts[i];
|
||||||
|
}
|
||||||
|
return new TestSpeedProvider(startTimesUs, speeds);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TestSpeedProvider(long[] startTimesUs, float[] speeds) {
|
||||||
|
checkArgument(startTimesUs.length == speeds.length);
|
||||||
|
this.startTimesUs = startTimesUs;
|
||||||
|
this.speeds = speeds;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getSpeed(long timeUs) {
|
||||||
|
int index =
|
||||||
|
Util.binarySearchFloor(
|
||||||
|
startTimesUs, timeUs, /* inclusive= */ true, /* stayInBounds= */ true);
|
||||||
|
return speeds[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getNextSpeedChangeTimeUs(long timeUs) {
|
||||||
|
int index =
|
||||||
|
Util.binarySearchCeil(
|
||||||
|
startTimesUs, timeUs, /* inclusive= */ false, /* stayInBounds= */ false);
|
||||||
|
return index < startTimesUs.length ? startTimesUs[index] : C.TIME_UNSET;
|
||||||
|
}
|
||||||
|
}
|
@ -342,6 +342,8 @@ public final class Composition {
|
|||||||
/** The {@link VideoCompositorSettings} to apply to the composition. */
|
/** The {@link VideoCompositorSettings} to apply to the composition. */
|
||||||
public final VideoCompositorSettings videoCompositorSettings;
|
public final VideoCompositorSettings videoCompositorSettings;
|
||||||
|
|
||||||
|
// TODO: b/302695659 - Ensure composition level effects are only applied consistently between the
|
||||||
|
// different VideoGraphs.
|
||||||
/** The {@link Effects} to apply to the composition. */
|
/** The {@link Effects} to apply to the composition. */
|
||||||
public final Effects effects;
|
public final Effects effects;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user