media_drm_bridge.cc revision cedac228d2dd51db4b79ea1e72c7f249408ee061
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 if (!j_media_drm_.is_null()) 315 Java_MediaDrmBridge_release(env, j_media_drm_.obj()); 316} 317 318// static 319scoped_ptr<MediaDrmBridge> MediaDrmBridge::Create( 320 const std::string& key_system, 321 const SessionCreatedCB& session_created_cb, 322 const SessionMessageCB& session_message_cb, 323 const SessionReadyCB& session_ready_cb, 324 const SessionClosedCB& session_closed_cb, 325 const SessionErrorCB& session_error_cb) { 326 scoped_ptr<MediaDrmBridge> media_drm_bridge; 327 if (!IsAvailable()) 328 return media_drm_bridge.Pass(); 329 330 UUID scheme_uuid = g_key_system_uuid_manager.Get().GetUUID(key_system); 331 if (scheme_uuid.empty()) 332 return media_drm_bridge.Pass(); 333 334 media_drm_bridge.reset(new MediaDrmBridge(scheme_uuid, 335 session_created_cb, 336 session_message_cb, 337 session_ready_cb, 338 session_closed_cb, 339 session_error_cb)); 340 341 if (media_drm_bridge->j_media_drm_.is_null()) 342 media_drm_bridge.reset(); 343 344 return media_drm_bridge.Pass(); 345} 346 347// static 348scoped_ptr<MediaDrmBridge> MediaDrmBridge::CreateSessionless( 349 const std::string& key_system) { 350 return MediaDrmBridge::Create(key_system, 351 SessionCreatedCB(), 352 SessionMessageCB(), 353 SessionReadyCB(), 354 SessionClosedCB(), 355 SessionErrorCB()); 356} 357 358bool MediaDrmBridge::SetSecurityLevel(SecurityLevel security_level) { 359 JNIEnv* env = AttachCurrentThread(); 360 361 std::string security_level_str = GetSecurityLevelString(security_level); 362 if (security_level_str.empty()) 363 return false; 364 365 ScopedJavaLocalRef<jstring> j_security_level = 366 ConvertUTF8ToJavaString(env, security_level_str); 367 return Java_MediaDrmBridge_setSecurityLevel( 368 env, j_media_drm_.obj(), j_security_level.obj()); 369} 370 371bool MediaDrmBridge::CreateSession(uint32 session_id, 372 const std::string& content_type, 373 const uint8* init_data, 374 int init_data_length) { 375 DVLOG(1) << __FUNCTION__; 376 377 DCHECK(!session_created_cb_.is_null()) 378 << "CreateSession called on a sessionless MediaDrmBridge object."; 379 380 JNIEnv* env = AttachCurrentThread(); 381 ScopedJavaLocalRef<jbyteArray> j_init_data; 382 // Caller should always use "video/*" content types. 383 DCHECK_EQ(0u, content_type.find("video/")); 384 385 // Widevine MediaDrm plugin only accepts the "data" part of the PSSH box as 386 // the init data when using MP4 container. 387 if (std::equal(scheme_uuid_.begin(), scheme_uuid_.end(), kWidevineUuid) && 388 content_type == "video/mp4") { 389 std::vector<uint8> pssh_data; 390 if (!GetPsshData(init_data, init_data_length, scheme_uuid_, &pssh_data)) 391 return false; 392 j_init_data = 393 base::android::ToJavaByteArray(env, &pssh_data[0], pssh_data.size()); 394 } else { 395 j_init_data = 396 base::android::ToJavaByteArray(env, init_data, init_data_length); 397 } 398 399 ScopedJavaLocalRef<jstring> j_mime = 400 ConvertUTF8ToJavaString(env, content_type); 401 Java_MediaDrmBridge_createSession( 402 env, j_media_drm_.obj(), session_id, j_init_data.obj(), j_mime.obj()); 403 return true; 404} 405 406void MediaDrmBridge::LoadSession(uint32 session_id, 407 const std::string& web_session_id) { 408 // MediaDrmBridge doesn't support loading sessions. 409 NOTREACHED(); 410} 411 412void MediaDrmBridge::UpdateSession(uint32 session_id, 413 const uint8* response, 414 int response_length) { 415 DVLOG(1) << __FUNCTION__; 416 417 DCHECK(!session_ready_cb_.is_null()) 418 << __FUNCTION__ << " called on a sessionless MediaDrmBridge object."; 419 420 JNIEnv* env = AttachCurrentThread(); 421 ScopedJavaLocalRef<jbyteArray> j_response = 422 base::android::ToJavaByteArray(env, response, response_length); 423 Java_MediaDrmBridge_updateSession( 424 env, j_media_drm_.obj(), session_id, j_response.obj()); 425} 426 427void MediaDrmBridge::ReleaseSession(uint32 session_id) { 428 DVLOG(1) << __FUNCTION__; 429 430 DCHECK(!session_closed_cb_.is_null()) 431 << __FUNCTION__ << " called on a sessionless MediaDrmBridge object."; 432 433 JNIEnv* env = AttachCurrentThread(); 434 Java_MediaDrmBridge_releaseSession(env, j_media_drm_.obj(), session_id); 435} 436 437void MediaDrmBridge::SetMediaCryptoReadyCB(const base::Closure& closure) { 438 if (closure.is_null()) { 439 media_crypto_ready_cb_.Reset(); 440 return; 441 } 442 443 DCHECK(media_crypto_ready_cb_.is_null()); 444 445 if (!GetMediaCrypto().is_null()) { 446 base::MessageLoopProxy::current()->PostTask(FROM_HERE, closure); 447 return; 448 } 449 450 media_crypto_ready_cb_ = closure; 451} 452 453void MediaDrmBridge::OnMediaCryptoReady(JNIEnv* env, jobject) { 454 DCHECK(!GetMediaCrypto().is_null()); 455 if (!media_crypto_ready_cb_.is_null()) 456 base::ResetAndReturn(&media_crypto_ready_cb_).Run(); 457} 458 459void MediaDrmBridge::OnSessionCreated(JNIEnv* env, 460 jobject j_media_drm, 461 jint j_session_id, 462 jstring j_web_session_id) { 463 uint32 session_id = j_session_id; 464 std::string web_session_id = ConvertJavaStringToUTF8(env, j_web_session_id); 465 session_created_cb_.Run(session_id, web_session_id); 466} 467 468void MediaDrmBridge::OnSessionMessage(JNIEnv* env, 469 jobject j_media_drm, 470 jint j_session_id, 471 jbyteArray j_message, 472 jstring j_destination_url) { 473 uint32 session_id = j_session_id; 474 std::vector<uint8> message; 475 JavaByteArrayToByteVector(env, j_message, &message); 476 GURL destination_gurl = GURL(ConvertJavaStringToUTF8(env, j_destination_url)); 477 if (!destination_gurl.is_valid() && !destination_gurl.is_empty()) { 478 DLOG(WARNING) << "SessionMessage destination_url is invalid : " 479 << destination_gurl.possibly_invalid_spec(); 480 destination_gurl = GURL::EmptyGURL(); // Replace invalid destination_url. 481 } 482 session_message_cb_.Run(session_id, message, destination_gurl); 483} 484 485void MediaDrmBridge::OnSessionReady(JNIEnv* env, 486 jobject j_media_drm, 487 jint j_session_id) { 488 uint32 session_id = j_session_id; 489 session_ready_cb_.Run(session_id); 490} 491 492void MediaDrmBridge::OnSessionClosed(JNIEnv* env, 493 jobject j_media_drm, 494 jint j_session_id) { 495 uint32 session_id = j_session_id; 496 session_closed_cb_.Run(session_id); 497} 498 499void MediaDrmBridge::OnSessionError(JNIEnv* env, 500 jobject j_media_drm, 501 jint j_session_id) { 502 uint32 session_id = j_session_id; 503 session_error_cb_.Run(session_id, MediaKeys::kUnknownError, 0); 504} 505 506ScopedJavaLocalRef<jobject> MediaDrmBridge::GetMediaCrypto() { 507 JNIEnv* env = AttachCurrentThread(); 508 return Java_MediaDrmBridge_getMediaCrypto(env, j_media_drm_.obj()); 509} 510 511MediaDrmBridge::SecurityLevel MediaDrmBridge::GetSecurityLevel() { 512 JNIEnv* env = AttachCurrentThread(); 513 ScopedJavaLocalRef<jstring> j_security_level = 514 Java_MediaDrmBridge_getSecurityLevel(env, j_media_drm_.obj()); 515 std::string security_level_str = 516 ConvertJavaStringToUTF8(env, j_security_level.obj()); 517 return GetSecurityLevelFromString(security_level_str); 518} 519 520bool MediaDrmBridge::IsProtectedSurfaceRequired() { 521 return IsSecureDecoderRequired(GetSecurityLevel()); 522} 523 524void MediaDrmBridge::ResetDeviceCredentials( 525 const ResetCredentialsCB& callback) { 526 DCHECK(reset_credentials_cb_.is_null()); 527 reset_credentials_cb_ = callback; 528 JNIEnv* env = AttachCurrentThread(); 529 Java_MediaDrmBridge_resetDeviceCredentials(env, j_media_drm_.obj()); 530} 531 532void MediaDrmBridge::OnResetDeviceCredentialsCompleted( 533 JNIEnv* env, jobject, bool success) { 534 base::ResetAndReturn(&reset_credentials_cb_).Run(success); 535} 536 537} // namespace media 538