Skip to content

Commit

Permalink
Always check audio processor chain for media playout duration
Browse files Browse the repository at this point in the history
When the PlaybackParameters are set to their DEFAULT value, we
currently bypass the audio processor chain when determining the
output media position, under the assumption that no timestamp
change happens in the audio processors. This assumption may not
be true as the audio processors can change playout durations on
their own accord independent of the provided PlaybackParameters.

To correctly reflect any updated playout duration, we can just
always check the audio processor chain. The default implementation
will continue to assume that only the SonicAudioProcessor changes
the playout duration.

PiperOrigin-RevId: 631726112
  • Loading branch information
tonihei authored and copybara-github committed May 8, 2024
1 parent 49c75fd commit 38b9a5d
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,9 @@ public boolean applySkipSilenceEnabled(boolean skipSilenceEnabled) {

@Override
public long getMediaDuration(long playoutDuration) {
return sonicAudioProcessor.getMediaDuration(playoutDuration);
return sonicAudioProcessor.isActive()
? sonicAudioProcessor.getMediaDuration(playoutDuration)
: playoutDuration;
}

@Override
Expand Down Expand Up @@ -1675,9 +1677,7 @@ private long applyMediaPositionParameters(long positionUs) {

long playoutDurationSinceLastCheckpointUs =
positionUs - mediaPositionParameters.audioTrackPositionUs;
if (mediaPositionParameters.playbackParameters.equals(PlaybackParameters.DEFAULT)) {
return mediaPositionParameters.mediaTimeUs + playoutDurationSinceLastCheckpointUs;
} else if (mediaPositionParametersCheckpoints.isEmpty()) {
if (mediaPositionParametersCheckpoints.isEmpty()) {
long mediaDurationSinceLastCheckpointUs =
audioProcessorChain.getMediaDuration(playoutDurationSinceLastCheckpointUs);
return mediaPositionParameters.mediaTimeUs + mediaDurationSinceLastCheckpointUs;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,22 @@
import androidx.media3.common.Format;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.PlaybackParameters;
import androidx.media3.common.audio.AudioProcessor;
import androidx.media3.common.audio.AudioProcessorChain;
import androidx.media3.exoplayer.audio.DefaultAudioSink.DefaultAudioProcessorChain;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowSystemClock;

/** Unit tests for {@link DefaultAudioSink}. */
@RunWith(AndroidJUnit4.class)
Expand Down Expand Up @@ -75,12 +79,61 @@ public void handlesSpecializedAudioProcessorArray() {
new DefaultAudioSink.Builder().setAudioProcessors(new TeeAudioProcessor[0]).build();
}

@Test
public void handlesBuffer_updatesPositionUsingAudioProcessorChain() throws Exception {
defaultAudioSink =
new DefaultAudioSink.Builder()
.setAudioProcessorChain(
new AudioProcessorChain() {
@Override
public AudioProcessor[] getAudioProcessors() {
return new AudioProcessor[0];
}

@Override
public PlaybackParameters applyPlaybackParameters(
PlaybackParameters playbackParameters) {
return playbackParameters;
}

@Override
public boolean applySkipSilenceEnabled(boolean skipSilenceEnabled) {
return false;
}

@Override
public long getMediaDuration(long playoutDuration) {
return playoutDuration * 2;
}

@Override
public long getSkippedOutputFrameCount() {
return 441; // 0.01 seconds at 44.1 kHz
}
})
.build();
configureDefaultAudioSink(CHANNEL_COUNT_STEREO);
retryUntilTrue(
() ->
defaultAudioSink.handleBuffer(
create1Sec44100HzSilenceBuffer(),
/* presentationTimeUs= */ 0,
/* encodedAccessUnitCount= */ 1));
defaultAudioSink.play();
ShadowSystemClock.advanceBy(1, TimeUnit.SECONDS);

long currentPositionUs = defaultAudioSink.getCurrentPositionUs(/* sourceEnded= */ false);

// Based on audio processor chain: 1 second * 2 + 0.01 seconds
assertThat(currentPositionUs).isEqualTo(2_010_000);
}

@Test
public void handlesBufferAfterReset() throws Exception {
configureDefaultAudioSink(CHANNEL_COUNT_STEREO);
assertThat(
defaultAudioSink.handleBuffer(
createDefaultSilenceBuffer(),
create1Sec44100HzSilenceBuffer(),
/* presentationTimeUs= */ 0,
/* encodedAccessUnitCount= */ 1))
.isTrue();
Expand All @@ -91,7 +144,7 @@ public void handlesBufferAfterReset() throws Exception {
retryUntilTrue(
() ->
defaultAudioSink.handleBuffer(
createDefaultSilenceBuffer(),
create1Sec44100HzSilenceBuffer(),
/* presentationTimeUs= */ 0,
/* encodedAccessUnitCount= */ 1));
}
Expand All @@ -102,7 +155,7 @@ public void handlesBufferAfterReset_withPlaybackSpeed() throws Exception {
configureDefaultAudioSink(CHANNEL_COUNT_STEREO);
assertThat(
defaultAudioSink.handleBuffer(
createDefaultSilenceBuffer(),
create1Sec44100HzSilenceBuffer(),
/* presentationTimeUs= */ 0,
/* encodedAccessUnitCount= */ 1))
.isTrue();
Expand All @@ -113,7 +166,7 @@ public void handlesBufferAfterReset_withPlaybackSpeed() throws Exception {
retryUntilTrue(
() ->
defaultAudioSink.handleBuffer(
createDefaultSilenceBuffer(),
create1Sec44100HzSilenceBuffer(),
/* presentationTimeUs= */ 0,
/* encodedAccessUnitCount= */ 1));
assertThat(defaultAudioSink.getPlaybackParameters())
Expand All @@ -125,7 +178,7 @@ public void handlesBufferAfterReset_withFormatChange() throws Exception {
configureDefaultAudioSink(CHANNEL_COUNT_STEREO);
assertThat(
defaultAudioSink.handleBuffer(
createDefaultSilenceBuffer(),
create1Sec44100HzSilenceBuffer(),
/* presentationTimeUs= */ 0,
/* encodedAccessUnitCount= */ 1))
.isTrue();
Expand All @@ -136,7 +189,7 @@ public void handlesBufferAfterReset_withFormatChange() throws Exception {
retryUntilTrue(
() ->
defaultAudioSink.handleBuffer(
createDefaultSilenceBuffer(),
create1Sec44100HzSilenceBuffer(),
/* presentationTimeUs= */ 0,
/* encodedAccessUnitCount= */ 1));
}
Expand All @@ -147,7 +200,7 @@ public void handlesBufferAfterReset_withFormatChangeAndPlaybackSpeed() throws Ex
configureDefaultAudioSink(CHANNEL_COUNT_STEREO);
assertThat(
defaultAudioSink.handleBuffer(
createDefaultSilenceBuffer(),
create1Sec44100HzSilenceBuffer(),
/* presentationTimeUs= */ 0,
/* encodedAccessUnitCount= */ 1))
.isTrue();
Expand All @@ -158,7 +211,7 @@ public void handlesBufferAfterReset_withFormatChangeAndPlaybackSpeed() throws Ex
retryUntilTrue(
() ->
defaultAudioSink.handleBuffer(
createDefaultSilenceBuffer(),
create1Sec44100HzSilenceBuffer(),
/* presentationTimeUs= */ 0,
/* encodedAccessUnitCount= */ 1));
assertThat(defaultAudioSink.getPlaybackParameters())
Expand All @@ -173,7 +226,7 @@ public void trimsStartFrames() throws Exception {
/* trimEndFrames= */ 0);
assertThat(
defaultAudioSink.handleBuffer(
createDefaultSilenceBuffer(),
create1Sec44100HzSilenceBuffer(),
/* presentationTimeUs= */ 0,
/* encodedAccessUnitCount= */ 1))
.isTrue();
Expand All @@ -193,7 +246,7 @@ public void trimsEndFrames() throws Exception {
/* trimEndFrames= */ TRIM_10_MS_FRAME_COUNT);
assertThat(
defaultAudioSink.handleBuffer(
createDefaultSilenceBuffer(),
create1Sec44100HzSilenceBuffer(),
/* presentationTimeUs= */ 0,
/* encodedAccessUnitCount= */ 1))
.isTrue();
Expand All @@ -213,7 +266,7 @@ public void trimsStartAndEndFrames() throws Exception {
/* trimEndFrames= */ TRIM_10_MS_FRAME_COUNT);
assertThat(
defaultAudioSink.handleBuffer(
createDefaultSilenceBuffer(),
create1Sec44100HzSilenceBuffer(),
/* presentationTimeUs= */ 0,
/* encodedAccessUnitCount= */ 1))
.isTrue();
Expand All @@ -230,7 +283,7 @@ public void getCurrentPosition_returnsPositionFromFirstBuffer() throws Exception
configureDefaultAudioSink(CHANNEL_COUNT_STEREO);
assertThat(
defaultAudioSink.handleBuffer(
createDefaultSilenceBuffer(),
create1Sec44100HzSilenceBuffer(),
/* presentationTimeUs= */ 5 * C.MICROS_PER_SECOND,
/* encodedAccessUnitCount= */ 1))
.isTrue();
Expand All @@ -242,7 +295,7 @@ public void getCurrentPosition_returnsPositionFromFirstBuffer() throws Exception
retryUntilTrue(
() ->
defaultAudioSink.handleBuffer(
createDefaultSilenceBuffer(),
create1Sec44100HzSilenceBuffer(),
/* presentationTimeUs= */ 8 * C.MICROS_PER_SECOND,
/* encodedAccessUnitCount= */ 1));
assertThat(defaultAudioSink.getCurrentPositionUs(/* sourceEnded= */ false))
Expand Down Expand Up @@ -336,7 +389,7 @@ public void setPlaybackParameters_doesNothingWhenTunnelingIsEnabled() throws Exc
configureDefaultAudioSink(/* channelCount= */ 2);
assertThat(
defaultAudioSink.handleBuffer(
createDefaultSilenceBuffer(),
create1Sec44100HzSilenceBuffer(),
/* presentationTimeUs= */ 5 * C.MICROS_PER_SECOND,
/* encodedAccessUnitCount= */ 1))
.isTrue();
Expand Down Expand Up @@ -372,7 +425,7 @@ private void configureDefaultAudioSink(int channelCount, int trimStartFrames, in
}

/** Creates a one second silence buffer for 44.1 kHz stereo 16-bit audio. */
private static ByteBuffer createDefaultSilenceBuffer() {
private static ByteBuffer create1Sec44100HzSilenceBuffer() {
return ByteBuffer.allocateDirect(
SAMPLE_RATE_44_1 * CHANNEL_COUNT_STEREO * BYTES_PER_FRAME_16_BIT)
.order(ByteOrder.nativeOrder());
Expand Down

0 comments on commit 38b9a5d

Please sign in to comment.