Skip decoder input buffers for encrypted content

Previous assertion was incorrect. Per-frame initialization
vectors are written to the output stream in
`FragmentedMp4Extractor`

PiperOrigin-RevId: 742203717
This commit is contained in:
dancho 2025-03-31 03:32:37 -07:00 committed by Copybara-Service
parent 427daef350
commit 73fa820828
2 changed files with 75 additions and 5 deletions

View File

@ -17,13 +17,19 @@ package androidx.media3.exoplayer.drm;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assume.assumeTrue;
import android.content.Context;
import androidx.media3.common.C;
import androidx.media3.common.MediaItem;
import androidx.media3.common.PlaybackException;
import androidx.media3.common.Player;
import androidx.media3.common.util.ConditionVariable;
import androidx.media3.common.util.Util;
import androidx.media3.exoplayer.DecoderCounters;
import androidx.media3.exoplayer.DefaultRenderersFactory;
import androidx.media3.exoplayer.ExoPlayer;
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import java.util.concurrent.atomic.AtomicReference;
import okhttp3.mockwebserver.MockResponse;
@ -97,4 +103,73 @@ public final class DrmPlaybackTest {
getInstrumentation().waitForIdleSync();
assertThat(playbackException.get()).isNull();
}
@Test
public void clearkeyPlayback_withLateThresholdToDropDecoderInput_dropsInputBuffers()
throws Exception {
// The API 21 emulator doesn't have a secure decoder. Due to b/18678462 MediaCodecUtil pretends
// that there is a secure decoder so we must only run this test on API 21 - i.e. we cannot
// assumeTrue() on getDecoderInfos.
assumeTrue(Util.SDK_INT > 21);
Context context = getInstrumentation().getContext();
MockWebServer mockWebServer = new MockWebServer();
mockWebServer.enqueue(new MockResponse().setResponseCode(200).setBody(CLEARKEY_RESPONSE));
mockWebServer.start();
MediaItem mediaItem =
new MediaItem.Builder()
.setUri("asset:///media/drm/sample_fragmented_clearkey.mp4")
.setDrmConfiguration(
new MediaItem.DrmConfiguration.Builder(C.CLEARKEY_UUID)
.setLicenseUri(mockWebServer.url("license").toString())
.build())
.build();
AtomicReference<ExoPlayer> player = new AtomicReference<>();
ConditionVariable playbackComplete = new ConditionVariable();
AtomicReference<PlaybackException> playbackException = new AtomicReference<>();
AtomicReference<DecoderCounters> decoderCountersAtomicReference = new AtomicReference<>();
getInstrumentation()
.runOnMainSync(
() -> {
player.set(
new ExoPlayer.Builder(
context,
new DefaultRenderersFactory(context)
.experimentalSetLateThresholdToDropDecoderInputUs(-100_000_000L),
new DefaultMediaSourceFactory(context)
.experimentalSetCodecsToParseWithinGopSampleDependencies(
C.VIDEO_CODEC_FLAG_H264))
.build());
player
.get()
.addListener(
new Player.Listener() {
@Override
public void onPlaybackStateChanged(@Player.State int playbackState) {
if (playbackState == Player.STATE_ENDED) {
decoderCountersAtomicReference.set(
player.get().getVideoDecoderCounters());
playbackComplete.open();
}
}
@Override
public void onPlayerError(PlaybackException error) {
playbackException.set(error);
playbackComplete.open();
}
});
player.get().setMediaItem(mediaItem);
player.get().prepare();
player.get().play();
});
playbackComplete.block();
getInstrumentation().runOnMainSync(() -> player.get().release());
getInstrumentation().waitForIdleSync();
assertThat(playbackException.get()).isNull();
// Which input buffers are dropped first depends on the number of MediaCodec buffer slots.
// This means the asserts cannot be isEqualTo.
assertThat(decoderCountersAtomicReference.get().droppedInputBufferCount).isAtLeast(1);
}
}

View File

@ -1527,11 +1527,6 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
// Make sure to decode and render the last frame.
return false;
}
if (buffer.isEncrypted()) {
// Commonly used decryption algorithms require updating the initialization vector for each
// block processed. Skipping input buffers before the decoder is not allowed.
return false;
}
boolean shouldSkipDecoderInputBuffer = isBufferBeforeStartTime(buffer);
if (!shouldSkipDecoderInputBuffer && !shouldDropDecoderInputBuffers) {
return false;