【问题标题】:Play AES encrypted video in ExoPlayer offline离线播放 ExoPlayer 中的 AES 加密视频
【发布时间】:2021-01-29 08:19:24
【问题描述】:

我正在尝试使用 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();
    }

Here是加密视频的链接。 主要问题:视频正在播放但未解密。我错过了什么?

【问题讨论】:

标签: encryption aes exoplayer


【解决方案1】:

查看 logcat 输出,似乎没有任何 DRM、AES 或 clearkey 错误。

然后查看视频文件本身,它似乎报告了一些问题:

但是,检查使用您使用的相同 ffmpeg 方法加密的其他示例文件时,它们显示出类似的问题,因此这似乎是 ffprobe 对以这种方式加密的文件的典型输出。

然后使用 MP4 解析器查看视频文件结构本身以查看单个原子或标题块,似乎没有 PSSH 框。

PSSH 框是一个标头区域,其中包含有关 ISOBMFF mp4 文件加密的数据 - 这实际上是 CENC 规范中的一个可选字段,因此即使没有这个,您的视频也是有效的。

那么显而易见的问题是,播放器如何知道视频已编码?根据 CENC 规范,答案是:

  1. 检测

对于确定为 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 在下面的 cmets 中所指出的,并在此 GIT 线程 https://github.com/google/ExoPlayer/issues/8532#issuecomment-771811707 中概述,ExoPlayer 目前(2021 年 2 月)不读取 MP4 的 PSSH 框,仅用于碎片化的 MP4。从那个线程:

在深入研究之后,我发现 ExoPlayer 的 Mp4Extractor 实际上不读取 pssh 框。目前我们只在碎片化的 MP4 文件中读取此信息(使用 FragmentedMp4Extractor)。这意味着即使在使用 pssh 框播放文件时,drmInitData 仍然以 null 结束——这意味着播放失败。当您最初提交问题时,我没有意识到这个限制,否则我会更早地标记它。

但是,查看 Mp4Extractor 代码时,它会检查“encv”并检查默认的 key_id,这两者都存在于检查时生成的视频文件中。同样,如果 ExoPlayer 没有以可以理解的格式找到这些错误,或者如果找到它们但没有提供相应的密钥来播放文件,则 ExoPlayer 标记错误是合理的。

那么如何才能对视频进行加密和可靠播放呢?

根据过去在 Android 上使用 ffmpeg 的经验,您可以在 android 上使用 ffplay,尽管我怀疑这不会太简单。

还有一些利用 ExoPlayer 的更简单(出现)的示例也值得一看 - 例如:

您还可以考虑利用 DASH。如今,流式传输到移动设备的大多数媒体都使用 DASH 或 HLS 等流式传输协议——这些格式几乎总是将加密数据包含在“清单”或“索引”文件中,ExoPlayer 肯定会识别这一点。有在线教程和免费工具可让您将视频打包到 DASH 中,包括添加加密。 ExoPlayer 团队提供了有关下载和播放此类流的信息(链接在撰写本文时正确):

如果您想自己更详细地检查 mp4 文件,可以使用各种免费工具,例如:

【讨论】:

  • 您建议使用什么工具进行媒体加密?它需要在 Android 和 iOS 上运行 - 我的项目要求是在移动设备本身上完成加密
  • @Duna,答案已更新。虽然它在理论上更复杂并且可能具有您不需要的功能,但您可能会发现使用 DASH 和 HLS 方法将允许您利用更多主流工具,并且如果您需要它还允许您在将来利用更强大的基于 DRM 的密钥保护。
  • 您能否分享将 PSSH 框包含在输出文件中所需的 ffmpeg/mp4box 命令行参数?我可以和 ffplayer 一起玩,但不能和其他使用 clearkey 的玩家一起玩。
  • @Duna - 我想我最好为此提出一个新问题,并包括您正在使用的命令以及输出和人们可以响应的内容。理论上,如果安全信息在清单中,即使它不在媒体流本身中,播放器也应该能够播放内容,但不同的播放器可能会有不同的行为。
  • 更好的视频文件头查看器是:gpac.github.io/mp4box.js/test/filereader.html
猜你喜欢
  • 2020-10-15
  • 2021-10-27
  • 1970-01-01
  • 2018-09-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-12
相关资源
最近更新 更多