天天看點

播放器支援商業DRM實戰(二)—— 支援WideVine

一. WideVine簡介

在收購Widevine之前,Android沒有系統的數字版權保護機制,2010年12月,Google不惜重金将視訊數字版權管理軟體公司Widevine招安,彌補了Android在這方面的短闆。Android從3.0開始就支援Widevine。現在Widevine已經成為GMS(Google Mobile Service)中必備的内容,所有想要得到GMS的手機廠商,都需要根據GMS的要求搭載Widevine。

WideVine的安全級别

WideVine提供了三種安全級别。

播放器支援商業DRM實戰(二)—— 支援WideVine

迪士尼等版權廠商對不同的視訊清晰度,有着不同的安全級别要求。使用者可以通過License中配置級别,進而滿足版權廠商的要求。

二. ExoPlayer實作 WideVine播放

ExoPlayer 是谷歌開發的在Android平台上使用的開源播放器。它通過MediaCodec + MediaDrm 實作了對WideVine播放的支援。封裝的接口也是比較容易使用,想播放WideVine視訊,隻需要實作接口

MediaDrmCallback

MediaDrmCallback drmCallback =
                new WideVineDrmCallback(ExternPlayerExo.this, mDataSourceFactory);
mediaDrm = FrameworkMediaDrm.newInstance(C.WIDEVINE_UUID);
DrmSessionManager<FrameworkMediaCrypto> drmSessionManager = new DefaultDrmSessionManager<>(C.WIDEVINE_UUID, mediaDrm, drmCallback, null, false);
....
mExoPlayer = ExoPlayerFactory.newSimpleInstance(
                mContext, new DefaultRenderersFactory(mContext), mTrackSelector, drmSessionManager);           

MediaDrmCallback 需要實作兩個接口:

public byte[] executeProvisionRequest(UUID uuid, ExoMediaDrm.ProvisionRequest request)

: 請求Provision

public byte[] executeKeyRequest(UUID uuid, ExoMediaDrm.KeyRequest request)

: 請求license。

整個過程比較簡單,如果需要快速開發出WideVine功能,可以使用ExoPlayer。但是會有幾個問題:

  1. 需要額外引入一個ExoPlayer 的庫。
  1. 出現問題無法修改。

是以最好還是自研WideVine的播放。

三. 自研WideVine播放

想要播放WideVine,會涉及到播放器的很多流程,大體涉及的流程如下:

播放器支援商業DRM實戰(二)—— 支援WideVine

1. 解析m3u8索引檔案。

解析出m3u8中KEY的資訊,包括:KEYFORMAT,URI等資訊。

#EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,AAAATHBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAACwSEOmcQnb1u7mkYV47U5xyFMYiEDI61IXNX0iWvdFsX6j2bNQ4AUjzxombBg==",KEYID=0xE99C4276F5BBB9A4615E3B539C7214C6,IV=0xBBA0A21E523D460BA3B0F154C507E7C7,KEYFORMATVERSIONS="1",KEYFORMAT="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"           

KEYFORMAT="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed" ,值是widevine的uuid, 這是一個WideVine加密的視訊。

URI 裡面是初始化CDM所需要的資訊。

2. 初始化CDM子產品

在Android中CDM是通過

MediaDrm

類實作的。

mediaDrm = new MediaDrm(WIDEVINE_UUID);
 mediaDrm.setOnEventListener(new MediaDrm.OnEventListener() {
                    @Override
                    public void onEvent(MediaDrm md, byte[] sessionId, int event, int extra, byte[] data) {
//監聽drm資訊,通過event,分别請求Provision或者請求license。
                    }
                });
sessionId = mediaDrm.openSession();           

請求license的initData,就是url中的值。

3. demux時,讀取packet中的解密資訊

播放器一般都是使用ffmpeg去做demux操作,出來一個packet。解密資訊就包含在packet的ENCRYPT_INFO字段中:

int encryption_info_size;
        const uint8_t *new_encryption_info = av_packet_get_side_data(mpkt, AV_PKT_DATA_ENCRYPTION_INFO, &encryption_info_size);

        if (encryption_info_size <= 0 || new_encryption_info == nullptr) {
            return false;
        }

        mAVEncryptionInfo = av_encryption_info_get_side_data(new_encryption_info, encryption_info_size);           

