media_drm_bridge.cc revision 46d4c2bc3267f3f028f39e7e311b0f89aba2e4fd
1// Copyright (c) 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "media/base/android/media_drm_bridge.h"
6
7#include <algorithm>
8
9#include "base/android/build_info.h"
10#include "base/android/jni_array.h"
11#include "base/android/jni_string.h"
12#include "base/callback_helpers.h"
13#include "base/containers/hash_tables.h"
14#include "base/lazy_instance.h"
15#include "base/location.h"
16#include "base/logging.h"
17#include "base/message_loop/message_loop_proxy.h"
18#include "base/strings/string_util.h"
19#include "jni/MediaDrmBridge_jni.h"
20
21#include "widevine_cdm_version.h"  // In SHARED_INTERMEDIATE_DIR.
22
23using base::android::AttachCurrentThread;
24using base::android::ConvertUTF8ToJavaString;
25using base::android::ConvertJavaStringToUTF8;
26using base::android::JavaByteArrayToByteVector;
27using base::android::ScopedJavaLocalRef;
28
29namespace media {
30
31static uint32 ReadUint32(const uint8_t* data) {
32  uint32 value = 0;
33  for (int i = 0; i < 4; ++i)
34    value = (value << 8) | data[i];
35  return value;
36}
37
38static uint64 ReadUint64(const uint8_t* data) {
39  uint64 value = 0;
40  for (int i = 0; i < 8; ++i)
41    value = (value << 8) | data[i];
42  return value;
43}
44
45// The structure of an ISO CENC Protection System Specific Header (PSSH) box is
46// as follows. (See ISO/IEC FDIS 23001-7:2011(E).)
47// Note: ISO boxes use big-endian values.
48//
49// PSSH {
50//   uint32 Size
51//   uint32 Type
52//   uint64 LargeSize  # Field is only present if value(Size) == 1.
53//   uint32 VersionAndFlags
54//   uint8[16] SystemId
55//   uint32 DataSize
56//   uint8[DataSize] Data
57// }
58const int kBoxHeaderSize = 8;  // Box's header contains Size and Type.
59const int kBoxLargeSizeSize = 8;
60const int kPsshVersionFlagSize = 4;
61const int kPsshSystemIdSize = 16;
62const int kPsshDataSizeSize = 4;
63const uint32 kTencType = 0x74656e63;
64const uint32 kPsshType = 0x70737368;
65const uint8 kWidevineUuid[16] = {
66    0xED, 0xEF, 0x8B, 0xA9, 0x79, 0xD6, 0x4A, 0xCE,
67    0xA3, 0xC8, 0x27, 0xDC, 0xD5, 0x1D, 0x21, 0xED };
68
69typedef std::vector<uint8> UUID;
70
71class KeySystemUuidManager {
72 public:
73  KeySystemUuidManager();
74  UUID GetUUID(const std::string& key_system);
75  void AddMapping(const std::string& key_system, const UUID& uuid);
76
77 private:
78  typedef base::hash_map<std::string, UUID> KeySystemUuidMap;
79
80  KeySystemUuidMap key_system_uuid_map_;
81
82  DISALLOW_COPY_AND_ASSIGN(KeySystemUuidManager);
83};
84
85KeySystemUuidManager::KeySystemUuidManager() {
86  // Widevine is always supported in Android.
87  key_system_uuid_map_[kWidevineKeySystem] =
88      UUID(kWidevineUuid, kWidevineUuid + arraysize(kWidevineUuid));
89}
90
91UUID KeySystemUuidManager::GetUUID(const std::string& key_system) {
92  KeySystemUuidMap::iterator it = key_system_uuid_map_.find(key_system);
93  if (it == key_system_uuid_map_.end())
94    return UUID();
95  return it->second;
96}
97
98void KeySystemUuidManager::AddMapping(const std::string& key_system,
99                                      const UUID& uuid) {
100  KeySystemUuidMap::iterator it = key_system_uuid_map_.find(key_system);
101  DCHECK(it == key_system_uuid_map_.end())
102      << "Shouldn't overwrite an existing key system.";
103  if (it != key_system_uuid_map_.end())
104    return;
105  key_system_uuid_map_[key_system] = uuid;
106}
107
108base::LazyInstance<KeySystemUuidManager>::Leaky g_key_system_uuid_manager =
109    LAZY_INSTANCE_INITIALIZER;
110
111// Tries to find a PSSH box whose "SystemId" is |uuid| in |data|, parses the
112// "Data" of the box and put it in |pssh_data|. Returns true if such a box is
113// found and successfully parsed. Returns false otherwise.
114// Notes:
115// 1, If multiple PSSH boxes are found,the "Data" of the first matching PSSH box
116// will be set in |pssh_data|.
117// 2, Only PSSH and TENC boxes are allowed in |data|. TENC boxes are skipped.
118static bool GetPsshData(const uint8* data, int data_size,
119                        const UUID& uuid,
120                        std::vector<uint8>* pssh_data) {
121  const uint8* cur = data;
122  const uint8* data_end = data + data_size;
123  int bytes_left = data_size;
124
125  while (bytes_left > 0) {
126    const uint8* box_head = cur;
127
128    if (bytes_left < kBoxHeaderSize)
129      return false;
130
131    uint64_t box_size = ReadUint32(cur);
132    uint32 type = ReadUint32(cur + 4);
133    cur += kBoxHeaderSize;
134    bytes_left -= kBoxHeaderSize;
135
136    if (box_size == 1) {  // LargeSize is present.
137      if (bytes_left < kBoxLargeSizeSize)
138        return false;
139
140      box_size = ReadUint64(cur);
141      cur += kBoxLargeSizeSize;
142      bytes_left -= kBoxLargeSizeSize;
143    } else if (box_size == 0) {
144      box_size = bytes_left + kBoxHeaderSize;
145    }
146
147    const uint8* box_end = box_head + box_size;
148    if (data_end < box_end)
149      return false;
150
151    if (type == kTencType) {
152      // Skip 'tenc' box.
153      cur = box_end;
154      bytes_left = data_end - cur;
155      continue;
156    } else if (type != kPsshType) {
157      return false;
158    }
159
160    const int kPsshBoxMinimumSize =
161        kPsshVersionFlagSize + kPsshSystemIdSize + kPsshDataSizeSize;
162    if (box_end < cur + kPsshBoxMinimumSize)
163      return false;
164
165    uint32 version_and_flags = ReadUint32(cur);
166    cur += kPsshVersionFlagSize;
167    bytes_left -= kPsshVersionFlagSize;
168    if (version_and_flags != 0)
169      return false;
170
171    DCHECK_GE(bytes_left, kPsshSystemIdSize);
172    if (!std::equal(uuid.begin(), uuid.end(), cur)) {
173      cur = box_end;
174      bytes_left = data_end - cur;
175      continue;
176    }
177
178    cur += kPsshSystemIdSize;
179    bytes_left -= kPsshSystemIdSize;
180
181    uint32 data_size = ReadUint32(cur);
182    cur += kPsshDataSizeSize;
183    bytes_left -= kPsshDataSizeSize;
184
185    if (box_end < cur + data_size)
186      return false;
187
188    pssh_data->assign(cur, cur + data_size);
189    return true;
190  }
191
192  return false;
193}
194
195static MediaDrmBridge::SecurityLevel GetSecurityLevelFromString(
196    const std::string& security_level_str) {
197  if (0 == security_level_str.compare("L1"))
198    return MediaDrmBridge::SECURITY_LEVEL_1;
199  if (0 == security_level_str.compare("L3"))
200    return MediaDrmBridge::SECURITY_LEVEL_3;
201  DCHECK(security_level_str.empty());
202  return MediaDrmBridge::SECURITY_LEVEL_NONE;
203}
204
205static std::string GetSecurityLevelString(
206    MediaDrmBridge::SecurityLevel security_level) {
207  switch (security_level) {
208    case MediaDrmBridge::SECURITY_LEVEL_NONE:
209      return "";
210    case MediaDrmBridge::SECURITY_LEVEL_1:
211      return "L1";
212    case MediaDrmBridge::SECURITY_LEVEL_3:
213      return "L3";
214  }
215  return "";
216}
217
218// Checks whether |key_system| is supported with |container_mime_type|. Only
219// checks |key_system| support if |container_mime_type| is empty.
220// TODO(xhwang): The |container_mime_type| is not the same as contentType in
221// the EME spec. Revisit this once the spec issue with initData type is
222// resolved.
223static bool IsKeySystemSupportedWithTypeImpl(
224    const std::string& key_system,
225    const std::string& container_mime_type) {
226  if (!MediaDrmBridge::IsAvailable())
227    return false;
228
229  UUID scheme_uuid = g_key_system_uuid_manager.Get().GetUUID(key_system);
230  if (scheme_uuid.empty())
231    return false;
232
233  JNIEnv* env = AttachCurrentThread();
234  ScopedJavaLocalRef<jbyteArray> j_scheme_uuid =
235      base::android::ToJavaByteArray(env, &scheme_uuid[0], scheme_uuid.size());
236  ScopedJavaLocalRef<jstring> j_container_mime_type =
237      ConvertUTF8ToJavaString(env, container_mime_type);
238  return Java_MediaDrmBridge_isCryptoSchemeSupported(
239      env, j_scheme_uuid.obj(), j_container_mime_type.obj());
240}
241
242// static
243bool MediaDrmBridge::IsAvailable() {
244  return base::android::BuildInfo::GetInstance()->sdk_int() >= 19;
245}
246
247// static
248bool MediaDrmBridge::IsSecureDecoderRequired(SecurityLevel security_level) {
249  DCHECK(IsAvailable());
250  return SECURITY_LEVEL_1 == security_level;
251}
252
253// static
254bool MediaDrmBridge::IsSecurityLevelSupported(const std::string& key_system,
255                                              SecurityLevel security_level) {
256  if (!IsAvailable())
257    return false;
258
259  scoped_ptr<MediaDrmBridge> media_drm_bridge =
260      MediaDrmBridge::CreateSessionless(key_system);
261  if (!media_drm_bridge)
262    return false;
263
264  return media_drm_bridge->SetSecurityLevel(security_level);
265}
266
267//static
268void MediaDrmBridge::AddKeySystemUuidMapping(const std::string& key_system,
269                                             const std::vector<uint8>& uuid) {
270  g_key_system_uuid_manager.Get().AddMapping(key_system, uuid);
271}
272
273// static
274bool MediaDrmBridge::IsKeySystemSupported(const std::string& key_system) {
275  DCHECK(!key_system.empty());
276  return IsKeySystemSupportedWithTypeImpl(key_system, "");
277}
278
279// static
280bool MediaDrmBridge::IsKeySystemSupportedWithType(
281    const std::string& key_system,
282    const std::string& container_mime_type) {
283  DCHECK(!key_system.empty() && !container_mime_type.empty());
284  return IsKeySystemSupportedWithTypeImpl(key_system, container_mime_type);
285}
286
287bool MediaDrmBridge::RegisterMediaDrmBridge(JNIEnv* env) {
288  return RegisterNativesImpl(env);
289}
290
291MediaDrmBridge::MediaDrmBridge(const std::vector<uint8>& scheme_uuid,
292                               const SessionCreatedCB& session_created_cb,
293                               const SessionMessageCB& session_message_cb,
294                               const SessionReadyCB& session_ready_cb,
295                               const SessionClosedCB& session_closed_cb,
296                               const SessionErrorCB& session_error_cb)
297    : scheme_uuid_(scheme_uuid),
298      session_created_cb_(session_created_cb),
299      session_message_cb_(session_message_cb),
300      session_ready_cb_(session_ready_cb),
301      session_closed_cb_(session_closed_cb),
302      session_error_cb_(session_error_cb) {
303  JNIEnv* env = AttachCurrentThread();
304  CHECK(env);
305
306  ScopedJavaLocalRef<jbyteArray> j_scheme_uuid =
307      base::android::ToJavaByteArray(env, &scheme_uuid[0], scheme_uuid.size());
308  j_media_drm_.Reset(Java_MediaDrmBridge_create(
309      env, j_scheme_uuid.obj(), reinterpret_cast<intptr_t>(this)));
310}
311
312MediaDrmBridge::~MediaDrmBridge() {
313  JNIEnv* env = AttachCurrentThread();
314  player_tracker_.NotifyCdmUnset();
315  if (!j_media_drm_.is_null())
316    Java_MediaDrmBridge_release(env, j_media_drm_.obj());
317}
318
319// static
320scoped_ptr<MediaDrmBridge> MediaDrmBridge::Create(
321    const std::string& key_system,
322    const SessionCreatedCB& session_created_cb,
323    const SessionMessageCB& session_message_cb,
324    const SessionReadyCB& session_ready_cb,
325    const SessionClosedCB& session_closed_cb,
326    const SessionErrorCB& session_error_cb) {
327  scoped_ptr<MediaDrmBridge> media_drm_bridge;
328  if (!IsAvailable())
329    return media_drm_bridge.Pass();
330
331  UUID scheme_uuid = g_key_system_uuid_manager.Get().GetUUID(key_system);
332  if (scheme_uuid.empty())
333    return media_drm_bridge.Pass();
334
335  media_drm_bridge.reset(new MediaDrmBridge(scheme_uuid,
336                                            session_created_cb,
337                                            session_message_cb,
338                                            session_ready_cb,
339                                            session_closed_cb,
340                                            session_error_cb));
341
342  if (media_drm_bridge->j_media_drm_.is_null())
343    media_drm_bridge.reset();
344
345  return media_drm_bridge.Pass();
346}
347
348// static
349scoped_ptr<MediaDrmBridge> MediaDrmBridge::CreateSessionless(
350    const std::string& key_system) {
351  return MediaDrmBridge::Create(key_system,
352                                SessionCreatedCB(),
353                                SessionMessageCB(),
354                                SessionReadyCB(),
355                                SessionClosedCB(),
356                                SessionErrorCB());
357}
358
359bool MediaDrmBridge::SetSecurityLevel(SecurityLevel security_level) {
360  JNIEnv* env = AttachCurrentThread();
361
362  std::string security_level_str = GetSecurityLevelString(security_level);
363  if (security_level_str.empty())
364    return false;
365
366  ScopedJavaLocalRef<jstring> j_security_level =
367      ConvertUTF8ToJavaString(env, security_level_str);
368  return Java_MediaDrmBridge_setSecurityLevel(
369      env, j_media_drm_.obj(), j_security_level.obj());
370}
371
372bool MediaDrmBridge::CreateSession(uint32 session_id,
373                                   const std::string& content_type,
374                                   const uint8* init_data,
375                                   int init_data_length) {
376  DVLOG(1) << __FUNCTION__;
377
378  DCHECK(!session_created_cb_.is_null())
379      << "CreateSession called on a sessionless MediaDrmBridge object.";
380
381  JNIEnv* env = AttachCurrentThread();
382  ScopedJavaLocalRef<jbyteArray> j_init_data;
383  // Caller should always use "video/*" content types.
384  DCHECK_EQ(0u, content_type.find("video/"));
385
386  // Widevine MediaDrm plugin only accepts the "data" part of the PSSH box as
387  // the init data when using MP4 container.
388  if (std::equal(scheme_uuid_.begin(), scheme_uuid_.end(), kWidevineUuid) &&
389      content_type == "video/mp4") {
390    std::vector<uint8> pssh_data;
391    if (!GetPsshData(init_data, init_data_length, scheme_uuid_, &pssh_data))
392      return false;
393    j_init_data =
394        base::android::ToJavaByteArray(env, &pssh_data[0], pssh_data.size());
395  } else {
396    j_init_data =
397        base::android::ToJavaByteArray(env, init_data, init_data_length);
398  }
399
400  ScopedJavaLocalRef<jstring> j_mime =
401      ConvertUTF8ToJavaString(env, content_type);
402  Java_MediaDrmBridge_createSession(
403      env, j_media_drm_.obj(), session_id, j_init_data.obj(), j_mime.obj());
404  return true;
405}
406
407void MediaDrmBridge::LoadSession(uint32 session_id,
408                                 const std::string& web_session_id) {
409  // MediaDrmBridge doesn't support loading sessions.
410  NOTREACHED();
411}
412
413void MediaDrmBridge::UpdateSession(uint32 session_id,
414                                   const uint8* response,
415                                   int response_length) {
416  DVLOG(1) << __FUNCTION__;
417
418  DCHECK(!session_ready_cb_.is_null())
419      << __FUNCTION__ << " called on a sessionless MediaDrmBridge object.";
420
421  JNIEnv* env = AttachCurrentThread();
422  ScopedJavaLocalRef<jbyteArray> j_response =
423      base::android::ToJavaByteArray(env, response, response_length);
424  Java_MediaDrmBridge_updateSession(
425      env, j_media_drm_.obj(), session_id, j_response.obj());
426
427  // TODO(xhwang/jrummell): Move this when usableKeyIds/keyschange are
428  // implemented.
429  player_tracker_.NotifyNewKey();
430}
431
432void MediaDrmBridge::ReleaseSession(uint32 session_id) {
433  DVLOG(1) << __FUNCTION__;
434
435  DCHECK(!session_closed_cb_.is_null())
436      << __FUNCTION__ << " called on a sessionless MediaDrmBridge object.";
437
438  JNIEnv* env = AttachCurrentThread();
439  Java_MediaDrmBridge_releaseSession(env, j_media_drm_.obj(), session_id);
440}
441
442int MediaDrmBridge::RegisterPlayer(const base::Closure& new_key_cb,
443                                   const base::Closure& cdm_unset_cb) {
444  return player_tracker_.RegisterPlayer(new_key_cb, cdm_unset_cb);
445}
446
447void MediaDrmBridge::UnregisterPlayer(int registration_id) {
448  player_tracker_.UnregisterPlayer(registration_id);
449}
450
451void MediaDrmBridge::SetMediaCryptoReadyCB(const base::Closure& closure) {
452  if (closure.is_null()) {
453    media_crypto_ready_cb_.Reset();
454    return;
455  }
456
457  DCHECK(media_crypto_ready_cb_.is_null());
458
459  if (!GetMediaCrypto().is_null()) {
460    base::MessageLoopProxy::current()->PostTask(FROM_HERE, closure);
461    return;
462  }
463
464  media_crypto_ready_cb_ = closure;
465}
466
467void MediaDrmBridge::OnMediaCryptoReady(JNIEnv* env, jobject) {
468  DCHECK(!GetMediaCrypto().is_null());
469  if (!media_crypto_ready_cb_.is_null())
470    base::ResetAndReturn(&media_crypto_ready_cb_).Run();
471}
472
473void MediaDrmBridge::OnSessionCreated(JNIEnv* env,
474                                      jobject j_media_drm,
475                                      jint j_session_id,
476                                      jstring j_web_session_id) {
477  uint32 session_id = j_session_id;
478  std::string web_session_id = ConvertJavaStringToUTF8(env, j_web_session_id);
479  session_created_cb_.Run(session_id, web_session_id);
480}
481
482void MediaDrmBridge::OnSessionMessage(JNIEnv* env,
483                                      jobject j_media_drm,
484                                      jint j_session_id,
485                                      jbyteArray j_message,
486                                      jstring j_destination_url) {
487  uint32 session_id = j_session_id;
488  std::vector<uint8> message;
489  JavaByteArrayToByteVector(env, j_message, &message);
490  GURL destination_gurl = GURL(ConvertJavaStringToUTF8(env, j_destination_url));
491  if (!destination_gurl.is_valid() && !destination_gurl.is_empty()) {
492    DLOG(WARNING) << "SessionMessage destination_url is invalid : "
493                  << destination_gurl.possibly_invalid_spec();
494    destination_gurl = GURL::EmptyGURL();  // Replace invalid destination_url.
495  }
496  session_message_cb_.Run(session_id, message, destination_gurl);
497}
498
499void MediaDrmBridge::OnSessionReady(JNIEnv* env,
500                                    jobject j_media_drm,
501                                    jint j_session_id) {
502  uint32 session_id = j_session_id;
503  session_ready_cb_.Run(session_id);
504}
505
506void MediaDrmBridge::OnSessionClosed(JNIEnv* env,
507                                     jobject j_media_drm,
508                                     jint j_session_id) {
509  uint32 session_id = j_session_id;
510  session_closed_cb_.Run(session_id);
511}
512
513void MediaDrmBridge::OnSessionError(JNIEnv* env,
514                                    jobject j_media_drm,
515                                    jint j_session_id) {
516  uint32 session_id = j_session_id;
517  session_error_cb_.Run(session_id, MediaKeys::kUnknownError, 0);
518}
519
520ScopedJavaLocalRef<jobject> MediaDrmBridge::GetMediaCrypto() {
521  JNIEnv* env = AttachCurrentThread();
522  return Java_MediaDrmBridge_getMediaCrypto(env, j_media_drm_.obj());
523}
524
525MediaDrmBridge::SecurityLevel MediaDrmBridge::GetSecurityLevel() {
526  JNIEnv* env = AttachCurrentThread();
527  ScopedJavaLocalRef<jstring> j_security_level =
528      Java_MediaDrmBridge_getSecurityLevel(env, j_media_drm_.obj());
529  std::string security_level_str =
530      ConvertJavaStringToUTF8(env, j_security_level.obj());
531  return GetSecurityLevelFromString(security_level_str);
532}
533
534bool MediaDrmBridge::IsProtectedSurfaceRequired() {
535  return IsSecureDecoderRequired(GetSecurityLevel());
536}
537
538void MediaDrmBridge::ResetDeviceCredentials(
539    const ResetCredentialsCB& callback) {
540  DCHECK(reset_credentials_cb_.is_null());
541  reset_credentials_cb_ = callback;
542  JNIEnv* env = AttachCurrentThread();
543  Java_MediaDrmBridge_resetDeviceCredentials(env, j_media_drm_.obj());
544}
545
546void MediaDrmBridge::OnResetDeviceCredentialsCompleted(
547    JNIEnv* env, jobject, bool success) {
548  base::ResetAndReturn(&reset_credentials_cb_).Run(success);
549}
550
551}  // namespace media
552