前言:
前面我们讲了在Mediacodec中如何实现DRM的(详见: https://blog.csdn.net/cheriyou_/article/details/101702704),本文我们将讲述如何在MediaPlayer中播放加密的视频文件。
APP中的调用过程:
首先我们看下,APP是怎么调用MediaPlayer java类的。
void playvideo{
MediaPlayer mplayer;
mplayer->setDataSource(.)
mplayer->setOnDrmConfigHelper(.) // setOnDrmConfigHelper
mplayer->prepare(.)
if(drmvideo){
mplayer->prepareDrm(.) // prepareDrm
mplayer->getKeyRequest(.)
mplayer->provideKeyResponse(.)
}
else{
mplayer->prepare(.)
}
// MediaPlayer is now ready to use
start(.)
// ...play/pause/resume...
stop()
releaseDrm(.)
}
和播放普通视频相比,播放加密视频需要调用: setOnDrmConfigHelper、prepareDrm、getKeyRequest、provideKeyResponse 等函数。
具体这些函数怎么实现的呢?我们继续往下看:
// frameworks/base/media/java/android/media/MediaPlayer.java
public void setOnDrmConfigHelper(OnDrmConfigHelper listener)
{
synchronized (mDrmLock) {
mOnDrmConfigHelper = listener;
// 这个函数很简单,就是把输入listener赋值非mOnDrmConfigHelper
}
}
public void prepareDrm(@NonNull UUID uuid) // 输入为uuid
throws UnsupportedSchemeException, ResourceBusyException,
ProvisioningNetworkErrorException, ProvisioningServerErrorException
{
Log.v(TAG, "prepareDrm: uuid: " + uuid + " mOnDrmConfigHelper: " + mOnDrmConfigHelper);
boolean allDoneWithoutProvisioning = false;
// get a snapshot as we'll use them outside the lock
OnDrmPreparedHandlerDelegate onDrmPreparedHandlerDelegate = null;
synchronized (mDrmLock) {
// 很多异常处理,忽略
cleanDrmObj(); // 清理此前的对象
mPrepareDrmInProgress = true;
// local copy while the lock is held
onDrmPreparedHandlerDelegate = mOnDrmPreparedHandlerDelegate;
try {
// only creating the DRM object to allow pre-openSession configuration
prepareDrm_createDrmStep(uuid); // 此处会创建MediaDrm
} catch (Exception e) {
Log.w(TAG, "prepareDrm(): Exception ", e);
mPrepareDrmInProgress = false;
throw e;
}
mDrmConfigAllowed = true;
} // synchronized
// call the callback outside the lock
if (mOnDrmConfigHelper != null) {
mOnDrmConfigHelper.onDrmConfig(this);
}
synchronized (mDrmLock) {
mDrmConfigAllowed = false;
boolean earlyExit = false;
try {
prepareDrm_openSessionStep(uuid);
mDrmUUID = uuid;
mActiveDrmScheme = true;
allDoneWithoutProvisioning = true;
} catch (IllegalStateException e) {
final String msg = "prepareDrm(): Wrong usage: The player must be " +
"in the prepared state to call prepareDrm().";
Log.e(TAG, msg);
earlyExit = true;
throw new IllegalStateException(msg);
} catch (NotProvisionedException e) {
Log.w(TAG, "prepareDrm: NotProvisionedException");
// handle provisioning internally; it'll reset mPrepareDrmInProgress
int result = HandleProvisioninig(uuid);
// if blocking mode, we're already done;
// if non-blocking mode, we attempted to launch background provisioning
if (result != PREPARE_DRM_STATUS_SUCCESS) {
earlyExit = true;
String msg;
switch (result) {
case PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR:
msg = "prepareDrm: Provisioning was required but failed " +
"due to a network error.";
Log.e(TAG, msg);
throw new ProvisioningNetworkErrorException(msg);
case PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR:
msg = "prepareDrm: Provisioning was required but the request " +
"was denied by the server.";
Log.e(TAG, msg);
throw new ProvisioningServerErrorException(msg);
case PREPARE_DRM_STATUS_PREPARATION_ERROR:
default: // default for safeguard
msg = "prepareDrm: Post-provisioning preparation failed.";
Log.e(TAG, msg);
throw new IllegalStateException(msg);
}
}
// nothing else to do;
// if blocking or non-blocking, HandleProvisioninig does the re-attempt & cleanup
} catch (Exception e) {
Log.e(TAG, "prepareDrm: Exception " + e);
earlyExit = true;
throw e;
} finally {
if (!mDrmProvisioningInProgress) {// if early exit other than provisioning exception
mPrepareDrmInProgress = false;
}
if (earlyExit) { // cleaning up object if didn't succeed
cleanDrmObj();
}
} // finally
} // synchronized
// if finished successfully without provisioning, call the callback outside the lock
if (allDoneWithoutProvisioning) {
if (onDrmPreparedHandlerDelegate != null)
onDrmPreparedHandlerDelegate.notifyClient(PREPARE_DRM_STATUS_SUCCESS);
}
}
5.2 MediaPlayer API组的DRM处理
APP调用MediaPlayer的prepareDrm,传入了参数uuid
MediaPlayer.java 的prepareDrm 中首先调用prepareDrm_createDrmStep新建MediaDrm,然后调用prepareDrm_openSessionStep最终调用native的prepareDrm。
MediaPlayer.java 的 prepareDrm_createDrmStep 中新建MediaDrm: mDrmObj = new MediaDrm(uuid);
MediaPlayer.java 的prepareDrm_openSessionStep首先获取mDrmSessionId = mDrmObj.openSession(); 然后调用 _prepareDrm(getByteArrayFromUUID(uuid), mDrmSessionId);
JNI层: android_media_MediaPlayer_prepareDrm 中获取 uuid = JByteArrayToVector(env, uuidObj); 和 drmSessionId = JByteArrayToVector(env, drmSessionIdObj);
然后调用native层 mp->prepareDrm(uuid.array(), drmSessionId);
MediaPlayer::prepareDrm
NuPlayer::prepareDrm 发送消息 kWhatPrepareDrm,
NuPlayer::onPrepareDrm 在此函数中调用NuPlayer::GenericSource::prepareDrm获取Crypto,并让mCrypto = crypto;此处的mCrypto就用于MediaCodec::configure。
NuPlayer::GenericSource::prepareDrm 调用 crypto = NuPlayerDrm::createCryptoAndPlugin(uuid, drmSessionId, status) 获取 crypto
NuPlayerDrm::createCryptoAndPlugin crypto = createCrypto(&status)
NuPlayerDrm::createCrypto 首先通过binder获取 "media.drm" service,然后调用service的makeCrypto获取crypto。
上面详细介绍了mCrypto由来,但是mCrypto有什么具体作用呢?
用于MediaCodec::configure。把mCrypto传入mediacodec。接下来就和5.1的处理类似。
NuPlayer::Decoder::onInputBufferFetched 首先通过NuPlayerDrm::getSampleCryptoInfo(meta_data)获取cryptInfo,然后调用MediaCodec的queueSecureInputBuffer,并把cryptInfo的信息传入。
在NuPlayer2Decoder.cpp中,当有input数据传来时,会判断cryptInfo是否为NULL,若为NULL表示当前数据不是加密的,直接调用MediaCodec的queueInputBuffer即可,否则调用queueSecureInputBuffer。在queueSecureInputBuffer中会设置解密秘钥给底层。后面的操作如下:
queueSecureInputBuffer发送消息kWhatQueueInputBuffer
MediaCodec::onQueueInputBuffer 响应消息kWhatQueueInputBuffer
ACodecBufferChannel::queueSecureInputBuffer 在此函数中会调用decrypt进行解密处理。然后发送ebd消息即可。
如果codec的componentname是以".secure"结束的,就会标记mFlags包含kFlagIsSecure。在后面的allocateBuffersOnPort中就会调用OMX的allocateSecureBuffer。