概观

我们正在构建一个主要针对Android设备的音乐流应用程序 . 主要目的是销售音乐订阅,同时保护我们为最终用户提供的媒体资产,因此DRM解决方案是我们基础设施的核心 . 我们的设置如下:

核心组件:

  • 使用Exoplayer的Android移动应用程序

  • 统一流服务器作为流媒体服务器

  • 用于DRM服务的Azure媒体服务

  • Azure Media Services Sdk中的密钥生成实用程序

这些是我们采取的步骤:

第1步:编码

我们使用ffmpeg将各种媒体资产编码为不同的mp4比特率输出:

  • 256 KBPS

  • 192 KBPS

  • 128 KBPS

  • 96 KBPS

  • 64 KBPS

第2步:加密一旦我们转换了媒体资产,我们就转换后的媒体资产运行两步编码过程 .

  • 首先,我们使用以下示例输出运行AZURE .NET SDK进行密钥生成:

key:nb:kid:UUID:fae1ab22-cd13-44f8-a746-0d7dd3a31645键值:MVEp833L3jmUaNvTy3JUuQ == PlayReady URL:https://music.keydelivery.mediaservices.windows.net/PlayReady/ Widevine URL:https:// music.keydelivery.mediaservices.windows.net/Widevine/?KID=fae1ab22-cd13-44f8-a746-0d7dd3a31645身份验证政策:nb:ckpid:UUID:15ec2f3f-4ad9-49be-9f89-b5431bc3dec1

这为我们生成了以下项目:KEY_ID,CONTENT_KEY,AUTH_POLICY和LICENSE_URL .

我们已经注意到,在使用Azure .NET SDK时,存在令牌限制策略选项,该选项负责通过SWT或带有一组选项的JWT令牌为许可证生成令牌保护 . 由于我们不需要令牌保护,因此我们将SDK设置为使用开放授权策略颁发许可证 . 我们已经突出显示了我们修改的代码行以实现此目的以供参考:

Azure Media Services .NET SDK

Line 49:
      bool tokenRestriction = false; - main change

    Line 81:
      if (tokenRestriction)
        tokenTemplateString = AddTokenRestrictedAuthorizationPolicy(key);
      else
        AddOpenAuthorizationPolicy(key);
      Console.WriteLine("Auth policy: {0}",
      key.AuthorizationPolicyId,          System.Convert.ToBase64String(key.GetClearKeyValue()));

由于ExoPlayer只能在Android TV上播放PlayReady,因此我们仅限于申请Widevine许可 .

  • 然后,我们使用Unified Streaming mp4split工具,使用我们在Azure .NET SDK中突出显示的信息来加密我们的媒体资产 . 下面显示了为Widevine许可证加密资产的命令,其中包含有关命令的更多详细信息 .

关于命令的注释:

我们需要将KEY_ID和CONTENT_KEY转换为十六进制值,然后才能在mp4split命令中使用它们:

UUID的KEY_ID:fae1ab22-cd13-44f8-a746-0d7dd3a31645变为22ABE1FA13CDF844A7460D7DD3A31645 MVEp833L3jmUaNvTy3JUuQ的CONTENT_KEY ==变为315129F37DCBDE399468DBD3CB7254B9

我们仍然不知道从Azure .NET SDK获得的响应中的PSSH值是什么 . Unified Streaming文档说明如下:

许可证服务器提供的DRM特定数据(Widevine PSSH数据) . 可以是Base64字符串,也可以是包含已解码Base64数据的文件 . 文件名必须包含' . '

请注意,LA_URL(许可证获取URL)未在“pssh”框中发出信号,并且通常在支持Winevine Modular本身的DASH播放器中进行硬编码 . 建议的设置是使用正确的URL .

我们正在寻找帮助,以确定我们收到的Azure .NET SDK输出中的PSSH数据是什么 . 与此同时,我们一直使用AUTH_POLICY_ID(转换为Base64)作为mp4split命令中的PSSH .

