Android-MediaCodec中DRM的实现

前言:

    对DRM的介绍、安卓中对DRM的集成、安卓中DRM文件播放框架等已经在这篇文章中做了介绍: https://blog.csdn.net/cheriyou_/article/details/101681367

    安卓系统中播放一个视频文件一般有两个接口,一个是MediaPlayer,一个是MediaCodec。那么在播放加密视频时这两个接口分别是怎么处理的呢?本文将一一揭晓。

MediaCodec API组的DRM处理:

如果使用MediaCodec进行decode的时候,configure()方法需要传进一个MediaCrypto。

// APP通过JNI调用MediaCodec::configure,并把crypto传入。
status_t MediaCodec::configure(
        const sp<AMessage> &format,
        const sp<Surface> &nativeWindow,
        const sp<ICrypto> &crypto, // 传入crypto
        uint32_t flags) {
    return configure(format, nativeWindow, crypto, NULL, flags);
}

status_t MediaCodec::configure(
        const sp<AMessage> &format,
        const sp<Surface> &surface,
        const sp<ICrypto> &crypto,
        const sp<IDescrambler> &descrambler,
        uint32_t flags) {
    sp<AMessage> msg = new AMessage(kWhatConfigure, this); // kWhatConfigure
    ......
    if (crypto != NULL || descrambler != NULL) {
        if (crypto != NULL) {
            msg->setPointer("crypto", crypto.get()); // 把crypto设给msg
        } else {
            msg->setPointer("descrambler", descrambler.get());
        }
        if (mAnalyticsItem != NULL) {
            mAnalyticsItem->setInt32(kCodecCrypto, 1);
        }
    } else if (mFlags & kFlagIsSecure) {
        ALOGW("Crypto or descrambler should be given for secure codec");
    }
    ......
    PostAndAwaitResponse(msg, &response); // 发送kWhatConfigure消息
    ......
}

