15、处理加密内容:DRM保护内容的提取与注意事项
DRM,全称是Digital Rights Management,数字版权管理。说白了,就是内容提供商给视频文件上了一把锁。你想用MediaExtractor直接提取?没门儿。我刚开始接触DRM时也天真地以为,只要拿到文件路径就能随便读——结果碰了一鼻子灰。
这一章,我们就来聊聊DRM是什么、MediaExtractor怎么跟它打交道、以及你实际开发中会遇到哪些坑。
15.1 DRM的基本概念
DRM不是一种技术,而是一套技术体系。它通常包含三个部分:
- 加密:视频内容本身是加密的,直接读出来是乱码
- 许可证:只有拿到合法的许可证,才能解密
- 解密模块:Android系统提供的DRM框架,负责解密
常见的DRM方案有:
| 方案 | 提供商 | Android支持情况 |
|---|---|---|
| Widevine | 原生支持,L1/L3等级 | |
| PlayReady | Microsoft | 部分设备支持 |
| FairPlay | Apple | 仅iOS,Android不支持 |
| ClearKey | 无 | 测试用,无实际保护 |
我个人习惯把DRM理解成「三层结构」:
内容层 → 加密层 → 许可证层
MediaExtractor只能接触到内容层,加密层和许可证层由DRM框架管理。
15.2 MediaExtractor与DRM的交互
你可能会问:MediaExtractor能不能直接读取DRM加密的视频?
答案是:能,但只能读到加密后的数据。就像你拿到一个保险箱,能看到箱子本身,但看不到里面的东西。
MediaExtractor读取DRM内容的流程是这样的:
// 1. 先检查是否有DRM信息
MediaExtractor extractor = new MediaExtractor();
extractor.setDataSource(videoPath);
// 2. 获取DRM信息
MediaFormat format = extractor.getTrackFormat(0);
byte[] initData = format.getByteBuffer("csd-0");
String mimeType = format.getString(MediaFormat.KEY_MIME);
// 3. 判断是否需要DRM
if (mimeType != null && mimeType.startsWith("video/")) {
// 检查是否有加密信息
int drmScheme = format.getInteger(MediaFormat.KEY_DRM_SCHEME, -1);
if (drmScheme != -1) {
// 这个轨道是DRM保护的
Log.d("DRM", "检测到DRM保护,scheme: " + drmScheme);
}
}
嗯,这里要注意:KEY_DRM_SCHEME这个字段在Android 10以下可能不存在。我在项目中遇到过,老设备上直接getInteger会抛异常,得用try-catch包一下。
15.3 实际提取DRM内容的步骤
如果你想真正提取DRM视频的原始帧数据,光靠MediaExtractor是不够的。你需要配合MediaDrm和MediaCrypto。
完整的流程分四步:
- 初始化DRM会话:创建MediaDrm实例,获取许可证
- 创建解密器:用MediaCrypto关联DRM会话
- 配置Extractor:将MediaCrypto传给MediaExtractor
- 读取并解密:Extractor读取加密数据,自动解密
代码示例:
// 步骤1:初始化DRM
UUID widevineUuid = new UUID(0xEDEF8BA979D64ACEL, 0xA3C827DCD51D21EDL);
MediaDrm mediaDrm = new MediaDrm(widevineUuid);
// 步骤2:获取许可证(这里简化了,实际需要联网请求)
byte[] sessionId = mediaDrm.openSession();
String licenseUrl = "https://your-license-server.com/getkey";
// ... 联网请求许可证 ...
// 步骤3:创建MediaCrypto
MediaCrypto mediaCrypto = new MediaCrypto(widevineUuid, sessionId);
// 步骤4:配置Extractor
MediaExtractor extractor = new MediaExtractor();
extractor.setDataSource(videoPath);
extractor.setMediaCrypto(mediaCrypto);
// 现在可以正常读取了
int trackIndex = extractor.getTrackCount();
for (int i = 0; i < trackIndex; i++) {
MediaFormat format = extractor.getTrackFormat(i);
String mime = format.getString(MediaFormat.KEY_MIME);
if (mime != null && mime.startsWith("video/")) {
extractor.selectTrack(i);
break;
}
}
ByteBuffer buffer = ByteBuffer.allocate(1024 * 1024);
MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
while (true) {
int sampleSize = extractor.readSampleData(buffer, 0);
if (sampleSize < 0) break;
// 这里拿到的已经是解密后的数据了
extractor.advance();
}
警告:上面的代码只是演示流程。实际项目中,许可证获取需要联网,而且许可证通常有有效期。我曾经因为许可证过期,调试了一整天才发现是时间问题。
15.4 避坑指南
我踩过的坑,列出来给你参考:
- 坑1:设备不支持——不是所有Android设备都支持Widevine L1。L3只能解密到540p,L1才能到1080p。你想想看,用户手机不支持,你代码写得再好也没用。
- 坑2:许可证过期——DRM许可证有时效性。我遇到过视频播到一半突然黑屏,就是因为许可证在播放过程中过期了。
- 坑3:多轨道DRM不一致——有些视频文件,视频轨道有DRM,音频轨道没有。MediaExtractor需要分别处理。
- 坑4:Android版本差异——Android 8之前和之后的DRM API差别很大。建议用
Build.VERSION.SDK_INT做兼容。
小技巧:调试DRM时,可以用adb shell dumpsys media.drm查看当前设备的DRM能力。这个命令能告诉你设备支持哪些方案、什么等级。
15.5 知识体系图
下面这张图,帮你理清DRM提取的核心逻辑:
15.6 总结
DRM处理,说白了就是跟许可证服务器打交道。MediaExtractor本身不负责解密,它只是把加密数据喂给DRM框架。你真正要关心的,是许可证怎么拿、什么时候过期、设备支不支持。
我个人建议:能不碰DRM就别碰。如果只是做本地视频播放器,大部分用户的内容都没有DRM。但如果你做的是Netflix、腾讯视频这类App,那DRM就是必修课了。
最后提醒一句:不要尝试破解DRM。这不仅是技术问题,更是法律问题。我见过有人因为破解Widevine被起诉的,得不偿失。
公众号:蓝海资料掘金营,微信deep3321