当我们将上述所有内容放在一起时,我们使用的mp4split脚本变为:

#!/bin/bash
      KID=22ABE1FA13CDF844A7460D7DD3A31645
      CEK=315129F37DCBDE399468DBD3CB7254B9
      LAURL="https://music.keydelivery.mediaservices.windows.net/Widevine/?     KID=fae1ab22-cd13-44f8-a746-0d7dd3a31645"
      PSSH="Py/sFdlKvkmfibVDG8PewQ=="

      mp4split --license-key=usp/license.key -o musicfile.ism \
        --mpd.inline_drm \
        --widevine.key=${KID}:${CEK} \
        --widevine.license_server_url=${LAURL} \
        --widevine.drm_specific_data=${PSSH} \
        musicfile-64kbps.mp4

      aws s3 cp musicfiles.ism s3://output-bucket/
      aws s3 cp musicfiles-64kbps.mp4 s3://output-bucket/

笔记:

  • 对于上面的命令,我们正在为64kps比特率mp4音乐文件生成带有Widevine许可加密的ism清单文件 .

  • --license-key用于统一流优质产品 .

  • AWS S3命令用于将我们生成的ism和关联的mp4 64kps比特率文件上传到我们的aws s3存储桶 .

完成此过程后,下一步是与ExoPlayer集成 .

第3步:流式传输

一旦我们将.ism及其相关的mp4文件上传到我们的S3存储桶,移动应用程序就会使用以下详细信息来访问流并进行回放:

{
        "name": "Music Tests",
        "uri": "http://mozart.musicfiles.com/auth/media/musicfiles.ism/.mpd",
        "drm_scheme": "widevine",
        "drm_license_url":      "https://music.keydelivery.mediaservices.windows.net/Widevine/?KID=fae1ab22-cd13-44f8-a746-0d7dd3a31645"
      }

哪里:

URI - 是我们的统一流服务器 endpoints DRM_LICENSE_URI - 是我们的Azure .NET SDK提供的宽服务器URL

在我们上传,编码和加密媒体资产后,我们在此阶段会收到错误 . Android应用中的错误如下:

