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