Add api to find duration after an effect is applied

PiperOrigin-RevId: 616157067
This commit is contained in:
tofunmi 2024-03-15 09:38:50 -07:00 committed by Copybara-Service
parent 415d779c62
commit 762065fae0
8 changed files with 143 additions and 1 deletions

View File

@ -20,4 +20,13 @@ import androidx.media3.common.util.UnstableApi;
/** Marker interface for a video frame effect. */ /** Marker interface for a video frame effect. */
@UnstableApi @UnstableApi
public interface Effect {} public interface Effect {
/**
* Returns the expected duration of the output stream when the effect is applied given a input
* {@code durationUs}.
*/
default long getDurationAfterEffectApplied(long durationUs) {
return durationUs;
}
}

View File

@ -128,6 +128,14 @@ public interface AudioProcessor {
/** An empty, direct {@link ByteBuffer}. */ /** An empty, direct {@link ByteBuffer}. */
ByteBuffer EMPTY_BUFFER = ByteBuffer.allocateDirect(0).order(ByteOrder.nativeOrder()); ByteBuffer EMPTY_BUFFER = ByteBuffer.allocateDirect(0).order(ByteOrder.nativeOrder());
/**
* Returns the expected duration of the output stream when the processor is applied given a input
* {@code durationUs}.
*/
default long getDurationAfterProcessorApplied(long durationUs) {
return durationUs;
}
/** /**
* Configures the processor to process input audio with the specified format. After calling this * Configures the processor to process input audio with the specified format. After calling this
* method, call {@link #isActive()} to determine whether the audio processor is active. Returns * method, call {@link #isActive()} to determine whether the audio processor is active. Returns

View File

@ -171,6 +171,11 @@ public class SonicAudioProcessor implements AudioProcessor {
return inputBytes - checkNotNull(sonic).getPendingInputBytes(); return inputBytes - checkNotNull(sonic).getPendingInputBytes();
} }
@Override
public long getDurationAfterProcessorApplied(long durationUs) {
return getPlayoutDuration(durationUs);
}
@Override @Override
public final AudioFormat configure(AudioFormat inputAudioFormat) public final AudioFormat configure(AudioFormat inputAudioFormat)
throws UnhandledAudioFormatException { throws UnhandledAudioFormatException {

View File

@ -22,6 +22,7 @@ import static java.lang.Math.round;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.util.LongArray; import androidx.media3.common.util.LongArray;
import androidx.media3.common.util.LongArrayQueue; import androidx.media3.common.util.LongArrayQueue;
import androidx.media3.common.util.SpeedProviderUtil;
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 java.nio.ByteBuffer; import java.nio.ByteBuffer;
@ -75,6 +76,11 @@ public final class SpeedChangingAudioProcessor extends BaseAudioProcessor {
currentSpeed = 1f; currentSpeed = 1f;
} }
@Override
public long getDurationAfterProcessorApplied(long durationUs) {
return SpeedProviderUtil.getDurationAfterSpeedProviderApplied(speedProvider, durationUs);
}
@Override @Override
public AudioFormat onConfigure(AudioFormat inputAudioFormat) public AudioFormat onConfigure(AudioFormat inputAudioFormat)
throws UnhandledAudioFormatException { throws UnhandledAudioFormatException {

View File

@ -0,0 +1,50 @@
/*
* 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.common.util;
import static java.lang.Math.min;
import static java.lang.Math.round;
import androidx.media3.common.C;
import androidx.media3.common.audio.SpeedProvider;
/** Utilities for {@link SpeedProvider}. */
@UnstableApi
public class SpeedProviderUtil {
private SpeedProviderUtil() {}
/**
* Returns the duration of the output when the given {@link SpeedProvider} is applied given an
* input stream with the given {@code durationUs}.
*/
public static long getDurationAfterSpeedProviderApplied(
SpeedProvider speedProvider, long durationUs) {
long speedChangeTimeUs = 0;
double outputDurationUs = 0;
while (speedChangeTimeUs < durationUs) {
long nextSpeedChangeTimeUs = speedProvider.getNextSpeedChangeTimeUs(speedChangeTimeUs);
if (nextSpeedChangeTimeUs == C.TIME_UNSET) {
nextSpeedChangeTimeUs = Long.MAX_VALUE;
}
outputDurationUs +=
(min(nextSpeedChangeTimeUs, durationUs) - speedChangeTimeUs)
/ (double) speedProvider.getSpeed(speedChangeTimeUs);
speedChangeTimeUs = nextSpeedChangeTimeUs;
}
return round(outputDurationUs);
}
}

View File

@ -0,0 +1,51 @@
/*
* Copyright (C) 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.common.util;
import static androidx.media3.common.util.SpeedProviderUtil.getDurationAfterSpeedProviderApplied;
import static com.google.common.truth.Truth.assertThat;
import androidx.media3.common.audio.SpeedProvider;
import androidx.media3.test.utils.TestSpeedProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Unit test for {@link SpeedProviderUtil}. */
@RunWith(AndroidJUnit4.class)
public class SpeedProviderUtilTest {
@Test
public void getDurationAfterProcessorApplied_returnsCorrectDuration() throws Exception {
SpeedProvider speedProvider =
TestSpeedProvider.createWithStartTimes(
/* startTimesUs= */ new long[] {0, 120}, /* speeds= */ new float[] {3, 0.5f});
assertThat(getDurationAfterSpeedProviderApplied(speedProvider, /* durationUs= */ 150))
.isEqualTo(100);
}
@Test
public void getDurationAfterProcessorApplied_durationOnSpeedChange_returnsCorrectDuration()
throws Exception {
SpeedProvider speedProvider =
TestSpeedProvider.createWithStartTimes(
/* startTimesUs= */ new long[] {0, 113}, /* speeds= */ new float[] {2, 1});
assertThat(getDurationAfterSpeedProviderApplied(speedProvider, /* durationUs= */ 113))
.isEqualTo(57);
}
}

View File

@ -21,6 +21,7 @@ import android.content.Context;
import androidx.annotation.FloatRange; import androidx.annotation.FloatRange;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.audio.SpeedProvider; import androidx.media3.common.audio.SpeedProvider;
import androidx.media3.common.util.SpeedProviderUtil;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
/** /**
@ -72,4 +73,9 @@ public final class SpeedChangeEffect implements GlEffect {
return speedProvider.getSpeed(/* timeUs= */ 0) == 1 return speedProvider.getSpeed(/* timeUs= */ 0) == 1
&& speedProvider.getNextSpeedChangeTimeUs(/* timeUs= */ 0) == C.TIME_UNSET; && speedProvider.getNextSpeedChangeTimeUs(/* timeUs= */ 0) == C.TIME_UNSET;
} }
@Override
public long getDurationAfterEffectApplied(long durationUs) {
return SpeedProviderUtil.getDurationAfterSpeedProviderApplied(speedProvider, durationUs);
}
} }

View File

@ -70,6 +70,13 @@ import java.nio.ByteBuffer;
return trimmedFrameCount; return trimmedFrameCount;
} }
@Override
public long getDurationAfterProcessorApplied(long durationUs) {
return durationUs
- Util.sampleCountToDurationUs(
/* sampleCount= */ trimEndFrames + trimStartFrames, inputAudioFormat.sampleRate);
}
@Override @Override
public AudioFormat onConfigure(AudioFormat inputAudioFormat) public AudioFormat onConfigure(AudioFormat inputAudioFormat)
throws UnhandledAudioFormatException { throws UnhandledAudioFormatException {