02-07 12:58:58.458 16454-5913/com.google.android.exoplayer2.demo   E/OMXMaster: A component of name 'OMX.qcom.audio.decoder.aac' already exists,  ignoring this one.
    02-07 12:59:02.892 16454-5811/com.google.android.exoplayer2.demo E/ExoPlayerImplInternal: Renderer error.
    com.google.android.exoplayer2.ExoPlaybackException
    at com.google.android.exoplayer2.mediacodec.MediaCodecRenderer.shouldWaitForKeys(MediaCodecRenderer.java:709)
    at com.google.android.exoplayer2.mediacodec.MediaCodecRenderer.feedInputBuffer(MediaCodecRenderer.java:650)
    at com.google.android.exoplayer2.mediacodec.MediaCodecRenderer.render(MediaCodecRenderer.java:490)
    at com.google.android.exoplayer2.ExoPlayerImplInternal.doSomeWork(ExoPlayerImplInternal.java:464)
    at com.google.android.exoplayer2.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:300)
    at android.os.Handler.dispatchMessage(Handler.java:98)
    at android.os.Looper.loop(Looper.java:168)
    at android.os.HandlerThread.run(HandlerThread.java:61)
    at com.google.android.exoplayer2.util.PriorityHandlerThread.run(PriorityHandlerThread.java:40)
    Caused by: com.google.android.exoplayer2.drm.DrmSession$DrmSessionException: com.google.android.exoplayer2.upstream.HttpDataSource$InvalidResponseCodeException: Response code: 400
    at com.google.android.exoplayer2.drm.DefaultDrmSessionManager.onError(DefaultDrmSessionManager.java:594)
    at com.google.android.exoplayer2.drm.DefaultDrmSessionManager.onKeysError(DefaultDrmSessionManager.java:589)
    at com.google.android.exoplayer2.drm.DefaultDrmSessionManager.onKeyResponse(DefaultDrmSessionManager.java:549)
    at com.google.android.exoplayer2.drm.DefaultDrmSessionManager.access$900(DefaultDrmSessionManager.java:49)
    at com.google.android.exoplayer2.drm.DefaultDrmSessionManager$PostResponseHandler.handleMessage(DefaultDrmSessionManager.java:669)
    at android.os.Handler.dispatchMessage(Handler.java:102)
    at android.os.Looper.loop(Looper.java:168) 
    at android.os.HandlerThread.run(HandlerThread.java:61) 
    at com.google.android.exoplayer2.util.PriorityHandlerThread.run(PriorityHandlerThread.java:40) 
    Caused by: com.google.android.exoplayer2.upstream.HttpDataSource$InvalidResponseCodeException: Response code: 400
    at com.google.android.exoplayer2.upstream.DefaultHttpDataSource.open(DefaultHttpDataSource.java:212)
    at com.google.android.exoplayer2.upstream.DataSourceInputStream.checkOpened(DataSourceInputStream.java:101)
    at com.google.android.exoplayer2.upstream.DataSourceInputStream.read(DataSourceInputStream.java:81)
    at com.google.android.exoplayer2.upstream.DataSourceInputStream.read(DataSourceInputStream.java:75)
    at com.google.android.exoplayer2.util.Util.toByteArray(Util.java:118)
    at com.google.android.exoplayer2.drm.HttpMediaDrmCallback.executePost(HttpMediaDrmCallback.java:106)
    at com.google.android.exoplayer2.drm.HttpMediaDrmCallback.executeKeyRequest(HttpMediaDrmCallback.java:91)
    at com.google.android.exoplayer2.drm.DefaultDrmSessionManager$PostRequestHandler.handleMessage(DefaultDrmSessionManager.java:692)
    at android.os.Handler.dispatchMessage(Handler.java:102)
    at android.os.Looper.loop(Looper.java:168)
    at android.os.HandlerThread.run(HandlerThread.java:61)
    02-07 12:59:02.896 16454-16454/com.google.android.exoplayer2.demo E/EventLogger: playerFailed [7.76]
    com.google.android.exoplayer2.ExoPlaybackException
    at com.google.android.exoplayer2.mediacodec.MediaCodecRenderer.shouldWaitForKeys(MediaCodecRenderer.java:709)
    at com.google.android.exoplayer2.mediacodec.MediaCodecRenderer.feedInputBuffer(MediaCodecRenderer.java:650)
    at com.google.android.exoplayer2.mediacodec.MediaCodecRenderer.render(MediaCodecRenderer.java:490)
    at com.google.android.exoplayer2.ExoPlayerImplInternal.doSomeWork(ExoPlayerImplInternal.java:464)
    at com.google.android.exoplayer2.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:300)
    at android.os.Handler.dispatchMessage(Handler.java:98)
    at android.os.Looper.loop(Looper.java:168)
    at android.os.HandlerThread.run(HandlerThread.java:61)
    at com.google.android.exoplayer2.util.PriorityHandlerThread.run(PriorityHandlerThread.java:40)
    Caused by: com.google.android.exoplayer2.drm.DrmSession$DrmSessionException: com.google.android.exoplayer2.upstream.HttpDataSource$InvalidResponseCodeException: Response code: 400
    at com.google.android.exoplayer2.drm.DefaultDrmSessionManager.onError(DefaultDrmSessionManager.java:594)
    at com.google.android.exoplayer2.drm.DefaultDrmSessionManager.onKeysError(DefaultDrmSessionManager.java:589)
    at com.google.android.exoplayer2.drm.DefaultDrmSessionManager.onKeyResponse(DefaultDrmSessionManager.java:549)
    at com.google.android.exoplayer2.drm.DefaultDrmSessionManager.access$900(DefaultDrmSessionManager.java:49)
    at com.google.android.exoplayer2.drm.DefaultDrmSessionManager$PostResponseHandler.handleMessage(DefaultDrmSessionManager.java:669)
    at android.os.Handler.dispatchMessage(Handler.java:102)
    at android.os.Looper.loop(Looper.java:168) 
    at android.os.HandlerThread.run(HandlerThread.java:61) 
    at com.google.android.exoplayer2.util.PriorityHandlerThread.run(PriorityHandlerThread.java:40) 
    Caused by: com.google.android.exoplayer2.upstream.HttpDataSource$InvalidResponseCodeException: Response code: 400
    at com.google.android.exoplayer2.upstream.DefaultHttpDataSource.open(DefaultHttpDataSource.java:212)
    at com.google.android.exoplayer2.upstream.DataSourceInputStream.checkOpened(DataSourceInputStream.java:101)
    at com.google.android.exoplayer2.upstream.DataSourceInputStream.read(DataSourceInputStream.java:81)
    at com.google.android.exoplayer2.upstream.DataSourceInputStream.read(DataSourceInputStream.java:75)
    at com.google.android.exoplayer2.util.Util.toByteArray(Util.java:118)
    at com.google.android.exoplayer2.drm.HttpMediaDrmCallback.executePost(HttpMediaDrmCallback.java:106)
    at com.google.android.exoplayer2.drm.HttpMediaDrmCallback.executeKeyRequest(HttpMediaDrmCallback.java:91)
    at com.google.android.exoplayer2.drm.DefaultDrmSessionManager$PostRequestHandler.handleMessage(DefaultDrmSessionManager.java:692)
    at android.os.Handler.dispatchMessage(Handler.java:102)
    at android.os.Looper.loop(Looper.java:168)
    at android.os.HandlerThread.run(HandlerThread.java:61)

