前言:
对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