Add getSampleCryptoInfo API to MediaExtractorCompat

This method enables handling encrypted samples by providing the necessary decryption details.

PiperOrigin-RevId: 700729949
This commit is contained in:
rohks 2024-11-27 09:44:44 -08:00 committed by Copybara-Service
parent c861947bee
commit 270543555d
3 changed files with 102 additions and 0 deletions

View File

@ -20,6 +20,8 @@
* Reduce default values for `bufferForPlaybackMs` and
`bufferForPlaybackAfterRebufferMs` in `DefaultLoadControl` to 1000 and
2000 ms respectively.
* Add `MediaExtractorCompat`, a new class that provides equivalent
functionality to platform `MediaExtractor`.
* Transformer:
* Update parameters of `VideoFrameProcessor.registerInputStream` and
`VideoFrameProcessor.Listener.onInputStreamRegistered` to use `Format`.

View File

@ -26,6 +26,7 @@ import static org.junit.Assert.assertThrows;
import static org.junit.Assume.assumeTrue;
import android.content.Context;
import android.media.MediaCodec;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.media.metrics.LogSessionId;
@ -60,6 +61,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
import com.google.common.base.Function;
import com.google.common.io.Files;
import com.google.common.primitives.Bytes;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
@ -1063,6 +1065,70 @@ public class MediaExtractorCompatTest {
assertThat(psshMap.get(WIDEVINE_UUID)).isEqualTo(rawSchemeData);
}
@Test
public void
getSampleCryptoInfo_forEncryptedSample_returnsTrueAndPopulatesPlatformCryptoInfoCorrectly()
throws IOException {
TrackOutput.CryptoData cryptoData =
new TrackOutput.CryptoData(
/* cryptoMode= */ C.CRYPTO_MODE_AES_CTR,
/* encryptionKey= */ new byte[] {5, 6, 7, 8},
/* encryptedBlocks= */ 0,
/* clearBlocks= */ 0);
byte[] sampleData = new byte[] {0, 1, 2};
byte[] initializationVector = new byte[] {7, 6, 5, 4, 3, 2, 1, 0, 7, 6, 5, 4, 3, 2, 1, 0};
byte[] encryptedSampleData =
Bytes.concat(
new byte[] {
0x10, // subsampleEncryption = false (1 bit), ivSize = 16 (7 bits).
},
initializationVector,
sampleData);
TrackOutput[] outputs = new TrackOutput[1];
fakeExtractor.addReadAction(
(input, seekPosition) -> {
outputs[0] = extractorOutput.track(/* id= */ 0, C.TRACK_TYPE_VIDEO);
outputs[0].format(PLACEHOLDER_FORMAT_VIDEO);
extractorOutput.endTracks();
return Extractor.RESULT_CONTINUE;
});
mediaExtractorCompat.selectTrack(0);
fakeExtractor.addReadAction(
(input, seekPosition) -> {
outputSampleData(outputs[0], encryptedSampleData);
outputs[0].sampleMetadata(
/* timeUs= */ 0,
C.BUFFER_FLAG_KEY_FRAME | C.BUFFER_FLAG_ENCRYPTED,
/* size= */ encryptedSampleData.length,
/* offset= */ 0,
cryptoData);
return Extractor.RESULT_CONTINUE;
});
mediaExtractorCompat.setDataSource(PLACEHOLDER_URI, /* offset= */ 0);
MediaCodec.CryptoInfo platformCryptoInfo = new MediaCodec.CryptoInfo();
assertThat(mediaExtractorCompat.getSampleCryptoInfo(platformCryptoInfo)).isTrue();
// Verify platform crypto info data.
assertThat(platformCryptoInfo.numSubSamples).isEqualTo(1);
assertThat(platformCryptoInfo.numBytesOfClearData).hasLength(1);
assertThat(platformCryptoInfo.numBytesOfClearData[0]).isEqualTo(0);
assertThat(platformCryptoInfo.numBytesOfEncryptedData).hasLength(1);
assertThat(platformCryptoInfo.numBytesOfEncryptedData[0]).isEqualTo(sampleData.length);
assertThat(platformCryptoInfo.key).isEqualTo(cryptoData.encryptionKey);
assertThat(platformCryptoInfo.iv).isEqualTo(initializationVector);
assertThat(platformCryptoInfo.mode).isEqualTo(cryptoData.cryptoMode);
// Verify sample data and flags.
assertThat(mediaExtractorCompat.getSampleFlags())
.isEqualTo(MediaExtractor.SAMPLE_FLAG_SYNC | MediaExtractor.SAMPLE_FLAG_ENCRYPTED);
ByteBuffer buffer = ByteBuffer.allocate(sampleData.length);
assertThat(mediaExtractorCompat.readSampleData(buffer, /* offset= */ 0))
.isEqualTo(sampleData.length);
for (int i = 0; i < buffer.remaining(); i++) {
assertThat(buffer.get()).isEqualTo(sampleData[i]);
}
}
// Internal methods.
private void assertReadSample(int trackIndex, long timeUs, int size, byte... sampleData) {
@ -1070,6 +1136,7 @@ public class MediaExtractorCompatTest {
assertThat(mediaExtractorCompat.getSampleTime()).isEqualTo(timeUs);
assertThat(mediaExtractorCompat.getSampleFlags()).isEqualTo(MediaExtractor.SAMPLE_FLAG_SYNC);
assertThat(mediaExtractorCompat.getSampleSize()).isEqualTo(size);
assertThat(mediaExtractorCompat.getSampleCryptoInfo(new MediaCodec.CryptoInfo())).isFalse();
ByteBuffer buffer = ByteBuffer.allocate(100);
assertThat(mediaExtractorCompat.readSampleData(buffer, /* offset= */ 0))
.isEqualTo(sampleData.length);

View File

@ -25,6 +25,7 @@ import static java.lang.Math.max;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.media.MediaCodec;
import android.media.MediaDataSource;
import android.media.MediaExtractor;
import android.media.MediaFormat;
@ -594,6 +595,38 @@ public final class MediaExtractorCompat {
return sampleMetadataQueue.peekFirst().flags;
}
/**
* Returns {@code true} if the current sample is at least partially encrypted and fills the
* provided {@link MediaCodec.CryptoInfo} structure with relevant decryption information.
*
* @param info The {@link MediaCodec.CryptoInfo} structure to be filled with decryption data.
* @return {@code true} if the sample is at least partially encrypted, {@code false} otherwise.
*/
public boolean getSampleCryptoInfo(MediaCodec.CryptoInfo info) {
if (!advanceToSampleOrEndOfInput()) {
return false;
}
boolean isEncrypted =
(sampleMetadataQueue.peekFirst().flags & MediaExtractor.SAMPLE_FLAG_ENCRYPTED) != 0;
if (!isEncrypted) {
return false;
}
peekNextSelectedTrackSample(sampleHolderWithBufferReplacementEnabled);
populatePlatformCryptoInfoParameters(info);
return true;
}
private void populatePlatformCryptoInfoParameters(MediaCodec.CryptoInfo info) {
MediaCodec.CryptoInfo platformCryptoInfo =
checkNotNull(sampleHolderWithBufferReplacementEnabled.cryptoInfo).getFrameworkCryptoInfo();
info.numSubSamples = platformCryptoInfo.numSubSamples;
info.numBytesOfClearData = platformCryptoInfo.numBytesOfClearData;
info.numBytesOfEncryptedData = platformCryptoInfo.numBytesOfEncryptedData;
info.key = platformCryptoInfo.key;
info.iv = platformCryptoInfo.iv;
info.mode = platformCryptoInfo.mode;
}
/** Sets the {@link LogSessionId} for MediaExtractorCompat. */
@RequiresApi(31)
public void setLogSessionId(LogSessionId logSessionId) {