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