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

Commit 3a37f3e9 authored by Hassan Shojania's avatar Hassan Shojania
Browse files

Modular DRM for MediaPlayer/SampleAES

Bug: 34559906
Test: CTS Tests + playback in Chrome

Change-Id: Iaa275c4cfe6d15f02774ff4cc8a2cb5e7d1012e1
parent 5551797e
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -78,6 +78,7 @@ LOCAL_SHARED_LIBRARIES := \
        libaudioutils \
        libbinder \
        libcamera_client \
        libcrypto \
        libcutils \
        libdl \
        libdrmframework \
+107 −26
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@
#include "include/avc_utils.h"
#include "include/ID3.h"
#include "mpeg2ts/AnotherPacketSource.h"
#include "mpeg2ts/HlsSampleDecryptor.h"

#include <media/stagefright/foundation/ABitReader.h>
#include <media/stagefright/foundation/ABuffer.h>
@@ -36,7 +37,6 @@

#include <ctype.h>
#include <inttypes.h>
#include <openssl/aes.h>

#define FLOGV(fmt, ...) ALOGV("[fetcher-%d] " fmt, mFetcherID, ##__VA_ARGS__)
#define FSLOGV(stream, fmt, ...) ALOGV("[fetcher-%d] [%s] " fmt, mFetcherID, \
@@ -167,11 +167,15 @@ PlaylistFetcher::PlaylistFetcher(
      mFirstPTSValid(false),
      mFirstTimeUs(-1ll),
      mVideoBuffer(new AnotherPacketSource(NULL)),
      mSampleAesKeyItemChanged(false),
      mThresholdRatio(-1.0f),
      mDownloadState(new DownloadState()),
      mHasMetadata(false) {
    memset(mPlaylistHash, 0, sizeof(mPlaylistHash));
    mHTTPDownloader = mSession->getHTTPDownloader();

    memset(mKeyData, 0, sizeof(mKeyData));
    memset(mAESInitVec, 0, sizeof(mAESInitVec));
}

PlaylistFetcher::~PlaylistFetcher() {
@@ -306,6 +310,15 @@ status_t PlaylistFetcher::decryptBuffer(
        }
    }

    // TODO: Revise this when we add support for KEYFORMAT
    // If method has changed (e.g., -> NONE); sufficient to check at the segment boundary
    if (mSampleAesKeyItem != NULL && first && found && method != "SAMPLE-AES") {
        ALOGI("decryptBuffer: resetting mSampleAesKeyItem(%p) with method %s",
                mSampleAesKeyItem.get(), method.c_str());
        mSampleAesKeyItem = NULL;
        mSampleAesKeyItemChanged = true;
    }

    if (!found) {
        method = "NONE";
    }
@@ -313,6 +326,8 @@ status_t PlaylistFetcher::decryptBuffer(

    if (method == "NONE") {
        return OK;
    } else if (method == "SAMPLE-AES") {
        ALOGV("decryptBuffer: Non-Widevine SAMPLE-AES is supported now.");
    } else if (!(method == "AES-128")) {
        ALOGE("Unsupported cipher method '%s'", method.c_str());
        return ERROR_UNSUPPORTED;
@@ -345,26 +360,11 @@ status_t PlaylistFetcher::decryptBuffer(
        mAESKeyForURI.add(keyURI, key);
    }

    AES_KEY aes_key;
    if (AES_set_decrypt_key(key->data(), 128, &aes_key) != 0) {
        ALOGE("failed to set AES decryption key.");
        return UNKNOWN_ERROR;
    }

    size_t n = buffer->size();
    if (!n) {
        return OK;
    }

    if (n < 16 || n % 16) {
        ALOGE("not enough or trailing bytes (%zu) in encrypted buffer", n);
        return ERROR_MALFORMED;
    }

    if (first) {
        // If decrypting the first block in a file, read the iv from the manifest
        // or derive the iv from the file's sequence number.

        unsigned char AESInitVec[AES_BLOCK_SIZE];
        AString iv;
        if (itemMeta->findString("cipher-iv", &iv)) {
            if ((!iv.startsWith("0x") && !iv.startsWith("0X"))
@@ -377,7 +377,7 @@ status_t PlaylistFetcher::decryptBuffer(
                iv.insert("0", 1, 2);
            }

            memset(mAESInitVec, 0, sizeof(mAESInitVec));
            memset(AESInitVec, 0, sizeof(AESInitVec));
            for (size_t i = 0; i < 16; ++i) {
                char c1 = tolower(iv.c_str()[2 + 2 * i]);
                char c2 = tolower(iv.c_str()[3 + 2 * i]);
@@ -388,15 +388,65 @@ status_t PlaylistFetcher::decryptBuffer(
                uint8_t nibble1 = isdigit(c1) ? c1 - '0' : c1 - 'a' + 10;
                uint8_t nibble2 = isdigit(c2) ? c2 - '0' : c2 - 'a' + 10;

                mAESInitVec[i] = nibble1 << 4 | nibble2;
                AESInitVec[i] = nibble1 << 4 | nibble2;
            }
        } else {
            memset(mAESInitVec, 0, sizeof(mAESInitVec));
            mAESInitVec[15] = mSeqNumber & 0xff;
            mAESInitVec[14] = (mSeqNumber >> 8) & 0xff;
            mAESInitVec[13] = (mSeqNumber >> 16) & 0xff;
            mAESInitVec[12] = (mSeqNumber >> 24) & 0xff;
            memset(AESInitVec, 0, sizeof(AESInitVec));
            AESInitVec[15] = mSeqNumber & 0xff;
            AESInitVec[14] = (mSeqNumber >> 8) & 0xff;
            AESInitVec[13] = (mSeqNumber >> 16) & 0xff;
            AESInitVec[12] = (mSeqNumber >> 24) & 0xff;
        }

        bool newKey = memcmp(mKeyData, key->data(), AES_BLOCK_SIZE) != 0;
        bool newInitVec = memcmp(mAESInitVec, AESInitVec, AES_BLOCK_SIZE) != 0;
        bool newSampleAesKeyItem = newKey || newInitVec;
        ALOGV("decryptBuffer: SAMPLE-AES newKeyItem %d/%d (Key %d initVec %d)",
                mSampleAesKeyItemChanged, newSampleAesKeyItem, newKey, newInitVec);

        if (newSampleAesKeyItem) {
            memcpy(mKeyData, key->data(), AES_BLOCK_SIZE);
            memcpy(mAESInitVec, AESInitVec, AES_BLOCK_SIZE);

            if (method == "SAMPLE-AES") {
                mSampleAesKeyItemChanged = true;

                sp<ABuffer> keyDataBuffer = ABuffer::CreateAsCopy(mKeyData, sizeof(mKeyData));
                sp<ABuffer> initVecBuffer = ABuffer::CreateAsCopy(mAESInitVec, sizeof(mAESInitVec));

                // always allocating a new one rather than updating the old message
                // lower layer might still have a reference to the old message
                mSampleAesKeyItem = new AMessage();
                mSampleAesKeyItem->setBuffer("keyData", keyDataBuffer);
                mSampleAesKeyItem->setBuffer("initVec", initVecBuffer);

                ALOGV("decryptBuffer: New SampleAesKeyItem: Key: %s  IV: %s",
                        HlsSampleDecryptor::aesBlockToStr(mKeyData).c_str(),
                        HlsSampleDecryptor::aesBlockToStr(mAESInitVec).c_str());
            } // SAMPLE-AES
        } // newSampleAesKeyItem
    } // first

    if (method == "SAMPLE-AES") {
        ALOGV("decryptBuffer: skipping full-seg decrypt for SAMPLE-AES");
        return OK;
    }


    AES_KEY aes_key;
    if (AES_set_decrypt_key(key->data(), 128, &aes_key) != 0) {
        ALOGE("failed to set AES decryption key.");
        return UNKNOWN_ERROR;
    }

    size_t n = buffer->size();
    if (!n) {
        return OK;
    }

    if (n < 16 || n % 16) {
        ALOGE("not enough or trailing bytes (%zu) in encrypted buffer", n);
        return ERROR_MALFORMED;
    }

    AES_cbc_encrypt(
@@ -409,7 +459,7 @@ status_t PlaylistFetcher::decryptBuffer(
status_t PlaylistFetcher::checkDecryptPadding(const sp<ABuffer> &buffer) {
    AString method;
    CHECK(buffer->meta()->findString("cipher-method", &method));
    if (method == "NONE") {
    if (method == "NONE" || method == "SAMPLE-AES") {
        return OK;
    }

@@ -1656,6 +1706,11 @@ status_t PlaylistFetcher::extractAndQueueAccessUnitsFromTs(const sp<ABuffer> &bu
        mNextPTSTimeUs = -1ll;
    }

    if (mSampleAesKeyItemChanged) {
        mTSParser->signalNewSampleAesKey(mSampleAesKeyItem);
        mSampleAesKeyItemChanged = false;
    }

    size_t offset = 0;
    while (offset + 188 <= buffer->size()) {
        status_t err = mTSParser->feedTSPacket(buffer->data() + offset, 188);
@@ -2038,10 +2093,24 @@ status_t PlaylistFetcher::extractAndQueueAccessUnits(
        }
    }

    sp<HlsSampleDecryptor> sampleDecryptor = NULL;
    if (mSampleAesKeyItem != NULL) {
        ALOGV("extractAndQueueAccessUnits[%d] SampleAesKeyItem: Key: %s  IV: %s",
                mSeqNumber,
                HlsSampleDecryptor::aesBlockToStr(mKeyData).c_str(),
                HlsSampleDecryptor::aesBlockToStr(mAESInitVec).c_str());

        sampleDecryptor = new HlsSampleDecryptor(mSampleAesKeyItem);
    }

    int frameId = 0;

    size_t offset = 0;
    while (offset < buffer->size()) {
        const uint8_t *adtsHeader = buffer->data() + offset;
        CHECK_LT(offset + 5, buffer->size());
        // non-const pointer for decryption if needed
        uint8_t *adtsFrame = buffer->data() + offset;

        unsigned aac_frame_length =
            ((adtsHeader[3] & 3) << 11)
@@ -2099,6 +2168,18 @@ status_t PlaylistFetcher::extractAndQueueAccessUnits(
            }
        }

        if (sampleDecryptor != NULL) {
            bool protection_absent = (adtsHeader[1] & 0x1);
            size_t headerSize = protection_absent ? 7 : 9;
            if (frameId == 0) {
                ALOGV("extractAndQueueAAC[%d] protection_absent %d (%02x) headerSize %zu",
                        mSeqNumber, protection_absent, adtsHeader[1], headerSize);
            }

            sampleDecryptor->processAAC(headerSize, adtsFrame, aac_frame_length);
        }
        frameId++;

        sp<ABuffer> unit = new ABuffer(aac_frame_length);
        memcpy(unit->data(), adtsHeader, aac_frame_length);

+5 −1
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@
#define PLAYLIST_FETCHER_H_

#include <media/stagefright/foundation/AHandler.h>
#include <openssl/aes.h>

#include "mpeg2ts/ATSParser.h"
#include "LiveSession.h"
@@ -175,7 +176,10 @@ private:
    // Stores the initialization vector to decrypt the next block of cipher text, which can
    // either be derived from the sequence number, read from the manifest, or copied from
    // the last block of cipher text (cipher-block chaining).
    unsigned char mAESInitVec[16];
    unsigned char mAESInitVec[AES_BLOCK_SIZE];
    unsigned char mKeyData[AES_BLOCK_SIZE];
    bool mSampleAesKeyItemChanged;
    sp<AMessage> mSampleAesKeyItem;

    Mutex mThresholdLock;
    float mThresholdRatio;
+75 −5
Original line number Diff line number Diff line
@@ -105,6 +105,8 @@ struct ATSParser::Program : public RefBase {

    void updateCasSessions();

    void signalNewSampleAesKey(const sp<AMessage> &keyItem);

private:
    struct StreamInfo {
        unsigned mType;
@@ -119,6 +121,7 @@ private:
    bool mFirstPTSValid;
    uint64_t mFirstPTS;
    int64_t mLastRecoveredPTS;
    sp<AMessage> mSampleAesKeyItem;

    status_t parseProgramMap(ABitReader *br);
    int64_t recoverPTS(uint64_t PTS_33bit);
@@ -168,6 +171,8 @@ struct ATSParser::Stream : public RefBase {
    bool isVideo() const;
    bool isMeta() const;

    void signalNewSampleAesKey(const sp<AMessage> &keyItem);

protected:
    virtual ~Stream();

@@ -194,6 +199,8 @@ private:
    ElementaryStreamQueue *mQueue;

    bool mScrambled;
    bool mSampleEncrypted;
    sp<AMessage> mSampleAesKeyItem;
    sp<IMemory> mMem;
    sp<MemoryDealer> mDealer;
    sp<ABuffer> mDescrambledBuffer;
@@ -586,6 +593,10 @@ status_t ATSParser::Program::parseProgramMap(ABitReader *br) {
            sp<Stream> stream = new Stream(
                    this, info.mPID, info.mType, PCR_PID, info.mCASystemId);

            if (mSampleAesKeyItem != NULL) {
                stream->signalNewSampleAesKey(mSampleAesKeyItem);
            }

            isAddingScrambledStream |= info.mCASystemId >= 0;
            mStreams.add(info.mPID, stream);
        }
@@ -710,22 +721,32 @@ ATSParser::Stream::Stream(
      mPrevPTS(0),
      mQueue(NULL),
      mScrambled(CA_system_ID >= 0) {
    ALOGV("new stream PID 0x%02x, type 0x%02x, scrambled %d",
            elementaryPID, streamType, mScrambled);

    uint32_t flags = (isVideo() && mScrambled) ?
            ElementaryStreamQueue::kFlag_ScrambledData : 0;
    mSampleEncrypted =
            mStreamType == STREAMTYPE_H264_ENCRYPTED ||
            mStreamType == STREAMTYPE_AAC_ENCRYPTED  ||
            mStreamType == STREAMTYPE_AC3_ENCRYPTED;

    ALOGV("new stream PID 0x%02x, type 0x%02x, scrambled %d, SampleEncrypted: %d",
            elementaryPID, streamType, mScrambled, mSampleEncrypted);

    uint32_t flags =
            (isVideo() && mScrambled) ? ElementaryStreamQueue::kFlag_ScrambledData :
            (mSampleEncrypted) ? ElementaryStreamQueue::kFlag_SampleEncryptedData :
            0;

    ElementaryStreamQueue::Mode mode = ElementaryStreamQueue::INVALID;

    switch (mStreamType) {
        case STREAMTYPE_H264:
        case STREAMTYPE_H264_ENCRYPTED:
            mode = ElementaryStreamQueue::H264;
            flags |= (mProgram->parserFlags() & ALIGNED_VIDEO_DATA) ?
                    ElementaryStreamQueue::kFlag_AlignedData : 0;
            break;

        case STREAMTYPE_MPEG2_AUDIO_ADTS:
        case STREAMTYPE_AAC_ENCRYPTED:
            mode = ElementaryStreamQueue::AAC;
            break;

@@ -745,6 +766,7 @@ ATSParser::Stream::Stream(

        case STREAMTYPE_LPCM_AC3:
        case STREAMTYPE_AC3:
        case STREAMTYPE_AC3_ENCRYPTED:
            mode = ElementaryStreamQueue::AC3;
            break;

@@ -761,6 +783,10 @@ ATSParser::Stream::Stream(
    mQueue = new ElementaryStreamQueue(mode, flags);

    if (mQueue != NULL) {
        if (mSampleAesKeyItem != NULL) {
            mQueue->signalNewSampleAesKey(mSampleAesKeyItem);
        }

        ensureBufferCapacity(kInitialStreamBufferSize);

        if (mScrambled && (isAudio() || isVideo())) {
@@ -913,6 +939,7 @@ status_t ATSParser::Stream::parse(
bool ATSParser::Stream::isVideo() const {
    switch (mStreamType) {
        case STREAMTYPE_H264:
        case STREAMTYPE_H264_ENCRYPTED:
        case STREAMTYPE_MPEG1_VIDEO:
        case STREAMTYPE_MPEG2_VIDEO:
        case STREAMTYPE_MPEG4_VIDEO:
@@ -930,6 +957,8 @@ bool ATSParser::Stream::isAudio() const {
        case STREAMTYPE_MPEG2_AUDIO_ADTS:
        case STREAMTYPE_LPCM_AC3:
        case STREAMTYPE_AC3:
        case STREAMTYPE_AAC_ENCRYPTED:
        case STREAMTYPE_AC3_ENCRYPTED:
            return true;

        default:
@@ -1454,7 +1483,7 @@ void ATSParser::Stream::onPayloadData(
    mPrevPTS = PTS;
#endif

    ALOGV("onPayloadData mStreamType=0x%02x", mStreamType);
    ALOGV("onPayloadData mStreamType=0x%02x size: %zu", mStreamType, size);

    int64_t timeUs = 0ll;  // no presentation timestamp available.
    if (PTS_DTS_flags == 2 || PTS_DTS_flags == 3) {
@@ -1492,6 +1521,8 @@ void ATSParser::Stream::onPayloadData(
                }
                mSource = new AnotherPacketSource(meta);
                mSource->queueAccessUnit(accessUnit);
                ALOGV("onPayloadData: created AnotherPacketSource PID 0x%08x of type 0x%02x",
                        mElementaryPID, mStreamType);
            }
        } else if (mQueue->getFormat() != NULL) {
            // After a discontinuity we invalidate the queue's format
@@ -1730,6 +1761,9 @@ void ATSParser::parseProgramAssociationTable(ABitReader *br) {
            if (!found) {
                mPrograms.push(
                        new Program(this, program_number, programMapPID, mLastRecoveredPTS));
                if (mSampleAesKeyItem != NULL) {
                    mPrograms.top()->signalNewSampleAesKey(mSampleAesKeyItem);
                }
            }

            if (mPSISections.indexOfKey(programMapPID) < 0) {
@@ -2228,4 +2262,40 @@ bool ATSParser::PSISection::isCRCOkay() const {
    ALOGV("crc: %08x\n", crc);
    return (crc == 0);
}

// SAMPLE_AES key handling
// TODO: Merge these to their respective class after Widevine-HLS
void ATSParser::signalNewSampleAesKey(const sp<AMessage> &keyItem) {
    ALOGD("signalNewSampleAesKey: %p", keyItem.get());

    mSampleAesKeyItem = keyItem;

    // a NULL key item will propagate to existing ElementaryStreamQueues
    for (size_t i = 0; i < mPrograms.size(); ++i) {
        mPrograms[i]->signalNewSampleAesKey(keyItem);
    }
}

void ATSParser::Program::signalNewSampleAesKey(const sp<AMessage> &keyItem) {
    ALOGD("Program::signalNewSampleAesKey: %p", keyItem.get());

    mSampleAesKeyItem = keyItem;

    // a NULL key item will propagate to existing ElementaryStreamQueues
    for (size_t i = 0; i < mStreams.size(); ++i) {
        mStreams[i]->signalNewSampleAesKey(keyItem);
    }
}

void ATSParser::Stream::signalNewSampleAesKey(const sp<AMessage> &keyItem) {
    ALOGD("Stream::signalNewSampleAesKey: 0x%04x size = %zu keyItem: %p",
          mElementaryPID, mBuffer->size(), keyItem.get());

    // a NULL key item will propagate to existing ElementaryStreamQueues
    mSampleAesKeyItem = keyItem;

    flush(NULL);
    mQueue->signalNewSampleAesKey(keyItem);
}

}  // namespace android
+9 −0
Original line number Diff line number Diff line
@@ -131,6 +131,8 @@ struct ATSParser : public RefBase {

    int64_t getFirstPTSTimeUs();

    void signalNewSampleAesKey(const sp<AMessage> &keyItem);

    enum {
        // From ISO/IEC 13818-1: 2000 (E), Table 2-29
        STREAMTYPE_RESERVED             = 0x00,
@@ -149,6 +151,11 @@ struct ATSParser : public RefBase {
        // Stream type 0x83 is non-standard,
        // it could be LPCM or TrueHD AC3
        STREAMTYPE_LPCM_AC3             = 0x83,

        //Sample Encrypted types
        STREAMTYPE_H264_ENCRYPTED       = 0xDB,
        STREAMTYPE_AAC_ENCRYPTED        = 0xCF,
        STREAMTYPE_AC3_ENCRYPTED        = 0xC1,
    };

protected:
@@ -181,6 +188,8 @@ private:

    size_t mNumTSPacketsParsed;

    sp<AMessage> mSampleAesKeyItem;

    void parseProgramAssociationTable(ABitReader *br);
    void parseProgramMap(ABitReader *br);
    // Parse PES packet where br is pointing to. If the PES contains a sync
Loading