DRM frameworks for video and how to set it up in Widevine

To use DRM in a video Android app there are a couple of components needed. The first step in order to playback DRM video, is to add the copy protection (DRM scheme) so that playback cannot happen unencrypted. In order to encode an asset with DRM, you first need to decide a DRM technology. For maximum device reach, the best solution is to use a multi DRM approach, where the video is DRM encoded with different schemes, for example Widevine for Android. Encoding a video with Widevine means that each video segment that is created, can be encrypted, or only a subset of all the segments, to decrease the overhead of encrypting and decrypting all the segments while at the same time encrypting some so that playback doesn’t happen. During the encoding phase the encoding software contacts the DRM license server (in this case Widevine) with a carefully built request (which includes the content id and other relevant data) which then replies with the key that the encoding software uses to encrypt the content. Once the video has been DRM encoded, it can be played back via a player that supports DRM playback. The video player will be configured with the source URL for the manifest and also a license URL specifying the location of the DRM license server which will be contacted to obtain the license to decrypt the content. When I developed a DRM proxy server, to test the correctness of the Widevine DRM encrypted stream and license server I used the Bitmovin DRM video player which is freely available to use for testing online.

An example Widevine JSON server response to then be proxied back to the player has the following structure:

{ status: "OK",
  license: "CAISmQMKKgoQqlQ/HRzzdu2aF6Gbe7DIpRIQqlQ/+2tFu/UiEormYi4HZwJxaR+sp+ItecrtC29ou52c5QhWxPs39SzpOSDAuJSxAAJ8lp4vmi0TW+ta4U+zF6VN8rIAEaTgoQGVj96CfZXoOCHub<SHORTENED>",
   { content_id: "MjAoNV90ZWFxcw==",
     license_type: "STREAMING",
     request_type: "NEW" },
   [ { type: "SD", key_id: "GVe96CfZXoOCHubvRfm+vw==" },
     { type: "AUDIO", key_id: "+culCjPPVmqptrt0+JKZkQ==" },
     { type: "HD", key_id: "6VQ5DljWVHP09fKZU7Dz1A==" } ],
  make: "Google",
  model: "ChromeCDM-Mac",
  security_level: 3,
  internal_status: 0,
   { license_id: 
      { request_id: "qlQ/HRzzdu2aF6Gbe7DIxQ==",
        session_id: "qlQ/HRzzdu2aF6Gbe7DIxQ==",
        purchase_id: "",
        type: "STREAMING",
        version: 0 },
     signing_key: "357HHwLcmxxGFpY+BBnbewSViqTg5btKK+y8UdGbMuDJxnE3R2393/LzHt+iABFOA0Ad4Ti2kSAFVVvU8dAc6Q==",
     keybox_system_id: 7649,
     license_counter: 0 },
  drm_cert_serial_number: "MjMeZDNkNXFhZmM1OTQ5PTkzNmRlY2JnMDI5LGQxNGQ=",
  device_whitelist_state: "DEVICE_NOT_WHITELISTED",
  message_type: "LICENSE",
  platform: "pc",
  device_state: "RELEASED",
  pssh_data: { key_id: [ "MA==" ], content_id: "MjAxNV90ZWFycw==" },
  client_max_hdcp_version: "HDCP_NONE",
   [ { name: "architecture_name", value: "x86-64" },
     { name: "company_name", value: "Google" },
     { name: "model_name", value: "ChromeCDM" },
     { name: "platform_name", value: "MacOSX" },
     { name: "widevine_cdm_version", value: "4.10.1146.0" } ],
  signature_expiration_secs: 27174574,
  platform_verification_status: "PLATFORM_SECURE_STORAGE_SOFTWARE_VERIFIED",
  content_owner: "widevine_test",
  content_provider: "widevine_test" }

You can use a DRM test proxy server to protect and decrypt content with DRM.

To create DRM video protected content suitable for Android, use Shaka packager for Widevine encryption (or FFMPEG for AES-128 encryption with Key ID and Initialization Vector):

packager \ in=h264_baseline_360p_600.mp4,stream=audio,output=audio.mp4 \ in=h264_baseline_360p_600.mp4,stream=video,output=h264_360p.mp4 \ in=h264_main_480p_1000.mp4,stream=video,output=h264_480p.mp4 \ in=h264_main_720p_3000.mp4,stream=video,output=h264_720p.mp4 \ in=h264_high_1080p_6000.mp4,stream=video,output=h264_1080p.mp4 \ --enable_widevine_encryption \ --key_server_url https://license.uat.widevine.com/cenc/getcontentkey/widevine_test \ --content_id 7465737420636f6e74656e74206964 \ --signer widevine_test \ --aes_signing_key 1ae8ccd0e7985cc0b6203a55855a1034afc252980e970ca90e5202689f947ab9 \ --aes_signing_iv d58ce954203b7c9a9a9d467f59839249 \ --mpd_output h264.mpd \ --hls_master_playlist_output h264_master.m3u8

