mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
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:
parent
c861947bee
commit
270543555d
@ -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`.
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user