encryption - 离线播放 ExoPlayer 中的 AES 加密视频
问题描述
我正在尝试使用 ExoPlayer 从本地存储播放加密视频。使用 FFMPEG 加密视频的命令如下:
-i /storage/emulated/0/Download/20210125_193031.mp4 -vcodec copy -acodec copy -c:v libx264 -encryption_scheme cenc-aes-ctr -encryption_key b42ca3172ee4e69bf51848a59db9cd13 -encryption_kid 09e367028f33436ca5dd60ffe6671e70 /storage/emulated/0/Download/out_enc.mp4
这是我的播放器的源代码:
public class PlayerActivity extends AppCompatActivity {
private SimpleExoPlayer player;
private DefaultDrmSessionManager drmSessionManager;
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_player);
// Build the media item.
PlayerView playerView = findViewById(R.id.video_view);
player = new SimpleExoPlayer.Builder(this).build();
playerView.setPlayer(player);
//player.prepare();
//FFMPEG command: -i /storage/emulated/0/Download/20210125_193031.mp4 -vf scale=-1:720 -c:v libx264 -encryption_scheme cenc-aes-ctr -encryption_key b42ca3172ee4e69bf51848a59db9cd13 -encryption_kid 09e367028f33436ca5dd60ffe6671e70 /storage/emulated/0/Download/out_enc.mp4
//base 64 keys generated from: https://www.base64encode.org/
//playVideo("/storage/emulated/0/Download/out_enc.mp4", "MDllMzY3MDI4ZjMzNDM2Y2E1ZGQ2MGZmZTY2NzFlNzA=", "YjQyY2EzMTcyZWU0ZTY5YmY1MTg0OGE1OWRiOWNkMTM=");
playVideo("/storage/emulated/0/Download/out_enc.mp4", "CeNnAo8zQ2yl3WD/5mcecA", "tCyjFy7k5pv1GEilnbnNEw");
}
private void playVideo(String url, String keyID, String keyValue) {
try {
drmSessionManager = buildDrmSessionManager(Util.getDrmUuid(C.CLEARKEY_UUID.toString()), true, keyID, keyValue
);
} catch (Exception e) {
e.printStackTrace();
}
player.setMediaSource(buildDashMediaSource(Uri.parse(url)));
player.prepare();
player.setPlayWhenReady(true);
}
private MediaSource buildDashMediaSource(Uri uri) {
DefaultDataSourceFactory dashChunkSourceFactory = new DefaultDataSourceFactory(this, "agent");
return new ProgressiveMediaSource.Factory(dashChunkSourceFactory)
.setDrmSessionManager(drmSessionManager)
.createMediaSource(uri);
}
private DefaultDrmSessionManager buildDrmSessionManager(UUID uuid, Boolean multiSession, String id, String value) {
/* String base64Id = Base64.encodeToString(id.getBytes(), Base64.DEFAULT);
String base64Value = Base64.encodeToString(value.getBytes(), Base64.DEFAULT);*/
String keyString = "{\"keys\":[{\"kty\":\"oct\",\"k\":\""+value+"\",\"kid\":\""+id+"\"}],\"type\":\"temporary\"}";;
LocalMediaDrmCallback drmCallback = new LocalMediaDrmCallback(keyString.getBytes());
FrameworkMediaDrm mediaDrm = null;
try {
mediaDrm = FrameworkMediaDrm.newInstance(uuid);
} catch (UnsupportedDrmException e) {
e.printStackTrace();
}
return new DefaultDrmSessionManager(uuid, mediaDrm, drmCallback, null, multiSession);
}
@Override
protected void onDestroy() {
player.release();
super.onDestroy();
}
这是加密视频的链接。主要问题:视频正在播放但未解密。我错过了什么?
解决方案
查看 logcat 输出,似乎没有任何 DRM、AES 或 clearkey 错误。
然后查看视频文件本身,它似乎报告了一些问题:
但是,检查使用您使用的相同 ffmpeg 方法加密的其他示例文件时,它们会显示类似的问题,因此这似乎是 ffprobe 对于以这种方式加密的文件的典型输出。
然后使用 MP4 解析器查看视频文件结构本身以查看单个原子或标题块,似乎没有 PSSH 框。
PSSH 框是一个标头区域,其中包含有关 ISOBMFF mp4 文件加密的数据 - 这实际上是 CENC 规范中的一个可选字段,因此即使没有这个,您的视频也是有效的。
那么显而易见的问题是,播放器如何知道视频已编码?根据 CENC 规范,答案是:
- 检测
对于确定为 ISO 基本媒体文件格式 [ISOBMFF] 的流,该 ISO 通用加密 ('cenc') 保护方案可如下检测。
保护方案信令符合[ISOBMFF]。应用保护后,流类型将转换为视频的“encv”或音频的“enca”,并在样本描述框(“stsd”的样本条目中添加保护方案信息框(“sinf”) )。保护方案信息框 ('sinf') 将包含一个方案类型框 ('schm'),其中 scheme_type 字段的值设置为 'cenc'
使用 MP4 分析器(见下文)查看您的视频表明它确实具有在 stud 框中显示为“encv”的流类型(下面的检查工具的输出):
使用相同的加密密钥测试 ffplay 本身的播放,表明视频确实成功播放:
ffplay out_enc.mp4 -decryption_key b42ca3172ee4e69bf51848a59db9cd13
但是,除非您提供解密密钥,否则普通玩家将无法播放它。在这种情况下,期望播放器标记错误是合理的,但我检查过的一些常见播放器(包括 VLC)似乎不会发生这种情况,因此 Android 上的 ExoPlayer 很可能也没有标记此错误。
具体来看 ExoPlayer,正如 Duna 在下面的评论中指出并在此 GIT 线程https://github.com/google/ExoPlayer/issues/8532#issuecomment-771811707中概述的那样,ExoPlayer 目前(2021 年 2 月)不读取 PSSH用于 MP4 的框,仅适用于零碎的 MP4。从那个线程:
在深入研究之后,我发现 ExoPlayer 的 Mp4Extractor 实际上不读取 pssh 框。目前我们只在碎片化的 MP4 文件中读取此信息(使用 FragmentedMp4Extractor)。这意味着即使在使用 pssh 框播放文件时,drmInitData 仍然以 null 结束——这意味着播放失败。当您最初提交问题时,我没有意识到这个限制,否则我会更早地标记它。
但是,查看 Mp4Extractor 代码,它确实会检查“encv”,并且还会检查默认的 key_id,这两者都存在于检查时生成的视频文件中。同样,如果 ExoPlayer 没有以可以理解的格式找到这些错误,或者如果找到它们但没有提供相应的密钥来播放文件,则 ExoPlayer 标记错误是合理的。
那么如何才能对视频进行加密并可靠播放呢?
您可以在 android 上使用 ffplay,尽管根据过去在 Android 上使用 ffmpeg 的经验,我怀疑这不会太简单。
还有一些更容易(出现)的例子利用 ExoPlayer 也值得一看 - 例如:
您还可以考虑利用 DASH。如今,流式传输到移动设备的大多数媒体都使用 DASH 或 HLS 等流式传输协议——这些格式几乎总是将加密数据包含在“清单”或“索引”文件中,ExoPlayer 肯定会识别这一点。有在线教程和免费工具可让您将视频打包到 DASH 中,包括添加加密。ExoPlayer 团队提供了有关下载和播放这些流的信息(在撰写本文时链接正确):
如果您想自己更详细地检查 mp4 文件,可以使用各种免费工具,例如:
推荐阅读
- hibernate - org.springframework.transaction.CannotCreateTransactionException(访问 aws RDS 时)
- c# - 多个工作人员(同一实例) - 确定工作何时完成的模式?
- utf-8 - UTF-8 中的代理字符是什么?
- sql-server - 如何将大型平面文件导入 SQL SERVER 2016?
- html - PHP AJAX 实时搜索文本框下拉菜单
- php - 通过 php 客户端进行 HTTP 放置
- python - 使用 for 循环替换熊猫列每一行中的单元格值
- scala - 类型类或通过隐式添加方法?
- android - Android 应用数据库到 Azure
- visual-studio-code - Visual Studio Code 在运行电子时打开多个终端