Above an example command using Shaka packager to create a Widevine compliant DRM stream using the Widevine test data.

Check playback on a player to test a stream:

DASH Manifest example to be used with DRM encrypted video

The audio and video files should be in separate AdaptationSets tags. Also, both the video and audio files are normally split into variable length segments (usually between 1 and 4 seconds long) and the file location of these segments is programmatically referenced with a progressive increasing integer. Each adaptation set can contain one or multiple Representations tags. For video the Representation tags reference the different bit rate the content is available. This is so that the video player can consume the Adaptive Bitrate stream which is most suitable depending on the context of the client, like internet connection speed and display resolution.

For example a Media Package Description file for DASH with the above defined structure for DRM is:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

<MPD id="e81d0383-82a0-41eb-9df4-4c1f895fea55" profiles="urn:mpeg:dash:profile:isoff-live:2011" type="static" mediaPresentationDuration="P0Y0M0DT0H1M13.440S" minBufferTime="P0Y0M0DT0H0M2.000S" xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:ns2="http://www.w3.org/1999/xlink">

<Period id="7ade7b96-8cf4-423f-933c-918d3aa269be">

    <AdaptationSet segmentAlignment="true" mimeType="video/mp4">

        <Representation id="c3345b20-c266-4248-ba67-b31c3868a3cf" bandwidth="1580185" width="1920" height="1080" frameRate="25" codecs="avc1.640033">

            <SegmentTemplate media="video/1080/1580185_8dc487d1-920a-4354-9fc1-f6b755865bee/segment_$Number$.m4s" initialization="video/1080/1580185_8dc487d1-920a-4354-9fc1-f6b755865bee/init.mp4" duration="100000" startNumber="0" timescale="25000"/>


        <Representation id="96565e2f-21fd-4527-a8d6-1488edc045dc" bandwidth="456000" width="1600" height="900" frameRate="25" codecs="avc1.640032">

            <SegmentTemplate media="video/900/456000_24dec1d8-12d2-40a2-bff9-32de4f116fa7/segment_$Number$.m4s" initialization="video/900/456000_24dec1d8-12d2-40a2-bff9-32de4f116fa7/init.mp4" duration="100000" startNumber="0" timescale="25000"/>


        <Representation id="dc022739-a6b8-4802-b52e-b3730998d54f" bandwidth="240000" width="1024" height="576" frameRate="25" codecs="avc1.640032">

            <SegmentTemplate media="video/576/240000_a104f4cb-8ceb-4a97-bf15-7ff4c759b865/segment_$Number$.m4s" initialization="video/576/240000_a104f4cb-8ceb-4a97-bf15-7ff4c759b865/init.mp4" duration="100000" startNumber="0" timescale="25000"/>


        <Representation id="9662b552-4cc5-4e9d-9f17-0e7f9c5274a4" bandwidth="866400" width="1920" height="1080" frameRate="25" codecs="avc1.640033">

            <SegmentTemplate media="video/1080/866400_874efcc5-a65d-4c80-ac0b-243ae3115ca4/segment_$Number$.m4s" initialization="video/1080/866400_874efcc5-a65d-4c80-ac0b-243ae3115ca4/init.mp4" duration="100000" startNumber="0" timescale="25000"/>


        <Representation id="6c8042ba-93a6-4675-b47e-f7cb6a3b57cd" bandwidth="2882025" width="1920" height="1080" frameRate="25" codecs="avc1.640033">

            <SegmentTemplate media="video/1080/2882025_1c0bec27-6277-41a0-ad31-cade212ac54d/segment_$Number$.m4s" initialization="video/1080/2882025_1c0bec27-6277-41a0-ad31-cade212ac54d/init.mp4" duration="100000" startNumber="0" timescale="25000"/>



    <AdaptationSet lang="en" segmentAlignment="true" mimeType="audio/mp4">

        <AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>

        <Representation id="47e3896d-19cc-4df3-9562-6439df9caf4b" bandwidth="128000" audioSamplingRate="48000" codecs="mp4a.40.2">

            <SegmentTemplate media="audio/segment_$Number$.m4s" initialization="audio/init.mp4" duration="192000" startNumber="0" timescale="48000"/>