AVEcnrypttionInfo的結構如下:

/**
 * This describes encryption info for a packet.  This contains frame-specific
 * info for how to decrypt the packet before passing it to the decoder.
 *
 * The size of this struct is not part of the public ABI.
 */
typedef struct AVEncryptionInfo {
    /** The fourcc encryption scheme, in big-endian byte order. */
    uint32_t scheme;

    /**
     * Only used for pattern encryption.  This is the number of 16-byte blocks
     * that are encrypted.
     */
    uint32_t crypt_byte_block;

    /**
     * Only used for pattern encryption.  This is the number of 16-byte blocks
     * that are clear.
     */
    uint32_t skip_byte_block;

    /**
     * The ID of the key used to encrypt the packet.  This should always be
     * 16 bytes long, but may be changed in the future.
     */
    uint8_t *key_id;
    uint32_t key_id_size;

    /**
     * The initialization vector.  This may have been zero-filled to be the
     * correct block size.  This should always be 16 bytes long, but may be
     * changed in the future.
     */
    uint8_t *iv;
    uint32_t iv_size;

    /**
     * An array of subsample encryption info specifying how parts of the sample
     * are encrypted.  If there are no subsamples, then the whole sample is
     * encrypted.
     */
    AVSubsampleEncryptionInfo *subsamples;
    uint32_t subsample_count;
} AVEncryptionInfo;           

4. 建立硬解碼器MediaCodec

由于是需要解密的,是以建立MediaCodec的時候,需要MediaCrypto資訊去解密。

UUID drmUUID = UUID.fromString(uuid);
mediaCrypto = new MediaCrypto(drmUUID, sessionId);           

同時,MediaCodec對流的格式有不同的要求:

  • 對于video, 必須是用00000001/000001分隔的資料。(而對于用CENC加密的資料,則必須要是00000001分隔的,因為CENC就是用4個位元組表示大小的,MediaCodec内部會做轉換。)
  • 對于Audio aac, 必須要指明是不是ADTS格式。如果不對的話,會出現解碼錯誤。
boolean needSecureDecoder = false;
        if (mediaCrypto != null) {
            needSecureDecoder = !forceInsecureDecoder && mediaCrypto.requiresSecureDecoderComponent(mMime);
        }

String codecName = getDecoderName(videoFormat, needSecureDecoder);

mMediaCodec = MediaCodec.createByCodecName(codecName);
   if (surface instanceof Surface) {
                mMediaCodec.configure(videoFormat, (Surface) surface, mediaCrypto, 0);
            } else { //audio 沒有surface
                mMediaCodec.configure(videoFormat, null, mediaCrypto, 0);
            }           

5. 送入解碼器去解碼

解碼加密資料時,需要使用

queueSecureInputBuffer

, 而EOS的時候,則需要使用

queueInputBuffer

方法。

if (secure && buffer != null) {
                MediaCodec.CryptoInfo crypInfo = createCryptoInfo((EncryptionInfo) encryptionInfo); //建立解密資訊
                synchronized (queLock) {
                    mMediaCodec.queueSecureInputBuffer(index, 0, crypInfo, pts, flags);
                }
            } else {
                if ((flags & BUFFER_FLAG_END_OF_STREAM) == BUFFER_FLAG_END_OF_STREAM) {
                    mMediaCodec.queueInputBuffer(index, 0, 0, 0, flags);
                } else {
                    mMediaCodec.queueInputBuffer(index, 0, inputBuffer.limit(), pts, flags);
                }
            }           

6. 渲染音頻和視訊

對于音頻,可以從MediaCodec的buffer中直接讀取出pcm資料。(根據這個猜測:Android 手機的音頻安全級别都是L3的。)

對于視訊,如果是L3的級别,也可以從buffer中讀取出yuv資料。

對于L1的安全級别,則需要在mMediaCodec.configure的時候,傳入最後渲染的surfaceView(必須是SurfaceView)。這樣在mediaCodec releaseBuffer的時候,就直接渲染到view上了。程式無法直接接觸到解碼後的資料,保證了安全性。

四. 其他的一些總結

1. 安全等級,view之間的關系總結

播放器支援商業DRM實戰(二)—— 支援WideVine