Android-MediaPlayer中DRM的实现

前言:

  前面我们讲了在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。

你可能感兴趣的:(安卓基础)