void MediaCodec::onMessageReceived(const sp<AMessage> &msg) {
    switch (msg->what()) {
    {
        ......
        case kWhatConfigure: // MediaCodec收到kWhatConfigure消息
            if (!msg->findPointer("crypto", &crypto)) {
                crypto = NULL;
            }

            ALOGV("kWhatConfigure: Old mCrypto: %p (%d)",
                    mCrypto.get(), (mCrypto != NULL ? mCrypto->getStrongCount() : 0));

            mCrypto = static_cast<ICrypto *>(crypto); // 更新mCrypto
            mBufferChannel->setCrypto(mCrypto); // 把mCrypto给mBufferChannel

            ALOGV("kWhatConfigure: New mCrypto: %p (%d)",
                    mCrypto.get(), (mCrypto != NULL ? mCrypto->getStrongCount() : 0));

            void *descrambler;
            if (!msg->findPointer("descrambler", &descrambler)) {
                descrambler = NULL;
            }

            mDescrambler = static_cast<IDescrambler *>(descrambler);
            mBufferChannel->setDescrambler(mDescrambler);

            uint32_t flags;
            CHECK(msg->findInt32("flags", (int32_t *)&flags));

            if (flags & CONFIGURE_FLAG_ENCODE) {
                format->setInt32("encoder", true);
                mFlags |= kFlagIsEncoder;
            }

            extractCSD(format);

            mCodec->initiateConfigureComponent(format);
            break;
           ......
     }
}

MediaCodec::configure 在此函数中设置 msg->setPointer("crypto", crypto.get());发送msg。

MediaCodec::onMessageReceived 收到 configure 发送的消息,取出crypto,并调用mBufferChannel->setCrypto(mCrypto);

下面看看mBufferChannel中对crypto的应用:

void BufferChannelBase::setCrypto(const sp<ICrypto> &crypto) {
    mCrypto = crypto; // 先把crypto赋值给mCrypto
}

status_t ACodecBufferChannel::queueSecureInputBuffer( // 然后在此函数中应用mCrypto
        const sp<MediaCodecBuffer> &buffer, bool secure, const uint8_t *key,
        const uint8_t *iv, CryptoPlugin::Mode mode, CryptoPlugin::Pattern pattern,
        const CryptoPlugin::SubSample *subSamples, size_t numSubSamples,
        AString *errorDetailMsg) {
    ......
    ssize_t result = -1;
    if (mCrypto != NULL) {
        ICrypto::DestinationBuffer destination;
        if (secure) {
            destination.mType = ICrypto::kDestinationTypeNativeHandle;
            destination.mHandle = secureHandle;
        } else {
            destination.mType = ICrypto::kDestinationTypeSharedMemory;
            destination.mSharedMemory = mDecryptDestination;
        }

        ICrypto::SourceBuffer source;
        source.mSharedMemory = it->mSharedEncryptedBuffer;
        source.mHeapSeqNum = mHeapSeqNum;

        result = mCrypto->decrypt(key, iv, mode, pattern, // 解密过程
                source, it->mClientBuffer->offset(),
                subSamples, numSubSamples, destination, errorDetailMsg);

        if (result < 0) {
            return result;
        }

        if (destination.mType == ICrypto::kDestinationTypeSharedMemory) {
            memcpy(it->mCodecBuffer->base(), destination.mSharedMemory->pointer(), result);
        }
    } else {
        ......
    }

    ......
}

 ACodecBufferChannel.cpp中对mCrypto的应用就是在调用queueSecureInputBuffer时对input buffer进行解密。

接下来看看,Mediacodec中对mCrypto的应用:

// 当APP给input buffer写入数据之后就会调用queueSecureInputBuffer把input buffer给mediacodec
status_t MediaCodec::queueSecureInputBuffer(
        size_t index,
        size_t offset,
        const CryptoPlugin::SubSample *subSamples,
        size_t numSubSamples,
        const uint8_t key[16],
        const uint8_t iv[16],
        CryptoPlugin::Mode mode,
        const CryptoPlugin::Pattern &pattern,
        int64_t presentationTimeUs,
        uint32_t flags,
        AString *errorDetailMsg) {
    if (errorDetailMsg != NULL) {
        errorDetailMsg->clear();
    }

    sp<AMessage> msg = new AMessage(kWhatQueueInputBuffer, this);
    msg->setSize("index", index);
    msg->setSize("offset", offset);
    msg->setPointer("subSamples", (void *)subSamples);
    msg->setSize("numSubSamples", numSubSamples);
    msg->setPointer("key", (void *)key); // key???
    msg->setPointer("iv", (void *)iv);
    msg->setInt32("mode", mode);
    msg->setInt32("encryptBlocks", pattern.mEncryptBlocks);
    msg->setInt32("skipBlocks", pattern.mSkipBlocks);
    msg->setInt64("timeUs", presentationTimeUs);
    msg->setInt32("flags", flags);
    msg->setPointer("errorDetailMsg", errorDetailMsg);

    sp<AMessage> response;
    status_t err = PostAndAwaitResponse(msg, &response); // 发送kWhatQueueInputBuffer

    return err;
}

status_t MediaCodec::onQueueInputBuffer(const sp<AMessage> &msg) {
    // 响应kWhatQueueInputBuffer
    size_t index;
    size_t offset;
    size_t size;
    int64_t timeUs;
    uint32_t flags;
    CHECK(msg->findSize("index", &index));
    CHECK(msg->findSize("offset", &offset));
    CHECK(msg->findInt64("timeUs", &timeUs));
    CHECK(msg->findInt32("flags", (int32_t *)&flags));

    const CryptoPlugin::SubSample *subSamples;
    size_t numSubSamples;
    const uint8_t *key;
    const uint8_t *iv;
    CryptoPlugin::Mode mode = CryptoPlugin::kMode_Unencrypted;

    // We allow the simpler queueInputBuffer API to be used even in
    // secure mode, by fabricating a single unencrypted subSample.
    CryptoPlugin::SubSample ss;
    CryptoPlugin::Pattern pattern;

    if (msg->findSize("size", &size)) {
        if (hasCryptoOrDescrambler()) {
            // 未加密,忽略
        }
    } else {
        if (!hasCryptoOrDescrambler()) {
            // 加密但无mCrypto or mDescrambler,报错,退出
        }

        CHECK(msg->findPointer("subSamples", (void **)&subSamples));
        CHECK(msg->findSize("numSubSamples", &numSubSamples));
        CHECK(msg->findPointer("key", (void **)&key)); // 把key取出来
        CHECK(msg->findPointer("iv", (void **)&iv));
        CHECK(msg->findInt32("encryptBlocks", (int32_t *)&pattern.mEncryptBlocks));
        CHECK(msg->findInt32("skipBlocks", (int32_t *)&pattern.mSkipBlocks));

        int32_t tmp;
        CHECK(msg->findInt32("mode", &tmp));

        mode = (CryptoPlugin::Mode)tmp;

        size = 0;
        for (size_t i = 0; i < numSubSamples; ++i) {
            size += subSamples[i].mNumBytesOfClearData;
            size += subSamples[i].mNumBytesOfEncryptedData;
        }
    }

    if (index >= mPortBuffers[kPortIndexInput].size()) {
        return -ERANGE;
    }

    BufferInfo *info = &mPortBuffers[kPortIndexInput][index];

    if (info->mData == nullptr || !info->mOwnedByClient) {
        return -EACCES;
    }

    if (offset + size > info->mData->capacity()) {
        if ( ((int)size < 0) && !(flags & BUFFER_FLAG_EOS)) {
            size = 0;
            ALOGD("EOS, reset size to zero");
        } else {
            return -EINVAL;
        }
    }

    info->mData->setRange(offset, size);
    info->mData->meta()->setInt64("timeUs", timeUs);
    if (flags & BUFFER_FLAG_EOS) {
        info->mData->meta()->setInt32("eos", true);
    }

    if (flags & BUFFER_FLAG_CODECCONFIG) {
        info->mData->meta()->setInt32("csd", true);
    }

    sp<MediaCodecBuffer> buffer = info->mData;
    status_t err = OK;
    if (hasCryptoOrDescrambler()) {
        AString *errorDetailMsg;
        CHECK(msg->findPointer("errorDetailMsg", (void **)&errorDetailMsg));

        err = mBufferChannel->queueSecureInputBuffer( 
        // 调用ACodecBufferChannel的queueSecureInputBuffer函数
                buffer,
                (mFlags & kFlagIsSecure),
                key, // 把key传入
                iv,
                mode,
                pattern,
                subSamples,
                numSubSamples,
                errorDetailMsg);
    } else {
        err = mBufferChannel->queueInputBuffer(buffer);
    }

    if (err == OK) {
        // synchronization boundary for getBufferAndFormat
        Mutex::Autolock al(mBufferLock);
        info->mOwnedByClient = false;
        info->mData.clear();

        statsBufferSent(timeUs);
    }

    return err;
}

从上面可以知道APP调用了MediaCodec的queueSecureInputBuffer并把key传入,此函数会发送kWhatQueueInputBuffer消息,onQueueInputBuffer会响应这个消息并把key取出,然后再调用ACodecBufferChannel的queueSecureInputBuffer函数。ACodecBufferChannel中是怎么应用这个key的呢?

status_t ACodecBufferChannel::queueSecureInputBuffer(
        const sp<MediaCodecBuffer> &buffer, bool secure, const uint8_t *key,
        const uint8_t *iv, CryptoPlugin::Mode mode, CryptoPlugin::Pattern pattern,
        const CryptoPlugin::SubSample *subSamples, size_t numSubSamples,
        AString *errorDetailMsg) {
    ......
        result = mCrypto->decrypt(key, iv, mode, pattern, // 解密过程
                source, it->mClientBuffer->offset(),
                subSamples, numSubSamples, destination, errorDetailMsg);
    ......
    // input buffer解密完之后就会发送etb消息,让mediacodec去解码
    sp<AMessage> msg = mInputBufferFilled->dup();
    msg->setObject("buffer", it->mCodecBuffer);
    msg->setInt32("buffer-id", it->mBufferId);
    msg->post();
    return OK;
}

// frameworks/av/drm/libmediadrm/ICrypto.cpp
virtual ssize_t decrypt(const uint8_t key[16], const uint8_t iv[16],
        CryptoPlugin::Mode mode, const CryptoPlugin::Pattern &pattern,
        const SourceBuffer &source, size_t offset,
        const CryptoPlugin::SubSample *subSamples, size_t numSubSamples,
        const DestinationBuffer &destination, AString *errorDetailMsg) {
    Parcel data, reply;
    data.writeInterfaceToken(ICrypto::getInterfaceDescriptor());
    data.writeInt32(mode);
    data.writeInt32(pattern.mEncryptBlocks);
    data.writeInt32(pattern.mSkipBlocks);

    static const uint8_t kDummy[16] = { 0 };

    if (key == NULL) {
        key = kDummy;
    }

    if (iv == NULL) {
        iv = kDummy;
    }

    data.write(key, 16); // 把key写入data中
    data.write(iv, 16);

    size_t totalSize = 0;
    for (size_t i = 0; i < numSubSamples; ++i) {
        totalSize += subSamples[i].mNumBytesOfEncryptedData;
        totalSize += subSamples[i].mNumBytesOfClearData;
    }

    data.writeInt32(totalSize);
    data.writeStrongBinder(IInterface::asBinder(source.mSharedMemory));
    data.writeInt32(source.mHeapSeqNum);
    data.writeInt32(offset);

    data.writeInt32(numSubSamples);
    data.write(subSamples, sizeof(CryptoPlugin::SubSample) * numSubSamples);

    data.writeInt32((int32_t)destination.mType);
    if (destination.mType == kDestinationTypeNativeHandle) {
        if (destination.mHandle == NULL) {
        return BAD_VALUE;
        }
        data.writeNativeHandle(destination.mHandle);
    } else {
        if (destination.mSharedMemory == NULL) {
        return BAD_VALUE;
        }
        data.writeStrongBinder(IInterface::asBinder(destination.mSharedMemory));
    }

    remote()->transact(DECRYPT, data, &reply); // 调用服务,此处的实际实现在plugin的DRM方案中。

    ssize_t result = reply.readInt32();

    if (isCryptoError(result)) {
        AString msg = reply.readCString();
        if (errorDetailMsg) {
        *errorDetailMsg = msg;
        }
    }

    return result;
}

上面就是APP调用mediacodec解码加密视频的整个流程啦~

如果想要了解,mediaplayer怎么播放加密视频的,请戳: https://blog.csdn.net/cheriyou_/article/details/101715080

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值