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
|
* Reduce default values for `bufferForPlaybackMs` and
|
||||||
`bufferForPlaybackAfterRebufferMs` in `DefaultLoadControl` to 1000 and
|
`bufferForPlaybackAfterRebufferMs` in `DefaultLoadControl` to 1000 and
|
||||||
2000 ms respectively.
|
2000 ms respectively.
|
||||||
|
* Add `MediaExtractorCompat`, a new class that provides equivalent
|
||||||
|
functionality to platform `MediaExtractor`.
|
||||||
* Transformer:
|
* Transformer:
|
||||||
* Update parameters of `VideoFrameProcessor.registerInputStream` and
|
* Update parameters of `VideoFrameProcessor.registerInputStream` and
|
||||||
`VideoFrameProcessor.Listener.onInputStreamRegistered` to use `Format`.
|
`VideoFrameProcessor.Listener.onInputStreamRegistered` to use `Format`.
|
||||||
|
@ -26,6 +26,7 @@ import static org.junit.Assert.assertThrows;
|
|||||||
import static org.junit.Assume.assumeTrue;
|
import static org.junit.Assume.assumeTrue;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.media.MediaCodec;
|
||||||
import android.media.MediaExtractor;
|
import android.media.MediaExtractor;
|
||||||
import android.media.MediaFormat;
|
import android.media.MediaFormat;
|
||||||
import android.media.metrics.LogSessionId;
|
import android.media.metrics.LogSessionId;
|
||||||
@ -60,6 +61,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
|
|||||||
import androidx.test.platform.app.InstrumentationRegistry;
|
import androidx.test.platform.app.InstrumentationRegistry;
|
||||||
import com.google.common.base.Function;
|
import com.google.common.base.Function;
|
||||||
import com.google.common.io.Files;
|
import com.google.common.io.Files;
|
||||||
|
import com.google.common.primitives.Bytes;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
@ -1063,6 +1065,70 @@ public class MediaExtractorCompatTest {
|
|||||||
assertThat(psshMap.get(WIDEVINE_UUID)).isEqualTo(rawSchemeData);
|
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.
|
// Internal methods.
|
||||||
|
|
||||||
private void assertReadSample(int trackIndex, long timeUs, int size, byte... sampleData) {
|
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.getSampleTime()).isEqualTo(timeUs);
|
||||||
assertThat(mediaExtractorCompat.getSampleFlags()).isEqualTo(MediaExtractor.SAMPLE_FLAG_SYNC);
|
assertThat(mediaExtractorCompat.getSampleFlags()).isEqualTo(MediaExtractor.SAMPLE_FLAG_SYNC);
|
||||||
assertThat(mediaExtractorCompat.getSampleSize()).isEqualTo(size);
|
assertThat(mediaExtractorCompat.getSampleSize()).isEqualTo(size);
|
||||||
|
assertThat(mediaExtractorCompat.getSampleCryptoInfo(new MediaCodec.CryptoInfo())).isFalse();
|
||||||
ByteBuffer buffer = ByteBuffer.allocate(100);
|
ByteBuffer buffer = ByteBuffer.allocate(100);
|
||||||
assertThat(mediaExtractorCompat.readSampleData(buffer, /* offset= */ 0))
|
assertThat(mediaExtractorCompat.readSampleData(buffer, /* offset= */ 0))
|
||||||
.isEqualTo(sampleData.length);
|
.isEqualTo(sampleData.length);
|
||||||
|
@ -25,6 +25,7 @@ import static java.lang.Math.max;
|
|||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.AssetFileDescriptor;
|
import android.content.res.AssetFileDescriptor;
|
||||||
|
import android.media.MediaCodec;
|
||||||
import android.media.MediaDataSource;
|
import android.media.MediaDataSource;
|
||||||
import android.media.MediaExtractor;
|
import android.media.MediaExtractor;
|
||||||
import android.media.MediaFormat;
|
import android.media.MediaFormat;
|
||||||
@ -594,6 +595,38 @@ public final class MediaExtractorCompat {
|
|||||||
return sampleMetadataQueue.peekFirst().flags;
|
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. */
|
/** Sets the {@link LogSessionId} for MediaExtractorCompat. */
|
||||||
@RequiresApi(31)
|
@RequiresApi(31)
|
||||||
public void setLogSessionId(LogSessionId logSessionId) {
|
public void setLogSessionId(LogSessionId logSessionId) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user