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