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