Azure Media Services的错误代码在此处指出响应代码400的以下内容:

Azure Error Codes

400错误请求请求包含信息无效,由于以下原因之一而被拒绝: - 指定了不受支持的API版本 . 有关最新版本,请参阅介质服务REST API开发的设置 . - 未指定媒体服务的API版本 . 有关如何指定API版本的信息,请参阅使用Media Services REST API连接到媒体服务 . 注意如果您使用.NET或Java SDK连接到Media Services,则只要您尝试对Media Services执行某些操作,就会为您指定API版本 . 已指定未定义的属性 . 属性名称位于错误消息中 . 只能指定属于给定实体成员的属性 . 有关实体及其属性的列表,请参阅Azure Media Services REST API参考 . 指定了无效的属性值 . 属性名称位于错误消息中 . 有关有效的属性类型及其值,请参阅上一个链接 . 缺少属性值并且是必需的 . 指定的部分URL包含错误值 . 尝试更新WriteOnce属性 . 尝试创建具有输入资产的作业,该资产具有未指定或无法确定的主AssetFile . 尝试更新SAS定位器 . 只能创建或删除SAS定位器 . 流式定位器可以更新 . 有关更多信息,请参阅定位器 . 提交了不受支持的操作或查询 .

此外,当我们在widevine服务器URL上运行GET请求时

GET https://music.keydelivery.mediaservices.windows.net/Widevine/?KID=fae1ab22-cd13-44f8-a746-0d7dd3a31645

我们得到以下回复:

<?xml version="1.0" encoding="utf-8"?>
<Error>
<Message>License challenge is missing from the request body.</Message>
<Code>NoLicenseChallenge</Code>
</Error>

上述错误消息是什么意思?

我们一直在阅读Azure文档,但我们无法弄清楚如何解决错误 .

我们正在尝试将我们的第三方流媒体服务器统一流媒体与天蓝色媒体服务DRM解决方案相集成 .