Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit fc74570e authored by Santiago Seifert's avatar Santiago Seifert
Browse files

Align CryptoInfo value and identity equality

CryptoInfo.pattern is private, so users need to ensure
CryptoInfo instances do not change between being obtained
from MediaParser, and being passed to MediaCodec. This CL
ensures that MediaParser does not modify any already output
CryptoInfo instances.

Bug: 154120292
Test: manually.
Test: atest CtsMediaParserTestCases.
Change-Id: I800b6e6ae98af490e5001a5da35cae5d3b8449e6
parent 58c7846f
Loading
Loading
Loading
Loading
+90 −34
Original line number Diff line number Diff line
@@ -401,9 +401,9 @@ public final class MediaParser {
         *     onSampleDataFound(int, MediaParser.InputReader)} for the specified track, since the
         *     last byte belonging to the sample whose metadata is being passed.
         * @param cryptoInfo Encryption data required to decrypt the sample. May be null for
         *     unencrypted samples. MediaParser may reuse {@link CryptoInfo} instances to avoid
         *     allocations, so implementations of this method must not write to or keep reference to
         *     the fields of this parameter.
         *     unencrypted samples. Implementors should treat any output {@link CryptoInfo}
         *     instances as immutable. MediaParser will not modify any output {@code cryptoInfos}
         *     and implementors should not modify them either.
         */
        void onSampleCompleted(
                int trackIndex,
@@ -1360,23 +1360,28 @@ public final class MediaParser {
    private class TrackOutputAdapter implements TrackOutput {

        private final int mTrackIndex;
        private final CryptoInfo mCryptoInfo;

        private CryptoInfo mLastOutputCryptoInfo;
        private CryptoInfo.Pattern mLastOutputEncryptionPattern;
        private CryptoData mLastReceivedCryptoData;

        @EncryptionDataReadState private int mEncryptionDataReadState;
        private int mEncryptionDataSizeToSubtractFromSampleDataSize;
        private int mEncryptionVectorSize;
        private byte[] mScratchIvSpace;
        private int mSubsampleEncryptionDataSize;
        private int[] mScratchSubsampleEncryptedBytesCount;
        private int[] mScratchSubsampleClearBytesCount;
        private boolean mHasSubsampleEncryptionData;
        private CryptoInfo.Pattern mEncryptionPattern;
        private int mSkippedSupplementalDataBytes;

        private TrackOutputAdapter(int trackIndex) {
            mTrackIndex = trackIndex;
            mCryptoInfo = new CryptoInfo();
            mCryptoInfo.iv = new byte[16]; // Size documented in CryptoInfo.
            mCryptoInfo.numBytesOfClearData = new int[0];
            mCryptoInfo.numBytesOfEncryptedData = new int[0];
            mScratchIvSpace = new byte[16]; // Size documented in CryptoInfo.
            mScratchSubsampleEncryptedBytesCount = new int[32];
            mScratchSubsampleClearBytesCount = new int[32];
            mEncryptionDataReadState = STATE_READING_SIGNAL_BYTE;
            mEncryptionPattern =
            mLastOutputEncryptionPattern =
                    new CryptoInfo.Pattern(/* blocksToEncrypt= */ 0, /* blocksToSkip= */ 0);
        }

@@ -1417,35 +1422,39 @@ public final class MediaParser {
                            mEncryptionDataReadState = STATE_READING_INIT_VECTOR;
                            break;
                        case STATE_READING_INIT_VECTOR:
                            Arrays.fill(mCryptoInfo.iv, (byte) 0); // Ensure 0-padding.
                            data.readBytes(mCryptoInfo.iv, /* offset= */ 0, mEncryptionVectorSize);
                            Arrays.fill(mScratchIvSpace, (byte) 0); // Ensure 0-padding.
                            data.readBytes(mScratchIvSpace, /* offset= */ 0, mEncryptionVectorSize);
                            length -= mEncryptionVectorSize;
                            if (mHasSubsampleEncryptionData) {
                                mEncryptionDataReadState = STATE_READING_SUBSAMPLE_ENCRYPTION_SIZE;
                            } else {
                                mCryptoInfo.numSubSamples = 0;
                                mSubsampleEncryptionDataSize = 0;
                                mEncryptionDataReadState = STATE_READING_SIGNAL_BYTE;
                            }
                            break;
                        case STATE_READING_SUBSAMPLE_ENCRYPTION_SIZE:
                            int numSubSamples = data.readUnsignedShort();
                            mCryptoInfo.numSubSamples = numSubSamples;
                            if (mCryptoInfo.numBytesOfClearData.length < numSubSamples) {
                                mCryptoInfo.numBytesOfClearData = new int[numSubSamples];
                                mCryptoInfo.numBytesOfEncryptedData = new int[numSubSamples];
                            mSubsampleEncryptionDataSize = data.readUnsignedShort();
                            if (mScratchSubsampleClearBytesCount.length
                                    < mSubsampleEncryptionDataSize) {
                                mScratchSubsampleClearBytesCount =
                                        new int[mSubsampleEncryptionDataSize];
                                mScratchSubsampleEncryptedBytesCount =
                                        new int[mSubsampleEncryptionDataSize];
                            }
                            length -= 2;
                            mEncryptionDataSizeToSubtractFromSampleDataSize +=
                                    2 + numSubSamples * BYTES_PER_SUBSAMPLE_ENCRYPTION_ENTRY;
                                    2
                                            + mSubsampleEncryptionDataSize
                                                    * BYTES_PER_SUBSAMPLE_ENCRYPTION_ENTRY;
                            mEncryptionDataReadState = STATE_READING_SUBSAMPLE_ENCRYPTION_DATA;
                            break;
                        case STATE_READING_SUBSAMPLE_ENCRYPTION_DATA:
                            for (int i = 0; i < mCryptoInfo.numSubSamples; i++) {
                                mCryptoInfo.numBytesOfClearData[i] = data.readUnsignedShort();
                                mCryptoInfo.numBytesOfEncryptedData[i] = data.readInt();
                            for (int i = 0; i < mSubsampleEncryptionDataSize; i++) {
                                mScratchSubsampleClearBytesCount[i] = data.readUnsignedShort();
                                mScratchSubsampleEncryptedBytesCount[i] = data.readInt();
                            }
                            length -=
                                    mCryptoInfo.numSubSamples
                                    mSubsampleEncryptionDataSize
                                            * BYTES_PER_SUBSAMPLE_ENCRYPTION_ENTRY;
                            mEncryptionDataReadState = STATE_READING_SIGNAL_BYTE;
                            if (length != 0) {
@@ -1487,24 +1496,71 @@ public final class MediaParser {
            if (cryptoData == null) {
                // The sample is not encrypted.
                return null;
            }
            mCryptoInfo.key = cryptoData.encryptionKey;
            // ExoPlayer modes match MediaCodec modes.
            mCryptoInfo.mode = cryptoData.cryptoMode;
            if (cryptoData.clearBlocks != 0) {
                // Content is pattern-encrypted.
                mCryptoInfo.setPattern(mEncryptionPattern);
                mEncryptionPattern.set(cryptoData.encryptedBlocks, cryptoData.clearBlocks);
            } else if (mInBandCryptoInfo) {
                if (cryptoData != mLastReceivedCryptoData) {
                    mLastOutputCryptoInfo =
                            createNewCryptoInfoAndPopulateWithCryptoData(cryptoData);
                }
            } else /* We must populate the full CryptoInfo. */ {
                // CryptoInfo.pattern is not accessible to the user, so the user needs to feed
                // this CryptoInfo directly to MediaCodec. We need to create a new CryptoInfo per
                // sample because of per-sample initialization vector changes.
                CryptoInfo newCryptoInfo = createNewCryptoInfoAndPopulateWithCryptoData(cryptoData);
                newCryptoInfo.iv = Arrays.copyOf(mScratchIvSpace, mScratchIvSpace.length);
                boolean canReuseSubsampleInfo =
                        mLastOutputCryptoInfo != null
                                && mLastOutputCryptoInfo.numSubSamples
                                        == mSubsampleEncryptionDataSize;
                for (int i = 0; i < mSubsampleEncryptionDataSize && canReuseSubsampleInfo; i++) {
                    canReuseSubsampleInfo =
                            mLastOutputCryptoInfo.numBytesOfClearData[i]
                                            == mScratchSubsampleClearBytesCount[i]
                                    && mLastOutputCryptoInfo.numBytesOfEncryptedData[i]
                                            == mScratchSubsampleEncryptedBytesCount[i];
                }
                newCryptoInfo.numSubSamples = mSubsampleEncryptionDataSize;
                if (canReuseSubsampleInfo) {
                    newCryptoInfo.numBytesOfClearData = mLastOutputCryptoInfo.numBytesOfClearData;
                    newCryptoInfo.numBytesOfEncryptedData =
                            mLastOutputCryptoInfo.numBytesOfEncryptedData;
                } else {
                mCryptoInfo.setPattern(null);
                    newCryptoInfo.numBytesOfClearData =
                            Arrays.copyOf(
                                    mScratchSubsampleClearBytesCount, mSubsampleEncryptionDataSize);
                    newCryptoInfo.numBytesOfEncryptedData =
                            Arrays.copyOf(
                                    mScratchSubsampleEncryptedBytesCount,
                                    mSubsampleEncryptionDataSize);
                }
                mLastOutputCryptoInfo = newCryptoInfo;
            }
            return mCryptoInfo;
            mLastReceivedCryptoData = cryptoData;
            return mLastOutputCryptoInfo;
        }

        private CryptoInfo createNewCryptoInfoAndPopulateWithCryptoData(CryptoData cryptoData) {
            CryptoInfo cryptoInfo = new CryptoInfo();
            cryptoInfo.key = cryptoData.encryptionKey;
            cryptoInfo.mode = cryptoData.cryptoMode;
            if (cryptoData.clearBlocks != mLastOutputEncryptionPattern.getSkipBlocks()
                    || cryptoData.encryptedBlocks
                            != mLastOutputEncryptionPattern.getEncryptBlocks()) {
                mLastOutputEncryptionPattern =
                        new CryptoInfo.Pattern(cryptoData.encryptedBlocks, cryptoData.clearBlocks);
            }
            cryptoInfo.setPattern(mLastOutputEncryptionPattern);
            return cryptoInfo;
        }

        private void outputSampleData(ParsableByteArray data, int length) {
            mScratchParsableByteArrayAdapter.resetWithByteArray(data, length);
            try {
                mOutputConsumer.onSampleDataFound(mTrackIndex, mScratchParsableByteArrayAdapter);
                // Read all bytes from data. ExoPlayer extractors expect all sample data to be
                // consumed by TrackOutput implementations when passing a ParsableByteArray.
                while (mScratchParsableByteArrayAdapter.getLength() > 0) {
                    mOutputConsumer.onSampleDataFound(
                            mTrackIndex, mScratchParsableByteArrayAdapter);
                }
            } catch (IOException e) {
                // Unexpected.
                throw new RuntimeException(e);