播放器支持商业DRM实战(二)—— 支持WideVine

简介: ### 一. WideVine简介在收购Widevine之前,Android没有系统的数字版权保护机制,2010年12月,Google不惜重金将视频数字版权管理软件公司Widevine招安,弥补了Android在这方面的短板。Android从3.0开始就支持Widevine。现在Widevine已经成为GMS(Google Mobile Service)中必备的内容,所有想要得到GMS的手机厂商

一. WideVine简介

在收购Widevine之前,Android没有系统的数字版权保护机制,2010年12月,Google不惜重金将视频数字版权管理软件公司Widevine招安,弥补了Android在这方面的短板。Android从3.0开始就支持Widevine。现在Widevine已经成为GMS(Google Mobile Service)中必备的内容,所有想要得到GMS的手机厂商,都需要根据GMS的要求搭载Widevine。

WideVine的安全级别

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,会涉及到播放器的很多流程,大体涉及的流程如下:

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之间的关系总结

相关文章
|
2月前
|
编解码 Linux API
从FFplay到自定义播放器:构建高性能多媒体应用程序的进阶之路
【10月更文挑战第15天】多媒体应用程序的开发是一个复杂的过程,尤其是在追求高性能和定制化体验时。本文将引导你从使用FFplay作为起点,逐步过渡到构建一个完全自定义的播放器。我们将探讨FFmpeg库的高级用法、多媒体同步原理、跨平台开发注意事项,以及如何实现用户界面与音视频解码的无缝集成。
45 1
|
3月前
|
Linux 开发工具 Android开发
FFmpeg开发笔记(五十三)移动端的国产直播录制工具EasyPusher
EasyPusher是一款国产RTSP直播录制推流客户端工具,支持Windows、Linux、Android及iOS等系统。尽管其GitHub仓库(安卓版:https://github.com/EasyDarwin/EasyPusher-Android)已多年未更新,但通过一系列改造,如升级SDK版本、迁移到AndroidX、指定本地NDK版本及更新Gradle版本等,仍可在最新Android Studio上运行。以下是针对Android Studio Dolphin版本的具体改造步骤。
63 3
FFmpeg开发笔记(五十三)移动端的国产直播录制工具EasyPusher
|
4月前
|
编解码 Linux 开发工具
大牛直播SDK跨平台RTMP直播推送模块技术设计和功能列表
大牛直播SDK是一款跨平台RTMP直播推送模块,支持Windows、Linux(x64_64与aarch64架构)、Android及iOS平台。该SDK功能全面,包括摄像头、屏幕、麦克风等数据采集与推送,并支持编码前后数据对接。其架构设计优秀,确保低延迟与高效率,结合SmartPlayer播放器实现毫秒级延迟体验。具备全自研框架,易于扩展且支持多种数据源接入,如外部YUV/RGB/H.264等格式。此外,各平台支持特性丰富,如Windows平台支持多摄像头合成,Android与iOS平台支持前后摄像头实时切换等。大牛直播SDK还提供了多个示例项目以帮助开发者快速上手。
100 0
设计并实现同时支持多种视频格式的流媒体点播系统
设计并实现同时支持多种视频格式的流媒体点播系统
164 0
|
小程序 JavaScript 数据安全/隐私保护
适配最新微信小程序隐私协议开发指南,兼容uniapp版本
前一阵微信小程序官方发布了一个用户隐私保护指引填写说明,说是为了规范开发者的用户个人信息处理行为,保障用户合法权益,小程序、插件中涉及处理用户个人信息的开发者,均需补充相应用户隐私保护指引
189 0
|
编解码 Java 开发工具
[技术分享]Android平台实时音视频录像模块设计之道
录像有什么难的?无非就是数据过来,编码保存mp4而已,这可能是好多开发者在做录像模块的时候的思考输出。是的,确实不难,但是做好,或者和其他模块有非常好的逻辑配合,确实不容易。
109 0
|
编解码 开发工具 数据安全/隐私保护
Windows平台RTMP直播推送集成简要说明
好多开发者在集成大牛直播SDK (官方)的Windows平台RTMP推送模块时吓一跳,怎么这么多接口?本文做个简单的拆分:
HarmonyOS学习路之开发篇—多媒体开发(媒体会话管理开发)
AVSession是一套媒体播放控制框架,对媒体服务和界面进行解耦,并提供规范的通信接口,使应用可以自由、高效地在不同的媒体之间完成切换。
|
存储 网络协议 流计算
|
Web App开发 编解码 算法
Web端H.265播放器研发解密
音视频编解码对于前端工程师是一个比较少涉足的领域,涉及到流媒体技术中的文本、图形、图像、音频和视频多种理论知识的学习,才能够应用到具体实践中,本团队在多媒体领域深耕两年多,才算是有一定产出,我们自研web播放器并支持h.265解码,在码率优化的大背景下(保持画质不变情况下,应用图像增强、roi区域检测、智能场景分类和h265编解码等多种技术能力,将码流降低50%。
Web端H.265播放器研发解密