diff options
author | Primiano Tucci <primiano@google.com> | 2014-09-30 14:45:55 +0100 |
---|---|---|
committer | Primiano Tucci <primiano@google.com> | 2014-09-30 14:45:55 +0100 |
commit | 1320f92c476a1ad9d19dba2a48c72b75566198e9 (patch) | |
tree | ea7f149ccad687b22c18a72b729646568b2d54fb /chromecast | |
parent | 39b78c562f50ad7d5551ee861121f899239525a2 (diff) | |
download | chromium_org-1320f92c476a1ad9d19dba2a48c72b75566198e9.tar.gz |
Merge from Chromium at DEPS revision 267aeeb8d85c
This commit was generated by merge_to_master.py.
Change-Id: Id3aac9713b301fae64408cdaee0888724eeb7c0e
Diffstat (limited to 'chromecast')
140 files changed, 9398 insertions, 194 deletions
diff --git a/chromecast/DEPS b/chromecast/DEPS index 438dbc008d..a10b9578e9 100644 --- a/chromecast/DEPS +++ b/chromecast/DEPS @@ -5,6 +5,7 @@ include_rules = [ "+crypto", "+grit/chromecast_settings.h", "+grit/shell_resources.h", + "+jni", "+net", "+ui", ] diff --git a/chromecast/android/cast_jni_registrar.cc b/chromecast/android/cast_jni_registrar.cc new file mode 100644 index 0000000000..e8bae63a75 --- /dev/null +++ b/chromecast/android/cast_jni_registrar.cc @@ -0,0 +1,29 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chromecast/android/cast_jni_registrar.h" + +#include "base/android/jni_android.h" +#include "base/android/jni_registrar.h" +#include "chromecast/shell/browser/android/cast_window_android.h" +#include "chromecast/shell/browser/android/cast_window_manager.h" + +namespace chromecast { +namespace android { + +namespace { + +static base::android::RegistrationMethod kMethods[] = { + { "CastWindowAndroid", shell::CastWindowAndroid::RegisterJni }, + { "CastWindowManager", shell::RegisterCastWindowManager }, +}; + +} // namespace + +bool RegisterJni(JNIEnv* env) { + return RegisterNativeMethods(env, kMethods, arraysize(kMethods)); +} + +} // namespace android +} // namespace chromecast diff --git a/chromecast/android/cast_jni_registrar.h b/chromecast/android/cast_jni_registrar.h new file mode 100644 index 0000000000..b07316bd98 --- /dev/null +++ b/chromecast/android/cast_jni_registrar.h @@ -0,0 +1,19 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROMECAST_ANDROID_CAST_JNI_REGISTRAR_H_ +#define CHROMECAST_ANDROID_CAST_JNI_REGISTRAR_H_ + +#include <jni.h> + +namespace chromecast { +namespace android { + +// Register all JNI bindings necessary for the Android cast shell. +bool RegisterJni(JNIEnv* env); + +} // namespace android +} // namespace chromecast + +#endif // CHROMECAST_ANDROID_CAST_JNI_REGISTRAR_H_ diff --git a/chromecast/android/chromecast_config_android.cc b/chromecast/android/chromecast_config_android.cc new file mode 100644 index 0000000000..d698b70a18 --- /dev/null +++ b/chromecast/android/chromecast_config_android.cc @@ -0,0 +1,33 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chromecast/android/chromecast_config_android.h" + +namespace chromecast { +namespace android { + +namespace { +base::LazyInstance<ChromecastConfigAndroid> g_instance = + LAZY_INSTANCE_INITIALIZER; +} // namespace + +// static +ChromecastConfigAndroid* ChromecastConfigAndroid::GetInstance() { + return g_instance.Pointer(); +} + +ChromecastConfigAndroid::ChromecastConfigAndroid() { +} + +ChromecastConfigAndroid::~ChromecastConfigAndroid() { +} + +// Registers a handler to be notified when SendUsageStats is changed. +void ChromecastConfigAndroid::SetSendUsageStatsChangedCallback( + const base::Callback<void(bool)>& callback) { + send_usage_stats_changed_callback_ = callback; +} + +} // namespace android +} // namespace chromecast diff --git a/chromecast/android/chromecast_config_android.h b/chromecast/android/chromecast_config_android.h new file mode 100644 index 0000000000..627259e994 --- /dev/null +++ b/chromecast/android/chromecast_config_android.h @@ -0,0 +1,43 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROMECAST_ANDROID_CHROMECAST_CONFIG_ANDROID_H_ +#define CHROMECAST_ANDROID_CHROMECAST_CONFIG_ANDROID_H_ + +#include <jni.h> + +#include "base/callback.h" +#include "base/lazy_instance.h" +#include "base/macros.h" + +namespace chromecast { +namespace android { + +class ChromecastConfigAndroid { + public: + static ChromecastConfigAndroid* GetInstance(); + + // Registers a handler to be notified when SendUsageStats is changed. + void SetSendUsageStatsChangedCallback( + const base::Callback<void(bool)>& callback); + + const base::Callback<void(bool)>& send_usage_stats_changed_callback() const { + return send_usage_stats_changed_callback_; + } + + private: + friend struct base::DefaultLazyInstanceTraits<ChromecastConfigAndroid>; + + ChromecastConfigAndroid(); + ~ChromecastConfigAndroid(); + + base::Callback<void(bool)> send_usage_stats_changed_callback_; + + DISALLOW_COPY_AND_ASSIGN(ChromecastConfigAndroid); +}; + +} // namespace android +} // namespace chromecast + +#endif // CHROMECAST_ANDROID_CHROMECAST_CONFIG_ANDROID_H_ diff --git a/chromecast/android/platform_jni_loader.h b/chromecast/android/platform_jni_loader.h new file mode 100644 index 0000000000..76307461a2 --- /dev/null +++ b/chromecast/android/platform_jni_loader.h @@ -0,0 +1,18 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROMECAST_ANDROID_PLATFORM_JNI_LOADER_H_ +#define CHROMECAST_ANDROID_PLATFORM_JNI_LOADER_H_ + +#include <jni.h> + +namespace chromecast { +namespace android { + +bool PlatformRegisterJni(JNIEnv* env); + +} // namespace android +} // namespace chromecast + +#endif // CHROMECAST_ANDROID_PLATFORM_JNI_LOADER_H_ diff --git a/chromecast/android/platform_jni_loader_stub.cc b/chromecast/android/platform_jni_loader_stub.cc new file mode 100644 index 0000000000..0f8d814b0f --- /dev/null +++ b/chromecast/android/platform_jni_loader_stub.cc @@ -0,0 +1,16 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chromecast/android/platform_jni_loader.h" + +namespace chromecast { +namespace android { + +bool PlatformRegisterJni(JNIEnv* env) { + // Intentional no-op for public build. + return true; +} + +} // namespace android +} // namespace chromecast diff --git a/chromecast/android/src/dummy b/chromecast/android/src/dummy new file mode 100644 index 0000000000..c1069e016a --- /dev/null +++ b/chromecast/android/src/dummy @@ -0,0 +1,2 @@ +Note(gunsch): This file is for the cast_shell_apk target in chromecast.gyp. +See the notes above that target's 'java_in_dir' variable in chromecast.gyp. diff --git a/chromecast/chromecast.gyp b/chromecast/chromecast.gyp index 070ffc73ba..9ba706aa61 100644 --- a/chromecast/chromecast.gyp +++ b/chromecast/chromecast.gyp @@ -26,6 +26,8 @@ 'common/cast_resource_delegate.h', 'common/chromecast_config.cc', 'common/chromecast_config.h', + 'common/chromecast_switches.cc', + 'common/chromecast_switches.h', 'common/pref_names.cc', 'common/pref_names.h', ], @@ -42,6 +44,61 @@ ], }, { + 'target_name': 'cast_metrics', + 'type': '<(component)', + 'dependencies': [ + 'cast_common', + '../components/components.gyp:component_metrics_proto', + '../components/components.gyp:metrics', + '../components/components.gyp:metrics_gpu', + '../components/components.gyp:metrics_net', + '../components/components.gyp:metrics_profiler', + ], + 'sources': [ + 'metrics/cast_metrics_prefs.cc', + 'metrics/cast_metrics_prefs.h', + 'metrics/cast_metrics_service_client.cc', + 'metrics/cast_metrics_service_client.h', + 'metrics/platform_metrics_providers.h', + ], + 'conditions': [ + ['chromecast_branding=="Chrome"', { + 'dependencies': [ + '<(cast_internal_gyp):cast_metrics_internal', + ], + }, { + 'sources': [ + 'metrics/platform_metrics_providers_simple.cc', + ], + }], + ], + }, + { + 'target_name': 'cast_metrics_unittests', + 'type': '<(gtest_target_type)', + 'dependencies': [ + 'cast_metrics', + '../base/base.gyp:base_prefs_test_support', + '../base/base.gyp:run_all_unittests', + '../base/base.gyp:test_support_base', + '../components/components.gyp:component_metrics_proto', + '../testing/gtest.gyp:gtest', + ], + 'sources': [ + 'metrics/cast_metrics_service_client_unittest.cc', + ], + }, # end of target 'cast_metrics_unittests' + { + 'target_name': 'cast_net', + 'type': '<(component)', + 'sources': [ + 'net/network_change_notifier_cast.cc', + 'net/network_change_notifier_cast.h', + 'net/network_change_notifier_factory_cast.cc', + 'net/network_change_notifier_factory_cast.h', + ], + }, + { 'target_name': 'cast_service', 'type': '<(component)', 'dependencies': [ @@ -61,9 +118,18 @@ '../base/base.gyp:base', '../content/content.gyp:content', ], - 'sources': [ - 'service/cast_service_simple.cc', - 'service/cast_service_simple.h', + 'conditions': [ + ['OS=="android"', { + 'sources': [ + 'service/cast_service_android.cc', + 'service/cast_service_android.h', + ], + }, { + 'sources': [ + 'service/cast_service_simple.cc', + 'service/cast_service_simple.h', + ], + }], ], }], ], @@ -94,7 +160,6 @@ '../content/app/resources/content_resources.gyp:content_resources', '../content/app/strings/content_strings.gyp:content_strings', '../content/browser/devtools/devtools_resources.gyp:devtools_resources', - '../content/content_resources.gyp:content_resources', '../net/net.gyp:net_resources', '../third_party/WebKit/public/blink_resources.gyp:blink_resources', '../ui/resources/ui_resources.gyp:ui_resources', @@ -123,36 +188,42 @@ }, ], }, + # This target contains all content-embedder implementation that is + # non-platform-specific. { - 'target_name': 'cast_shell', - 'type': 'executable', + 'target_name': 'cast_shell_common', + 'type': '<(component)', 'dependencies': [ 'cast_common', + 'cast_metrics', 'cast_service', 'cast_shell_pak', 'cast_shell_resources', 'cast_version_header', 'chromecast_locales.gyp:chromecast_locales_pak', 'chromecast_locales.gyp:chromecast_settings', - '../ui/aura/aura.gyp:aura_test_support', + 'media/media.gyp:media_base', + '../components/components.gyp:cdm_renderer', + '../components/components.gyp:component_metrics_proto', '../content/content.gyp:content', '../content/content.gyp:content_app_browser', '../skia/skia.gyp:skia', + '../third_party/WebKit/public/blink.gyp:blink', + '../third_party/widevine/cdm/widevine_cdm.gyp:widevine_cdm_version_h', ], 'sources': [ - 'net/network_change_notifier_cast.cc', - 'net/network_change_notifier_cast.h', - 'net/network_change_notifier_factory_cast.cc', - 'net/network_change_notifier_factory_cast.h', - 'shell/app/cast_main.cc', 'shell/app/cast_main_delegate.cc', 'shell/app/cast_main_delegate.h', 'shell/browser/cast_browser_context.cc', 'shell/browser/cast_browser_context.h', 'shell/browser/cast_browser_main_parts.cc', 'shell/browser/cast_browser_main_parts.h', + 'shell/browser/cast_browser_process.cc', + 'shell/browser/cast_browser_process.h', 'shell/browser/cast_content_browser_client.cc', 'shell/browser/cast_content_browser_client.h', + 'shell/browser/cast_download_manager_delegate.cc', + 'shell/browser/cast_download_manager_delegate.h', 'shell/browser/cast_http_user_agent_settings.cc', 'shell/browser/cast_http_user_agent_settings.h', 'shell/browser/devtools/cast_dev_tools_delegate.cc', @@ -168,20 +239,19 @@ 'shell/common/cast_content_client.h', 'shell/renderer/cast_content_renderer_client.cc', 'shell/renderer/cast_content_renderer_client.h', + 'shell/renderer/key_systems_cast.cc', + 'shell/renderer/key_systems_cast.h', ], 'conditions': [ ['chromecast_branding=="Chrome"', { 'dependencies': [ - 'internal/chromecast_internal.gyp:cast_gfx_internal', 'internal/chromecast_internal.gyp:cast_shell_internal', ], }, { - 'dependencies': [ - '../ui/ozone/ozone.gyp:eglplatform_shim_x11', - ], 'sources': [ 'shell/browser/devtools/remote_debugging_server_simple.cc', 'shell/browser/webui/webui_cast_simple.cc', + 'shell/renderer/key_systems_cast_simple.cc', ], }], ], @@ -209,6 +279,9 @@ 'python', '<(version_py_path)', '-e', 'VERSION_FULL="<(version_full)"', + # Revision is taken from buildbot if available; otherwise, a dev string is used. + '-e', 'CAST_BUILD_REVISION="<!(echo ${BUILD_NUMBER:="local.${USER}"})"', + '-e', 'CAST_IS_DEBUG_BUILD=1 if "<(CONFIGURATION_NAME)" == "Debug" else 0', 'common/version.h.in', '<@(_outputs)', ], @@ -218,5 +291,189 @@ }, ], }, + { + 'target_name': 'cast_tests', + 'type': 'none', + 'dependencies': [ + 'media/media.gyp:cast_media_unittests', + ], + }, ], # end of targets + + # Targets for Android receiver. + 'conditions': [ + ['OS=="android"', { + 'targets': [ + { + 'target_name': 'libcast_shell_android', + 'type': 'shared_library', + 'dependencies': [ + 'cast_common', + 'cast_jni_headers', + 'cast_shell_common', + 'cast_shell_pak', + 'cast_version_header', + '../base/base.gyp:base', + '../content/content.gyp:content_app_browser', + '../content/content.gyp:content', + '../skia/skia.gyp:skia', + '../ui/gfx/gfx.gyp:gfx', + '../ui/gl/gl.gyp:gl', + ], + 'sources': [ + 'android/cast_jni_registrar.cc', + 'android/cast_jni_registrar.h', + 'android/chromecast_config_android.cc', + 'android/chromecast_config_android.h', + 'android/platform_jni_loader.h', + 'shell/app/android/cast_jni_loader.cc', + 'shell/browser/android/cast_window_manager.cc', + 'shell/browser/android/cast_window_manager.h', + 'shell/browser/android/cast_window_android.cc', + 'shell/browser/android/cast_window_android.h', + ], + 'conditions': [ + ['chromecast_branding=="Chrome"', { + 'dependencies': [ + '<(cast_internal_gyp):cast_shell_android_internal' + ], + }, { + 'sources': [ + 'android/platform_jni_loader_stub.cc', + ], + }] + ], + }, # end of target 'libcast_shell_android' + { + 'target_name': 'cast_shell_java', + 'type': 'none', + 'dependencies': [ + '../base/base.gyp:base_java', + '../content/content.gyp:content_java', + '../media/media.gyp:media_java', + '../net/net.gyp:net_java', + '../third_party/android_tools/android_tools.gyp:android_support_v13_javalib', + '../ui/android/ui_android.gyp:ui_java', + ], + 'variables': { + 'has_java_resources': 1, + 'java_in_dir': 'shell/android/apk', + 'resource_dir': 'shell/android/apk/res', + 'R_package': 'org.chromium.chromecast.shell', + }, + 'includes': ['../build/java.gypi'], + }, # end of target 'cast_shell_java' + { + 'target_name': 'cast_shell_apk', + 'type': 'none', + 'dependencies': [ + 'cast_shell_java', + 'libcast_shell_android', + ], + 'variables': { + 'apk_name': 'CastShell', + 'manifest_package_name': 'org.chromium.chromecast.shell', + # Note(gunsch): there are no Java files in the android/ directory. + # Unfortunately, the java_apk.gypi target rigidly insists on having + # a java_in_dir directory, but complains about duplicate classes + # from the common cast_shell_java target (shared with internal APK) + # if the actual Java path is used. + # This will hopefully be removable after the great GN migration. + 'java_in_dir': 'android', + 'android_manifest_path': 'shell/android/apk/AndroidManifest.xml', + 'package_name': 'org.chromium.chromecast.shell', + 'native_lib_target': 'libcast_shell_android', + 'asset_location': '<(PRODUCT_DIR)/assets', + 'additional_input_paths': ['<(PRODUCT_DIR)/assets/cast_shell.pak'], + }, + 'includes': [ '../build/java_apk.gypi' ], + }, + { + 'target_name': 'cast_jni_headers', + 'type': 'none', + 'sources': [ + 'shell/android/apk/src/org/chromium/chromecast/shell/CastWindowAndroid.java', + 'shell/android/apk/src/org/chromium/chromecast/shell/CastWindowManager.java', + ], + 'direct_dependent_settings': { + 'include_dirs': [ + '<(SHARED_INTERMEDIATE_DIR)/chromecast', + ], + }, + 'variables': { + 'jni_gen_package': 'chromecast', + }, + 'includes': [ '../build/jni_generator.gypi' ], + }, + ], # end of targets + }, { # OS != "android" + 'targets': [ + # This target contains all of the primary code of |cast_shell|, except + # for |main|. This allows end-to-end tests using |cast_shell|. + # This also includes all targets that cannot be built on Android. + { + 'target_name': 'cast_shell_core', + 'type': '<(component)', + 'dependencies': [ + 'cast_net', + 'cast_shell_common', + 'media/media.gyp:cast_media', + '../ui/aura/aura.gyp:aura_test_support', + ], + 'conditions': [ + ['chromecast_branding=="Chrome"', { + 'dependencies': [ + 'internal/chromecast_internal.gyp:cast_gfx_internal', + ], + }, { + 'dependencies': [ + '../ui/ozone/ozone.gyp:eglplatform_shim_x11', + ], + }], + ], + }, + { + 'target_name': 'cast_shell', + 'type': 'executable', + 'dependencies': [ + 'cast_shell_core', + ], + 'sources': [ + 'shell/app/cast_main.cc', + ], + }, + { + 'target_name': 'cast_shell_browser_test', + 'type': '<(gtest_target_type)', + 'dependencies': [ + 'cast_shell_test_support', + '../testing/gtest.gyp:gtest', + ], + 'defines': [ + 'HAS_OUT_OF_PROC_TEST_RUNNER', + ], + 'sources': [ + 'shell/browser/test/chromecast_shell_browser_test.cc', + ], + }, + { + 'target_name': 'cast_shell_test_support', + 'type': '<(component)', + 'defines': [ + 'HAS_OUT_OF_PROC_TEST_RUNNER', + ], + 'dependencies': [ + 'cast_shell_core', + '../content/content_shell_and_tests.gyp:content_browser_test_support', + '../testing/gtest.gyp:gtest', + ], + 'sources': [ + 'shell/browser/test/chromecast_browser_test.cc', + 'shell/browser/test/chromecast_browser_test.h', + 'shell/browser/test/chromecast_browser_test_runner.cc', + ], + }, + ], # end of targets + }], + ], # end of conditions } diff --git a/chromecast/common/cast_paths.cc b/chromecast/common/cast_paths.cc index ec485c7e57..d5d471ce97 100644 --- a/chromecast/common/cast_paths.cc +++ b/chromecast/common/cast_paths.cc @@ -5,8 +5,9 @@ #include "chromecast/common/cast_paths.h" #include "base/base_paths.h" -#include "base/file_util.h" #include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/logging.h" #include "base/path_service.h" #include "build/build_config.h" @@ -27,10 +28,18 @@ bool PathProvider(int key, base::FilePath* result) { #endif return true; } +#if defined(OS_ANDROID) + case FILE_CAST_ANDROID_LOG: { + base::FilePath base_dir; + CHECK(PathService::Get(base::DIR_ANDROID_APP_DATA, &base_dir)); + *result = base_dir.AppendASCII("cast_shell.log"); + return true; + } +#endif // defined(OS_ANDROID) case FILE_CAST_CONFIG: { base::FilePath data_dir; #if defined(OS_ANDROID) - CHECK(PathService::Get(base::DIR_ANDROID_APP_DATA, &data_dir); + CHECK(PathService::Get(base::DIR_ANDROID_APP_DATA, &data_dir)); *result = data_dir.Append("cast_shell.conf"); #else CHECK(PathService::Get(DIR_CAST_HOME, &data_dir)); diff --git a/chromecast/common/cast_paths.h b/chromecast/common/cast_paths.h index f228cd185c..5ab156c20f 100644 --- a/chromecast/common/cast_paths.h +++ b/chromecast/common/cast_paths.h @@ -5,6 +5,8 @@ #ifndef CHROMECAST_COMMON_CAST_PATHS_H_ #define CHROMECAST_COMMON_CAST_PATHS_H_ +#include "build/build_config.h" + // This file declares path keys for the chromecast module. These can be used // with the PathService to access various special directories and files. @@ -16,6 +18,9 @@ enum { DIR_CAST_HOME, // Return a modified $HOME which works for both // development use and the actual device. +#if defined(OS_ANDROID) + FILE_CAST_ANDROID_LOG, // Log file location for Android. +#endif // defined(OS_ANDROID) FILE_CAST_CONFIG, // Config/preferences file path. FILE_CAST_PAK, // cast_shell.pak file path. PATH_END diff --git a/chromecast/common/chromecast_config.cc b/chromecast/common/chromecast_config.cc index dfb3866966..af5a591996 100644 --- a/chromecast/common/chromecast_config.cc +++ b/chromecast/common/chromecast_config.cc @@ -7,7 +7,7 @@ #include <string> #include "base/command_line.h" -#include "base/file_util.h" +#include "base/files/file_util.h" #include "base/logging.h" #include "base/path_service.h" #include "base/prefs/json_pref_store.h" @@ -17,6 +17,7 @@ #include "base/strings/string_number_conversions.h" #include "chromecast/common/cast_paths.h" #include "chromecast/common/pref_names.h" +#include "chromecast/metrics/cast_metrics_prefs.h" namespace chromecast { @@ -74,13 +75,15 @@ bool ChromecastConfig::Load(PrefRegistrySimple* registry) { VLOG(1) << "Loading config from " << config_path_.value(); registry->RegisterIntegerPref(prefs::kRemoteDebuggingPort, 0); + metrics::RegisterPrefs(registry); RegisterPlatformPrefs(registry); PersistentPrefStore::PrefReadError prefs_read_error = PersistentPrefStore::PREF_READ_ERROR_NONE; base::PrefServiceFactory prefServiceFactory; - prefServiceFactory.SetUserPrefsFile(config_path_, - JsonPrefStore::GetTaskRunnerForFile(config_path_, worker_pool_)); + scoped_refptr<base::SequencedTaskRunner> task_runner = + JsonPrefStore::GetTaskRunnerForFile(config_path_, worker_pool_.get()); + prefServiceFactory.SetUserPrefsFile(config_path_, task_runner.get()); prefServiceFactory.set_async(false); prefServiceFactory.set_read_error_callback( base::Bind(&UserPrefsLoadError, &prefs_read_error)); @@ -135,4 +138,9 @@ void ChromecastConfig::SetIntValue(const std::string& key, int value) const { } } +bool ChromecastConfig::HasValue(const std::string& key) const { + DCHECK(thread_checker_.CalledOnValidThread()); + return pref_service_->HasPrefPath(key.c_str()); +} + } // namespace chromecast diff --git a/chromecast/common/chromecast_config.h b/chromecast/common/chromecast_config.h index 5cd6a1ae06..23482c6be4 100644 --- a/chromecast/common/chromecast_config.h +++ b/chromecast/common/chromecast_config.h @@ -37,18 +37,21 @@ class ChromecastConfig { // Saves configs into configuration file. void Save() const; - // Returns string value for key, if present. + // Returns string value for |key|, if present. const std::string GetValue(const std::string& key) const; - // Returns integer value for key, if present. + // Returns integer value for |key|, if present. const int GetIntValue(const std::string& key) const; - // Sets new string value for key. + // Sets new string value for |key|. void SetValue(const std::string& key, const std::string& value) const; - // Sets new int value for key. + // Sets new int value for |key|. void SetIntValue(const std::string& key, int value) const; + // Whether or not a value has been set for |key|. + bool HasValue(const std::string& key) const; + scoped_refptr<base::SequencedWorkerPool> worker_pool() const { return worker_pool_; } diff --git a/chromecast/common/chromecast_switches.cc b/chromecast/common/chromecast_switches.cc new file mode 100644 index 0000000000..90b524b4bb --- /dev/null +++ b/chromecast/common/chromecast_switches.cc @@ -0,0 +1,12 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chromecast/common/chromecast_switches.h" + +namespace switches { + +// Override the URL to which metrics logs are sent for debugging. +const char kOverrideMetricsUploadUrl[] = "override-metrics-upload-url"; + +} // namespace switches diff --git a/chromecast/common/chromecast_switches.h b/chromecast/common/chromecast_switches.h new file mode 100644 index 0000000000..73b8a67eb6 --- /dev/null +++ b/chromecast/common/chromecast_switches.h @@ -0,0 +1,17 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROMECAST_COMMON_CHROMECAST_SWITCHES_H_ +#define CHROMECAST_COMMON_CHROMECAST_SWITCHES_H_ + +#include "build/build_config.h" + +namespace switches { + +// Metrics switches +extern const char kOverrideMetricsUploadUrl[]; + +} // namespace switches + +#endif // CHROMECAST_COMMON_CHROMECAST_SWITCHES_H_ diff --git a/chromecast/common/global_descriptors.h b/chromecast/common/global_descriptors.h new file mode 100644 index 0000000000..cbbbb2ce93 --- /dev/null +++ b/chromecast/common/global_descriptors.h @@ -0,0 +1,21 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROMECAST_COMMON_GLOBAL_DESCRIPTORS_H_ +#define CHROMECAST_COMMON_GLOBAL_DESCRIPTORS_H_ + +#include "content/public/common/content_descriptors.h" + +// This is a list of global descriptor keys to be used with the +// base::GlobalDescriptors object (see base/posix/global_descriptors.h) +enum { + // TODO(gunsch): Remove once there's a real value here. Otherwise, non-Android + // build compile fails due to empty enum. + kDummyValue = kContentIPCDescriptorMax + 1, +#if defined(OS_ANDROID) + kAndroidPakDescriptor, +#endif // defined(OS_ANDROID) +}; + +#endif // CHROMECAST_COMMON_GLOBAL_DESCRIPTORS_H_ diff --git a/chromecast/common/version.h.in b/chromecast/common/version.h.in index 38bc38b918..628cb4c217 100644 --- a/chromecast/common/version.h.in +++ b/chromecast/common/version.h.in @@ -8,5 +8,7 @@ #define CHROMECAST_COMMON_VERSION_INFO_H_ #define PRODUCT_VERSION "@VERSION_FULL@" +#define CAST_BUILD_REVISION "@CAST_BUILD_REVISION@" +#define CAST_IS_DEBUG_BUILD @CAST_IS_DEBUG_BUILD@ #endif // CHROMECAST_COMMON_VERSION_INFO_H_ diff --git a/chromecast/media/DEPS b/chromecast/media/DEPS new file mode 100644 index 0000000000..1891d1afe5 --- /dev/null +++ b/chromecast/media/DEPS @@ -0,0 +1,4 @@ +include_rules = [ + "+media/base", + "+media/cdm", +] diff --git a/chromecast/media/base/key_systems_common.cc b/chromecast/media/base/key_systems_common.cc new file mode 100644 index 0000000000..89257192db --- /dev/null +++ b/chromecast/media/base/key_systems_common.cc @@ -0,0 +1,39 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chromecast/media/base/key_systems_common.h" + +#include <cstddef> + +#include "media/cdm/key_system_names.h" + +#include "widevine_cdm_version.h" // In SHARED_INTERMEDIATE_DIR. + +namespace chromecast { +namespace media { + +const char kChromecastPlayreadyKeySystem[] = "com.chromecast.playready"; + +CastKeySystem GetKeySystemByName(const std::string& key_system_name) { +#if defined(WIDEVINE_CDM_AVAILABLE) + if (key_system_name.compare(kWidevineKeySystem) == 0) { + return KEY_SYSTEM_WIDEVINE; + } +#endif // defined(WIDEVINE_CDM_AVAILABLE) + +#if defined(PLAYREADY_CDM_AVAILABLE) + if (key_system_name.compare(kChromecastPlayreadyKeySystem) == 0) { + return KEY_SYSTEM_PLAYREADY; + } +#endif // defined(PLAYREADY_CDM_AVAILABLE) + + if (key_system_name.compare(::media::kClearKey) == 0) { + return KEY_SYSTEM_CLEAR_KEY; + } + + return GetPlatformKeySystemByName(key_system_name); +} + +} // namespace media +} // namespace chromecast diff --git a/chromecast/media/base/key_systems_common.h b/chromecast/media/base/key_systems_common.h new file mode 100644 index 0000000000..9c0906b9f0 --- /dev/null +++ b/chromecast/media/base/key_systems_common.h @@ -0,0 +1,33 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROMECAST_MEDIA_BASE_KEY_SYSTEMS_COMMON_H_ +#define CHROMECAST_MEDIA_BASE_KEY_SYSTEMS_COMMON_H_ + +#include <string> + +namespace chromecast { +namespace media { + +extern const char kChromecastPlayreadyKeySystem[]; + +enum CastKeySystem { + KEY_SYSTEM_NONE = 0, + KEY_SYSTEM_CLEAR_KEY, + KEY_SYSTEM_PLAYREADY, + KEY_SYSTEM_WIDEVINE +}; + +// Translates a key system string into a CastKeySystem, calling into the +// platform for known key systems if needed. +CastKeySystem GetKeySystemByName(const std::string& key_system_name); + +// Translates a platform-specific key system string into a CastKeySystem. +// TODO(gunsch): Remove when prefixed EME is removed. +CastKeySystem GetPlatformKeySystemByName(const std::string& key_system_name); + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_BASE_KEY_SYSTEMS_COMMON_H_ diff --git a/chromecast/media/base/key_systems_common_simple.cc b/chromecast/media/base/key_systems_common_simple.cc new file mode 100644 index 0000000000..e6dbd02890 --- /dev/null +++ b/chromecast/media/base/key_systems_common_simple.cc @@ -0,0 +1,15 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chromecast/media/base/key_systems_common.h" + +namespace chromecast { +namespace media { + +CastKeySystem GetPlatformKeySystemByName(const std::string& key_system_name) { + return KEY_SYSTEM_NONE; +} + +} // namespace media +} // namespace chromecast diff --git a/chromecast/media/cma/base/balanced_media_task_runner_factory.cc b/chromecast/media/cma/base/balanced_media_task_runner_factory.cc new file mode 100644 index 0000000000..93132603cf --- /dev/null +++ b/chromecast/media/cma/base/balanced_media_task_runner_factory.cc @@ -0,0 +1,252 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chromecast/media/cma/base/balanced_media_task_runner_factory.h" + +#include <map> + +#include "base/bind.h" +#include "base/callback_helpers.h" +#include "base/logging.h" +#include "base/single_thread_task_runner.h" +#include "chromecast/media/cma/base/media_task_runner.h" +#include "media/base/buffers.h" + +namespace chromecast { +namespace media { + +// MediaTaskRunnerWithNotification - +// Media task runner which also behaves as a media task runner observer. +class MediaTaskRunnerWithNotification : public MediaTaskRunner { + public: + // Wraps a MediaTaskRunner so that a third party can: + // - be notified when a PostMediaTask is performed on this media task runner. + // |new_task_cb| is invoked in that case. + // - monitor the lifetime of the media task runner, i.e. check when the media + // task runner is not needed anymore. + // |shutdown_cb| is invoked in that case. + MediaTaskRunnerWithNotification( + const scoped_refptr<MediaTaskRunner>& media_task_runner, + const base::Closure& new_task_cb, + const base::Closure& shutdown_cb); + + // MediaTaskRunner implementation. + virtual bool PostMediaTask( + const tracked_objects::Location& from_here, + const base::Closure& task, + base::TimeDelta timestamp) OVERRIDE; + + private: + virtual ~MediaTaskRunnerWithNotification(); + + scoped_refptr<MediaTaskRunner> const media_task_runner_; + + const base::Closure new_task_cb_; + const base::Closure shutdown_cb_; + + DISALLOW_COPY_AND_ASSIGN(MediaTaskRunnerWithNotification); +}; + +MediaTaskRunnerWithNotification::MediaTaskRunnerWithNotification( + const scoped_refptr<MediaTaskRunner>& media_task_runner, + const base::Closure& new_task_cb, + const base::Closure& shutdown_cb) + : media_task_runner_(media_task_runner), + new_task_cb_(new_task_cb), + shutdown_cb_(shutdown_cb) { +} + +MediaTaskRunnerWithNotification::~MediaTaskRunnerWithNotification() { + shutdown_cb_.Run(); +} + +bool MediaTaskRunnerWithNotification::PostMediaTask( + const tracked_objects::Location& from_here, + const base::Closure& task, + base::TimeDelta timestamp) { + bool may_run_in_future = + media_task_runner_->PostMediaTask(from_here, task, timestamp); + if (may_run_in_future) + new_task_cb_.Run(); + return may_run_in_future; +} + + +// BalancedMediaTaskRunner - +// Run media tasks whose timestamp is less or equal to a max timestamp. +// +// Restrictions of BalancedMediaTaskRunner: +// - Can have at most one task in the queue. +// - Tasks should be given by increasing timestamps. +class BalancedMediaTaskRunner + : public MediaTaskRunner { + public: + explicit BalancedMediaTaskRunner( + const scoped_refptr<base::SingleThreadTaskRunner>& task_runner); + + // Schedule tasks whose timestamp is less than or equal to |max_timestamp|. + void ScheduleWork(base::TimeDelta max_timestamp); + + // Return the timestamp of the last media task. + // Return ::media::kNoTimestamp() if no media task has been posted. + base::TimeDelta GetMediaTimestamp() const; + + // MediaTaskRunner implementation. + virtual bool PostMediaTask( + const tracked_objects::Location& from_here, + const base::Closure& task, + base::TimeDelta timestamp) OVERRIDE; + + private: + virtual ~BalancedMediaTaskRunner(); + + scoped_refptr<base::SingleThreadTaskRunner> const task_runner_; + + // Protects the following variables. + mutable base::Lock lock_; + + // Possible pending media task. + tracked_objects::Location from_here_; + base::Closure pending_task_; + + // Timestamp of the last posted task. + // Is initialized to ::media::kNoTimestamp(). + base::TimeDelta last_timestamp_; + + DISALLOW_COPY_AND_ASSIGN(BalancedMediaTaskRunner); +}; + +BalancedMediaTaskRunner::BalancedMediaTaskRunner( + const scoped_refptr<base::SingleThreadTaskRunner>& task_runner) + : task_runner_(task_runner), + last_timestamp_(::media::kNoTimestamp()) { +} + +BalancedMediaTaskRunner::~BalancedMediaTaskRunner() { +} + +void BalancedMediaTaskRunner::ScheduleWork(base::TimeDelta max_media_time) { + base::Closure task; + { + base::AutoLock auto_lock(lock_); + if (pending_task_.is_null()) + return; + + if (last_timestamp_ != ::media::kNoTimestamp() && + last_timestamp_ >= max_media_time) { + return; + } + + task = base::ResetAndReturn(&pending_task_); + } + task_runner_->PostTask(from_here_, task); +} + +base::TimeDelta BalancedMediaTaskRunner::GetMediaTimestamp() const { + base::AutoLock auto_lock(lock_); + return last_timestamp_; +} + +bool BalancedMediaTaskRunner::PostMediaTask( + const tracked_objects::Location& from_here, + const base::Closure& task, + base::TimeDelta timestamp) { + DCHECK(!task.is_null()); + + // Pass through for a task with no timestamp. + if (timestamp == ::media::kNoTimestamp()) { + return task_runner_->PostTask(from_here, task); + } + + base::AutoLock auto_lock(lock_); + + // Timestamps must be in order. + // Any task that does not meet that condition is simply discarded. + if (last_timestamp_ != ::media::kNoTimestamp() && + timestamp < last_timestamp_) { + return false; + } + + // Only support one pending task at a time. + DCHECK(pending_task_.is_null()); + from_here_ = from_here; + pending_task_ = task; + last_timestamp_ = timestamp; + + return true; +} + + +BalancedMediaTaskRunnerFactory::BalancedMediaTaskRunnerFactory( + base::TimeDelta max_delta) + : max_delta_(max_delta) { +} + +BalancedMediaTaskRunnerFactory::~BalancedMediaTaskRunnerFactory() { +} + +scoped_refptr<MediaTaskRunner> +BalancedMediaTaskRunnerFactory::CreateMediaTaskRunner( + const scoped_refptr<base::SingleThreadTaskRunner>& task_runner) { + scoped_refptr<BalancedMediaTaskRunner> media_task_runner( + new BalancedMediaTaskRunner(task_runner)); + scoped_refptr<MediaTaskRunnerWithNotification> media_task_runner_wrapper( + new MediaTaskRunnerWithNotification( + media_task_runner, + base::Bind(&BalancedMediaTaskRunnerFactory::OnNewTask, this), + base::Bind( + &BalancedMediaTaskRunnerFactory::UnregisterMediaTaskRunner, + this, media_task_runner))); + base::AutoLock auto_lock(lock_); + // Note that |media_task_runner| is inserted here and + // not |media_task_runner_wrapper|. Otherwise, we would always have one + // ref on |media_task_runner_wrapper| and would never get the release + // notification. + // When |media_task_runner_wrapper| is going away, + // BalancedMediaTaskRunnerFactory will receive a notification and will in + // turn remove |media_task_runner|. + task_runners_.insert(media_task_runner); + return media_task_runner_wrapper; +} + +void BalancedMediaTaskRunnerFactory::OnNewTask() { + typedef + std::multimap<base::TimeDelta, scoped_refptr<BalancedMediaTaskRunner> > + TaskRunnerMap; + TaskRunnerMap runnable_task_runner; + + base::AutoLock auto_lock(lock_); + + // Get the minimum timestamp among all streams. + for (MediaTaskRunnerSet::const_iterator it = task_runners_.begin(); + it != task_runners_.end(); ++it) { + base::TimeDelta timestamp((*it)->GetMediaTimestamp()); + if (timestamp == ::media::kNoTimestamp()) + continue; + runnable_task_runner.insert( + std::pair<base::TimeDelta, scoped_refptr<BalancedMediaTaskRunner> >( + timestamp, *it)); + } + + // If there is no media task, just returns. + if (runnable_task_runner.empty()) + return; + + // Run tasks which meet the balancing criteria. + base::TimeDelta min_timestamp(runnable_task_runner.begin()->first); + base::TimeDelta max_timestamp = min_timestamp + max_delta_; + for (TaskRunnerMap::iterator it = runnable_task_runner.begin(); + it != runnable_task_runner.end(); ++it) { + (*it).second->ScheduleWork(max_timestamp); + } +} + +void BalancedMediaTaskRunnerFactory::UnregisterMediaTaskRunner( + const scoped_refptr<BalancedMediaTaskRunner>& media_task_runner) { + base::AutoLock auto_lock(lock_); + task_runners_.erase(media_task_runner); +} + +} // namespace media +} // namespace chromecast diff --git a/chromecast/media/cma/base/balanced_media_task_runner_factory.h b/chromecast/media/cma/base/balanced_media_task_runner_factory.h new file mode 100644 index 0000000000..488373d563 --- /dev/null +++ b/chromecast/media/cma/base/balanced_media_task_runner_factory.h @@ -0,0 +1,68 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROMECAST_MEDIA_CMA_BASE_BALANCED_TASK_RUNNER_FACTORY_H_ +#define CHROMECAST_MEDIA_CMA_BASE_BALANCED_TASK_RUNNER_FACTORY_H_ + +#include <set> + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/synchronization/lock.h" +#include "base/time/time.h" + +namespace base { +class SingleThreadTaskRunner; +} + +namespace chromecast { +namespace media { +class BalancedMediaTaskRunner; +class MediaTaskRunner; + +// BalancedMediaTaskRunnerFactory - +// Create media tasks runners that are loosely synchronized between each other. +// For two tasks T1 and T2 with timestamps ts1 and ts2, the scheduler ensures +// T2 is not scheduled before T1 if ts2 > ts1 + |max_delta|. +class BalancedMediaTaskRunnerFactory + : public base::RefCountedThreadSafe<BalancedMediaTaskRunnerFactory> { + public: + explicit BalancedMediaTaskRunnerFactory(base::TimeDelta max_delta); + + // Creates a media task runner using |task_runner| as the underlying + // regular task runner. + // Restriction on the returned media task runner: + // - can only schedule only one media task at a time. + // - timestamps of tasks posted on that task runner must be increasing. + scoped_refptr<MediaTaskRunner> CreateMediaTaskRunner( + const scoped_refptr<base::SingleThreadTaskRunner>& task_runner); + + private: + typedef std::set<scoped_refptr<BalancedMediaTaskRunner> > MediaTaskRunnerSet; + + friend class base::RefCountedThreadSafe<BalancedMediaTaskRunnerFactory>; + virtual ~BalancedMediaTaskRunnerFactory(); + + // Invoked when one of the registered media task runners received a new media + // task. + void OnNewTask(); + + // Unregister a media task runner. + void UnregisterMediaTaskRunner( + const scoped_refptr<BalancedMediaTaskRunner>& media_task_runner); + + // Maximum timestamp deviation between tasks from the registered task runners. + const base::TimeDelta max_delta_; + + // Task runners created by the factory that have not been unregistered yet. + base::Lock lock_; + MediaTaskRunnerSet task_runners_; + + DISALLOW_COPY_AND_ASSIGN(BalancedMediaTaskRunnerFactory); +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_BASE_BALANCED_TASK_RUNNER_FACTORY_H_ diff --git a/chromecast/media/cma/base/balanced_media_task_runner_unittest.cc b/chromecast/media/cma/base/balanced_media_task_runner_unittest.cc new file mode 100644 index 0000000000..e3448f67ca --- /dev/null +++ b/chromecast/media/cma/base/balanced_media_task_runner_unittest.cc @@ -0,0 +1,263 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <list> +#include <vector> + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/threading/thread.h" +#include "base/time/time.h" +#include "chromecast/media/cma/base/balanced_media_task_runner_factory.h" +#include "chromecast/media/cma/base/media_task_runner.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace chromecast { +namespace media { + +namespace { + +struct MediaTaskRunnerTestContext { + MediaTaskRunnerTestContext(); + ~MediaTaskRunnerTestContext(); + + scoped_refptr<MediaTaskRunner> media_task_runner; + + bool is_pending_task; + + std::vector<base::TimeDelta> task_timestamp_list; + + size_t task_index; + base::TimeDelta max_timestamp; +}; + +MediaTaskRunnerTestContext::MediaTaskRunnerTestContext() { +} + +MediaTaskRunnerTestContext::~MediaTaskRunnerTestContext() { +} + +} // namespace + +class BalancedMediaTaskRunnerTest : public testing::Test { + public: + BalancedMediaTaskRunnerTest(); + virtual ~BalancedMediaTaskRunnerTest(); + + void SetupTest(base::TimeDelta max_delta, + const std::vector<std::vector<int> >& timestamps_in_ms, + const std::vector<size_t>& pattern, + const std::vector<int>& expected_task_timestamps_ms); + void ProcessAllTasks(); + + protected: + // Expected task order based on their timestamps. + std::list<base::TimeDelta> expected_task_timestamps_; + + private: + void ScheduleTask(); + void Task(size_t task_runner_id, base::TimeDelta timestamp); + + void OnTestTimeout(); + + scoped_refptr<BalancedMediaTaskRunnerFactory> media_task_runner_factory_; + + // Schedule first a task on media task runner #scheduling_pattern[0] + // then a task on media task runner #scheduling_pattern[1] and so on. + // Wrap around when reaching the end of the pattern. + std::vector<size_t> scheduling_pattern_; + size_t pattern_index_; + + // For each media task runner, keep a track of which task has already been + // scheduled. + std::vector<MediaTaskRunnerTestContext> contexts_; + + DISALLOW_COPY_AND_ASSIGN(BalancedMediaTaskRunnerTest); +}; + +BalancedMediaTaskRunnerTest::BalancedMediaTaskRunnerTest() { +} + +BalancedMediaTaskRunnerTest::~BalancedMediaTaskRunnerTest() { +} + +void BalancedMediaTaskRunnerTest::SetupTest( + base::TimeDelta max_delta, + const std::vector<std::vector<int> >& timestamps_in_ms, + const std::vector<size_t>& pattern, + const std::vector<int>& expected_task_timestamps_ms) { + media_task_runner_factory_ = new BalancedMediaTaskRunnerFactory(max_delta); + + scheduling_pattern_ = pattern; + pattern_index_ = 0; + + // Setup each task runner. + size_t n = timestamps_in_ms.size(); + contexts_.resize(n); + for (size_t k = 0; k < n; k++) { + contexts_[k].media_task_runner = + media_task_runner_factory_->CreateMediaTaskRunner( + base::MessageLoopProxy::current()); + contexts_[k].is_pending_task = false; + contexts_[k].task_index = 0; + contexts_[k].task_timestamp_list.resize( + timestamps_in_ms[k].size()); + for (size_t i = 0; i < timestamps_in_ms[k].size(); i++) { + contexts_[k].task_timestamp_list[i] = + base::TimeDelta::FromMilliseconds(timestamps_in_ms[k][i]); + } + } + + // Expected task order (for tasks that are actually run). + for (size_t k = 0; k < expected_task_timestamps_ms.size(); k++) { + expected_task_timestamps_.push_back( + base::TimeDelta::FromMilliseconds(expected_task_timestamps_ms[k])); + } +} + +void BalancedMediaTaskRunnerTest::ProcessAllTasks() { + base::MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&BalancedMediaTaskRunnerTest::OnTestTimeout, + base::Unretained(this)), + base::TimeDelta::FromSeconds(5)); + ScheduleTask(); +} + +void BalancedMediaTaskRunnerTest::ScheduleTask() { + bool has_task = false; + for (size_t k = 0; k < contexts_.size(); k++) { + if (contexts_[k].task_index < contexts_[k].task_timestamp_list.size()) + has_task = true; + } + if (!has_task) { + base::MessageLoop::current()->QuitWhenIdle(); + return; + } + + size_t next_pattern_index = + (pattern_index_ + 1) % scheduling_pattern_.size(); + + size_t task_runner_id = scheduling_pattern_[pattern_index_]; + MediaTaskRunnerTestContext& context = contexts_[task_runner_id]; + + // Check whether all tasks have been scheduled for that task runner + // or if there is already one pending task. + if (context.task_index >= context.task_timestamp_list.size() || + context.is_pending_task) { + pattern_index_ = next_pattern_index; + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, + base::Bind(&BalancedMediaTaskRunnerTest::ScheduleTask, + base::Unretained(this))); + return; + } + + bool expected_may_run = false; + if (context.task_timestamp_list[context.task_index] >= + context.max_timestamp) { + expected_may_run = true; + context.max_timestamp = context.task_timestamp_list[context.task_index]; + } + + bool may_run = context.media_task_runner->PostMediaTask( + FROM_HERE, + base::Bind(&BalancedMediaTaskRunnerTest::Task, + base::Unretained(this), + task_runner_id, + context.task_timestamp_list[context.task_index]), + context.task_timestamp_list[context.task_index]); + EXPECT_EQ(may_run, expected_may_run); + + if (may_run) + context.is_pending_task = true; + + context.task_index++; + pattern_index_ = next_pattern_index; + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, + base::Bind(&BalancedMediaTaskRunnerTest::ScheduleTask, + base::Unretained(this))); +} + +void BalancedMediaTaskRunnerTest::Task( + size_t task_runner_id, base::TimeDelta timestamp) { + ASSERT_FALSE(expected_task_timestamps_.empty()); + EXPECT_EQ(timestamp, expected_task_timestamps_.front()); + expected_task_timestamps_.pop_front(); + + contexts_[task_runner_id].is_pending_task = false; +} + +void BalancedMediaTaskRunnerTest::OnTestTimeout() { + ADD_FAILURE() << "Test timed out"; + if (base::MessageLoop::current()) + base::MessageLoop::current()->QuitWhenIdle(); +} + +TEST_F(BalancedMediaTaskRunnerTest, OneTaskRunner) { + scoped_ptr<base::MessageLoop> message_loop(new base::MessageLoop()); + + // Timestamps of tasks for the single task runner. + int timestamps0_ms[] = {0, 10, 20, 30, 40, 30, 50, 60, 20, 30, 70}; + std::vector<std::vector<int> > timestamps_ms(1); + timestamps_ms[0] = std::vector<int>( + timestamps0_ms, timestamps0_ms + arraysize(timestamps0_ms)); + + // Scheduling pattern. + std::vector<size_t> scheduling_pattern(1); + scheduling_pattern[0] = 0; + + // Expected results. + int expected_timestamps[] = {0, 10, 20, 30, 40, 50, 60, 70}; + std::vector<int> expected_timestamps_ms(std::vector<int>( + expected_timestamps, + expected_timestamps + arraysize(expected_timestamps))); + + SetupTest(base::TimeDelta::FromMilliseconds(30), + timestamps_ms, + scheduling_pattern, + expected_timestamps_ms); + ProcessAllTasks(); + message_loop->Run(); + EXPECT_TRUE(expected_task_timestamps_.empty()); +} + +TEST_F(BalancedMediaTaskRunnerTest, TwoTaskRunnerUnbalanced) { + scoped_ptr<base::MessageLoop> message_loop(new base::MessageLoop()); + + // Timestamps of tasks for the 2 task runners. + int timestamps0_ms[] = {0, 10, 20, 30, 40, 30, 50, 60, 20, 30, 70}; + int timestamps1_ms[] = {5, 15, 25, 35, 45, 35, 55, 65, 25, 35, 75}; + std::vector<std::vector<int> > timestamps_ms(2); + timestamps_ms[0] = std::vector<int>( + timestamps0_ms, timestamps0_ms + arraysize(timestamps0_ms)); + timestamps_ms[1] = std::vector<int>( + timestamps1_ms, timestamps1_ms + arraysize(timestamps1_ms)); + + // Scheduling pattern. + size_t pattern[] = {1, 0, 0, 0, 0}; + std::vector<size_t> scheduling_pattern = std::vector<size_t>( + pattern, pattern + arraysize(pattern)); + + // Expected results. + int expected_timestamps[] = { + 5, 0, 10, 20, 30, 15, 40, 25, 50, 35, 60, 45, 70, 55, 65, 75 }; + std::vector<int> expected_timestamps_ms(std::vector<int>( + expected_timestamps, + expected_timestamps + arraysize(expected_timestamps))); + + SetupTest(base::TimeDelta::FromMilliseconds(30), + timestamps_ms, + scheduling_pattern, + expected_timestamps_ms); + ProcessAllTasks(); + message_loop->Run(); + EXPECT_TRUE(expected_task_timestamps_.empty()); +} + +} // namespace media +} // namespace chromecast diff --git a/chromecast/media/cma/base/buffering_controller.cc b/chromecast/media/cma/base/buffering_controller.cc new file mode 100644 index 0000000000..2adaf82053 --- /dev/null +++ b/chromecast/media/cma/base/buffering_controller.cc @@ -0,0 +1,194 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chromecast/media/cma/base/buffering_controller.h" + +#include "base/bind.h" +#include "base/location.h" +#include "base/message_loop/message_loop_proxy.h" +#include "chromecast/media/cma/base/buffering_state.h" +#include "chromecast/media/cma/base/cma_logging.h" +#include "media/base/buffers.h" + +namespace chromecast { +namespace media { + +BufferingController::BufferingController( + const scoped_refptr<BufferingConfig>& config, + const BufferingNotificationCB& buffering_notification_cb) + : config_(config), + buffering_notification_cb_(buffering_notification_cb), + is_buffering_(false), + begin_buffering_time_(base::Time()), + weak_factory_(this) { + weak_this_ = weak_factory_.GetWeakPtr(); + thread_checker_.DetachFromThread(); +} + +BufferingController::~BufferingController() { + // Some weak pointers might possibly be invalidated here. + DCHECK(thread_checker_.CalledOnValidThread()); +} + +void BufferingController::UpdateHighLevelThreshold( + base::TimeDelta high_level_threshold) { + // Can only decrease the high level threshold. + if (high_level_threshold > config_->high_level()) + return; + CMALOG(kLogControl) << "High buffer threshold: " + << high_level_threshold.InMilliseconds(); + config_->set_high_level(high_level_threshold); + + // Make sure the low level threshold is somewhat consistent. + // Currently, we set it to one third of the high level threshold: + // this value could be adjusted in the future. + base::TimeDelta low_level_threshold = high_level_threshold / 3; + if (low_level_threshold <= config_->low_level()) { + CMALOG(kLogControl) << "Low buffer threshold: " + << low_level_threshold.InMilliseconds(); + config_->set_low_level(low_level_threshold); + } + + // Signal all the streams the config has changed. + for (StreamList::iterator it = stream_list_.begin(); + it != stream_list_.end(); ++it) { + (*it)->OnConfigChanged(); + } + + // Once all the streams have been notified, the buffering state must be + // updated (no notification is received from the streams). + OnBufferingStateChanged(false, false); +} + +scoped_refptr<BufferingState> BufferingController::AddStream() { + DCHECK(thread_checker_.CalledOnValidThread()); + + // Add a new stream to the list of streams being monitored. + scoped_refptr<BufferingState> buffering_state(new BufferingState( + config_, + base::Bind(&BufferingController::OnBufferingStateChanged, weak_this_, + false, false), + base::Bind(&BufferingController::UpdateHighLevelThreshold, weak_this_))); + stream_list_.push_back(buffering_state); + + // Update the state and force a notification to the streams. + // TODO(damienv): Should this be a PostTask ? + OnBufferingStateChanged(true, false); + + return buffering_state; +} + +void BufferingController::SetMediaTime(base::TimeDelta time) { + for (StreamList::iterator it = stream_list_.begin(); + it != stream_list_.end(); ++it) { + (*it)->SetMediaTime(time); + } +} + +base::TimeDelta BufferingController::GetMaxRenderingTime() const { + base::TimeDelta max_rendering_time(::media::kNoTimestamp()); + for (StreamList::const_iterator it = stream_list_.begin(); + it != stream_list_.end(); ++it) { + base::TimeDelta max_stream_rendering_time = + (*it)->GetMaxRenderingTime(); + if (max_stream_rendering_time == ::media::kNoTimestamp()) + return ::media::kNoTimestamp(); + if (max_rendering_time == ::media::kNoTimestamp() || + max_stream_rendering_time < max_rendering_time) { + max_rendering_time = max_stream_rendering_time; + } + } + return max_rendering_time; +} + +void BufferingController::Reset() { + DCHECK(thread_checker_.CalledOnValidThread()); + + is_buffering_ = false; + stream_list_.clear(); +} + +void BufferingController::OnBufferingStateChanged( + bool force_notification, bool buffering_timeout) { + DCHECK(thread_checker_.CalledOnValidThread()); + + // Log the state of each stream. + DumpState(); + + bool is_low_buffering = IsLowBufferLevel(); + bool is_high_buffering = !is_low_buffering; + if (!buffering_timeout) { + // Hysteresis: + // - to leave buffering, not only should we leave the low buffer level state + // but we should go to the high buffer level state (medium is not enough). + is_high_buffering = IsHighBufferLevel(); + } + + bool is_buffering_prv = is_buffering_; + if (is_buffering_) { + if (is_high_buffering) + is_buffering_ = false; + } else { + if (is_low_buffering) + is_buffering_ = true; + } + + // Start buffering. + if (is_buffering_ && !is_buffering_prv) { + begin_buffering_time_ = base::Time::Now(); + } + + // End buffering. + if (is_buffering_prv && !is_buffering_) { + // TODO(damienv): |buffering_user_time| could be a UMA histogram. + base::Time current_time = base::Time::Now(); + base::TimeDelta buffering_user_time = current_time - begin_buffering_time_; + CMALOG(kLogControl) + << "Buffering took: " + << buffering_user_time.InMilliseconds() << "ms"; + } + + if (is_buffering_prv != is_buffering_ || force_notification) + buffering_notification_cb_.Run(is_buffering_); +} + +bool BufferingController::IsHighBufferLevel() { + if (stream_list_.empty()) + return true; + + bool is_high_buffering = true; + for (StreamList::iterator it = stream_list_.begin(); + it != stream_list_.end(); ++it) { + BufferingState::State stream_state = (*it)->GetState(); + is_high_buffering = is_high_buffering && + ((stream_state == BufferingState::kHighLevel) || + (stream_state == BufferingState::kEosReached)); + } + return is_high_buffering; +} + +bool BufferingController::IsLowBufferLevel() { + if (stream_list_.empty()) + return false; + + for (StreamList::iterator it = stream_list_.begin(); + it != stream_list_.end(); ++it) { + BufferingState::State stream_state = (*it)->GetState(); + if (stream_state == BufferingState::kLowLevel) + return true; + } + + return false; +} + +void BufferingController::DumpState() const { + CMALOG(kLogControl) << __FUNCTION__; + for (StreamList::const_iterator it = stream_list_.begin(); + it != stream_list_.end(); ++it) { + CMALOG(kLogControl) << (*it)->ToString(); + } +} + +} // namespace media +} // namespace chromecast diff --git a/chromecast/media/cma/base/buffering_controller.h b/chromecast/media/cma/base/buffering_controller.h new file mode 100644 index 0000000000..0581651d83 --- /dev/null +++ b/chromecast/media/cma/base/buffering_controller.h @@ -0,0 +1,108 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROMECAST_MEDIA_CMA_BASE_BUFFERING_CONTROLLER_H +#define CHROMECAST_MEDIA_CMA_BASE_BUFFERING_CONTROLLER_H + +#include <list> + +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/threading/thread_checker.h" +#include "base/time/time.h" + +namespace chromecast { +namespace media { +class BufferingConfig; +class BufferingState; + +class BufferingController { + public: + typedef base::Callback<void(bool)> BufferingNotificationCB; + + // Creates a buffering controller where the conditions to trigger rebuffering + // are given by |config|. The whole point of the buffering controller is to + // derive a single buffering state from the buffering state of various + // streams. + // |buffering_notification_cb| is a callback invoked to inform about possible + // changes of the buffering state. + BufferingController( + const scoped_refptr<BufferingConfig>& config, + const BufferingNotificationCB& buffering_notification_cb); + ~BufferingController(); + + // Creates a buffering state for one stream. This state is added to the list + // of streams monitored by the buffering controller. + scoped_refptr<BufferingState> AddStream(); + + // Sets the playback time. + void SetMediaTime(base::TimeDelta time); + + // Returns the maximum media time available for rendering. + // Return kNoTimestamp() if unknown. + base::TimeDelta GetMaxRenderingTime() const; + + // Returns whether there is an active buffering phase. + bool IsBuffering() const { return is_buffering_; } + + // Resets the buffering controller. This includes removing all the streams + // that were previously added. + void Reset(); + + private: + // Invoked each time the buffering state of one of the streams has changed. + // If |force_notification| is set, |buffering_notification_cb_| is invoked + // regardless whether the buffering state has changed or not. + // If |buffering_timeout| is set, then the condition to leave the buffering + // state is relaxed (we don't want to wait more). + void OnBufferingStateChanged(bool force_notification, + bool buffering_timeout); + + // Updates the high buffer level threshold to |high_level_threshold| + // if needed. + // This condition is triggered when one of the stream reached its maximum + // capacity. In that case, to avoid possible race condition (the buffering + // controller waits for more data to come but the buffer is to small to + // accomodate additional data), the thresholds in |config_| are adjusted + // accordingly. + void UpdateHighLevelThreshold(base::TimeDelta high_level_threshold); + + // Determines the overall buffer level based on the buffer level of each + // stream. + bool IsHighBufferLevel(); + bool IsLowBufferLevel(); + + // Logs the state of the buffering controller. + void DumpState() const; + + base::ThreadChecker thread_checker_; + + // Settings used to determine when to start/stop buffering. + scoped_refptr<BufferingConfig> config_; + + // Callback invoked each time there is a change of the buffering state. + BufferingNotificationCB buffering_notification_cb_; + + // State of the buffering controller. + bool is_buffering_; + + // Start time of a re-buffering phase. + base::Time begin_buffering_time_; + + // Buffering level for each individual stream. + typedef std::list<scoped_refptr<BufferingState> > StreamList; + StreamList stream_list_; + + base::WeakPtrFactory<BufferingController> weak_factory_; + base::WeakPtr<BufferingController> weak_this_; + + DISALLOW_COPY_AND_ASSIGN(BufferingController); +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_BASE_BUFFERING_CONTROLLER_H diff --git a/chromecast/media/cma/base/buffering_controller_unittest.cc b/chromecast/media/cma/base/buffering_controller_unittest.cc new file mode 100644 index 0000000000..75eaed9ce1 --- /dev/null +++ b/chromecast/media/cma/base/buffering_controller_unittest.cc @@ -0,0 +1,133 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/bind.h" +#include "base/callback_helpers.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/time/time.h" +#include "chromecast/media/cma/base/buffering_controller.h" +#include "chromecast/media/cma/base/buffering_state.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace chromecast { +namespace media { + +namespace { + +class MockBufferingControllerClient { + public: + MockBufferingControllerClient(); + ~MockBufferingControllerClient(); + + MOCK_METHOD1(OnBufferingNotification, void(bool is_buffering)); + + private: + DISALLOW_COPY_AND_ASSIGN(MockBufferingControllerClient); +}; + +MockBufferingControllerClient::MockBufferingControllerClient() { +} + +MockBufferingControllerClient::~MockBufferingControllerClient() { +} + +} // namespace + +class BufferingControllerTest : public testing::Test { + public: + BufferingControllerTest(); + virtual ~BufferingControllerTest(); + + protected: + scoped_ptr<BufferingController> buffering_controller_; + + MockBufferingControllerClient client_; + + // Buffer level under the low level threshold. + base::TimeDelta d1_; + + // Buffer level between the low and the high level. + base::TimeDelta d2_; + + // Buffer level above the high level. + base::TimeDelta d3_; + + private: + DISALLOW_COPY_AND_ASSIGN(BufferingControllerTest); +}; + +BufferingControllerTest::BufferingControllerTest() { + base::TimeDelta low_level_threshold( + base::TimeDelta::FromMilliseconds(2000)); + base::TimeDelta high_level_threshold( + base::TimeDelta::FromMilliseconds(6000)); + + d1_ = low_level_threshold - base::TimeDelta::FromMilliseconds(50); + d2_ = (low_level_threshold + high_level_threshold) / 2; + d3_ = high_level_threshold + base::TimeDelta::FromMilliseconds(50); + + scoped_refptr<BufferingConfig> buffering_config( + new BufferingConfig(low_level_threshold, high_level_threshold)); + buffering_controller_.reset(new BufferingController( + buffering_config, + base::Bind(&MockBufferingControllerClient::OnBufferingNotification, + base::Unretained(&client_)))); +} + +BufferingControllerTest::~BufferingControllerTest() { +} + +TEST_F(BufferingControllerTest, OneStream_Typical) { + EXPECT_CALL(client_, OnBufferingNotification(true)).Times(1); + scoped_refptr<BufferingState> buffering_state = + buffering_controller_->AddStream(); + buffering_state->SetMediaTime(base::TimeDelta()); + + // Simulate pre-buffering. + buffering_state->SetBufferedTime(d2_); + EXPECT_EQ(buffering_state->GetState(), BufferingState::kMediumLevel); + + EXPECT_CALL(client_, OnBufferingNotification(false)).Times(1); + buffering_state->SetBufferedTime(d3_); + EXPECT_EQ(buffering_state->GetState(), BufferingState::kHighLevel); + + // Simulate some fluctuations of the buffering level. + buffering_state->SetBufferedTime(d2_); + EXPECT_EQ(buffering_state->GetState(), BufferingState::kMediumLevel); + + // Simulate an underrun. + EXPECT_CALL(client_, OnBufferingNotification(true)).Times(1); + buffering_state->SetBufferedTime(d1_); + EXPECT_EQ(buffering_state->GetState(), BufferingState::kLowLevel); + + EXPECT_CALL(client_, OnBufferingNotification(false)).Times(1); + buffering_state->SetBufferedTime(d3_); + EXPECT_EQ(buffering_state->GetState(), BufferingState::kHighLevel); + + // Simulate the end of stream. + buffering_state->NotifyEos(); + EXPECT_EQ(buffering_state->GetState(), BufferingState::kEosReached); + + buffering_state->SetBufferedTime(d2_); + EXPECT_EQ(buffering_state->GetState(), BufferingState::kEosReached); + + buffering_state->SetBufferedTime(d1_); + EXPECT_EQ(buffering_state->GetState(), BufferingState::kEosReached); +} + +TEST_F(BufferingControllerTest, OneStream_LeaveBufferingOnEos) { + EXPECT_CALL(client_, OnBufferingNotification(true)).Times(1); + scoped_refptr<BufferingState> buffering_state = + buffering_controller_->AddStream(); + buffering_state->SetMediaTime(base::TimeDelta()); + + EXPECT_CALL(client_, OnBufferingNotification(false)).Times(1); + buffering_state->NotifyEos(); + EXPECT_EQ(buffering_state->GetState(), BufferingState::kEosReached); +} + +} // namespace media +} // namespace chromecast diff --git a/chromecast/media/cma/base/buffering_frame_provider.cc b/chromecast/media/cma/base/buffering_frame_provider.cc new file mode 100644 index 0000000000..1a8fec9a12 --- /dev/null +++ b/chromecast/media/cma/base/buffering_frame_provider.cc @@ -0,0 +1,140 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chromecast/media/cma/base/buffering_frame_provider.h" + +#include "base/bind.h" +#include "base/callback_helpers.h" +#include "chromecast/media/cma/base/buffering_state.h" +#include "chromecast/media/cma/base/decoder_buffer_base.h" +#include "media/base/bind_to_current_loop.h" +#include "media/base/buffers.h" + +namespace chromecast { +namespace media { + +BufferingFrameProvider::BufferWithConfig::BufferWithConfig( + const scoped_refptr<DecoderBufferBase>& buffer, + const ::media::AudioDecoderConfig& audio_config, + const ::media::VideoDecoderConfig& video_config) + : buffer_(buffer), + audio_config_(audio_config), + video_config_(video_config) { +} + +BufferingFrameProvider::BufferWithConfig::~BufferWithConfig() { +} + +BufferingFrameProvider::BufferingFrameProvider( + scoped_ptr<CodedFrameProvider> coded_frame_provider, + size_t max_buffer_size, + size_t max_frame_size, + const FrameBufferedCB& frame_buffered_cb) + : coded_frame_provider_(coded_frame_provider.Pass()), + is_pending_request_(false), + is_eos_(false), + total_buffer_size_(0), + max_buffer_size_(max_buffer_size), + max_frame_size_(max_frame_size), + frame_buffered_cb_(frame_buffered_cb), + weak_factory_(this), + weak_this_(weak_factory_.GetWeakPtr()) { + DCHECK_LE(max_frame_size, max_buffer_size); + thread_checker_.DetachFromThread(); +} + +BufferingFrameProvider::~BufferingFrameProvider() { + // Required since some weak pointers might be released in the destructor. + DCHECK(thread_checker_.CalledOnValidThread()); +} + +void BufferingFrameProvider::Read(const ReadCB& read_cb) { + DCHECK(thread_checker_.CalledOnValidThread()); + + DCHECK(!read_cb.is_null()); + read_cb_ = read_cb; + + CompleteReadIfNeeded(); + + RequestBufferIfNeeded(); +} + +void BufferingFrameProvider::Flush(const base::Closure& flush_cb) { + DCHECK(thread_checker_.CalledOnValidThread()); + + // Invalidate all the buffers that belong to this media timeline. + // This is needed since, even though |coded_frame_provider_| is flushed later + // in this function, there might be a pending task holding onto a buffer. + weak_factory_.InvalidateWeakPtrs(); + + // Create a new valid weak pointer that is used for the next media timeline. + weak_this_ = weak_factory_.GetWeakPtr(); + + is_pending_request_ = false; + is_eos_ = false; + buffer_list_.clear(); + total_buffer_size_ = 0; + read_cb_.Reset(); + coded_frame_provider_->Flush(flush_cb); +} + +void BufferingFrameProvider::OnNewBuffer( + const scoped_refptr<DecoderBufferBase>& buffer, + const ::media::AudioDecoderConfig& audio_config, + const ::media::VideoDecoderConfig& video_config) { + is_pending_request_ = false; + buffer_list_.push_back( + BufferWithConfig(buffer, audio_config, video_config)); + + if (buffer->end_of_stream()) { + is_eos_ = true; + } else { + total_buffer_size_ += buffer->data_size(); + } + + if (!frame_buffered_cb_.is_null()) { + // If the next upcoming frame is possibly filling the whole buffer, + // then the buffer is considered as having reached its max capacity. + bool max_capacity_flag = + (total_buffer_size_ + max_frame_size_ >= max_buffer_size_); + frame_buffered_cb_.Run(buffer, max_capacity_flag); + } + + RequestBufferIfNeeded(); + + CompleteReadIfNeeded(); +} + +void BufferingFrameProvider::RequestBufferIfNeeded() { + if (is_pending_request_) + return; + + if (is_eos_ || total_buffer_size_ >= max_buffer_size_) + return; + + is_pending_request_ = true; + coded_frame_provider_->Read(BindToCurrentLoop( + base::Bind(&BufferingFrameProvider::OnNewBuffer, weak_this_))); +} + +void BufferingFrameProvider::CompleteReadIfNeeded() { + if (read_cb_.is_null()) + return; + + if (buffer_list_.empty()) + return; + + BufferWithConfig buffer_with_config(buffer_list_.front()); + buffer_list_.pop_front(); + if (!buffer_with_config.buffer()->end_of_stream()) + total_buffer_size_ -= buffer_with_config.buffer()->data_size(); + + base::ResetAndReturn(&read_cb_).Run( + buffer_with_config.buffer(), + buffer_with_config.audio_config(), + buffer_with_config.video_config()); +} + +} // namespace media +} // namespace chromecast diff --git a/chromecast/media/cma/base/buffering_frame_provider.h b/chromecast/media/cma/base/buffering_frame_provider.h new file mode 100644 index 0000000000..99c863f068 --- /dev/null +++ b/chromecast/media/cma/base/buffering_frame_provider.h @@ -0,0 +1,117 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROMECAST_MEDIA_CMA_BASE_BUFFERING_FRAME_PROVIDER_H_ +#define CHROMECAST_MEDIA_CMA_BASE_BUFFERING_FRAME_PROVIDER_H_ + +#include <list> + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/threading/thread_checker.h" +#include "chromecast/media/cma/base/coded_frame_provider.h" +#include "media/base/audio_decoder_config.h" +#include "media/base/video_decoder_config.h" + +namespace chromecast { +namespace media { +class DecoderBufferBase; + +// BufferingFrameProvider - +// Fetch some data from another CodedFrameProvider up to a certain size limit. +class BufferingFrameProvider : public CodedFrameProvider { + public: + typedef base::Callback<void(const scoped_refptr<DecoderBufferBase>&, bool)> + FrameBufferedCB; + + // Creates a frame provider that buffers coded frames up to the + // |max_buffer_size| limit (given as a number of bytes). + // |max_frame_size| corresponds to an upper bound of the expected frame size. + // Each time a frame is buffered, |frame_buffered_cb| is invoked with the + // last frame buffered. The second parameter of the callback indicates + // whether the maximum capacity has been reached, i.e. whether the next frame + // size might overflow the buffer: |total_buffer_size_| + next_frame_size + // might be greater than |max_buffer_size|. + // Note: takes ownership of |coded_frame_provider|. + BufferingFrameProvider( + scoped_ptr<CodedFrameProvider> coded_frame_provider, + size_t max_buffer_size, + size_t max_frame_size, + const FrameBufferedCB& frame_buffered_cb); + virtual ~BufferingFrameProvider(); + + // CodedFrameProvider implementation. + virtual void Read(const ReadCB& read_cb) OVERRIDE; + virtual void Flush(const base::Closure& flush_cb) OVERRIDE; + + private: + class BufferWithConfig { + public: + BufferWithConfig( + const scoped_refptr<DecoderBufferBase>& buffer, + const ::media::AudioDecoderConfig& audio_config, + const ::media::VideoDecoderConfig& video_config); + ~BufferWithConfig(); + + const scoped_refptr<DecoderBufferBase>& buffer() const { return buffer_; } + const ::media::AudioDecoderConfig& audio_config() const { + return audio_config_; + } + const ::media::VideoDecoderConfig& video_config() const { + return video_config_; + } + + private: + scoped_refptr<DecoderBufferBase> buffer_; + ::media::AudioDecoderConfig audio_config_; + ::media::VideoDecoderConfig video_config_; + }; + + void OnNewBuffer(const scoped_refptr<DecoderBufferBase>& buffer, + const ::media::AudioDecoderConfig& audio_config, + const ::media::VideoDecoderConfig& video_config); + void RequestBufferIfNeeded(); + void CompleteReadIfNeeded(); + + base::ThreadChecker thread_checker_; + + // Frame provider the buffering frame provider fetches data from. + scoped_ptr<CodedFrameProvider> coded_frame_provider_; + + // Indicates whether there is a pending read request on + // |coded_frame_provider_|. + bool is_pending_request_; + + // Indicates whether the end of stream has been reached. + bool is_eos_; + + std::list<BufferWithConfig> buffer_list_; + + // Size in bytes of audio/video buffers in |buffer_list_|. + size_t total_buffer_size_; + + // Max amount of data to buffer. + // i.e. this is the maximum size of buffers in |buffer_list_|. + const size_t max_buffer_size_; + + // Maximum expected frame size. + const size_t max_frame_size_; + + // Callback invoked each time there is a new frame buffered. + FrameBufferedCB frame_buffered_cb_; + + // Pending read callback. + ReadCB read_cb_; + + base::WeakPtrFactory<BufferingFrameProvider> weak_factory_; + base::WeakPtr<BufferingFrameProvider> weak_this_; + + DISALLOW_COPY_AND_ASSIGN(BufferingFrameProvider); +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_BASE_BUFFERING_FRAME_PROVIDER_H_ diff --git a/chromecast/media/cma/base/buffering_frame_provider_unittest.cc b/chromecast/media/cma/base/buffering_frame_provider_unittest.cc new file mode 100644 index 0000000000..6f7902a70b --- /dev/null +++ b/chromecast/media/cma/base/buffering_frame_provider_unittest.cc @@ -0,0 +1,187 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <list> +#include <vector> + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/threading/thread.h" +#include "base/time/time.h" +#include "chromecast/media/cma/base/buffering_frame_provider.h" +#include "chromecast/media/cma/base/decoder_buffer_base.h" +#include "chromecast/media/cma/base/frame_generator_for_test.h" +#include "chromecast/media/cma/base/mock_frame_consumer.h" +#include "chromecast/media/cma/base/mock_frame_provider.h" +#include "media/base/audio_decoder_config.h" +#include "media/base/decoder_buffer.h" +#include "media/base/video_decoder_config.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace chromecast { +namespace media { + +class BufferingFrameProviderTest : public testing::Test { + public: + BufferingFrameProviderTest(); + virtual ~BufferingFrameProviderTest(); + + // Setup the test. + void Configure( + size_t frame_count, + const std::vector<bool>& provider_delayed_pattern, + const std::vector<bool>& consumer_delayed_pattern); + + // Start the test. + void Start(); + + protected: + scoped_ptr<BufferingFrameProvider> buffering_frame_provider_; + scoped_ptr<MockFrameConsumer> frame_consumer_; + + private: + void OnTestTimeout(); + void OnTestCompleted(); + + DISALLOW_COPY_AND_ASSIGN(BufferingFrameProviderTest); +}; + +BufferingFrameProviderTest::BufferingFrameProviderTest() { +} + +BufferingFrameProviderTest::~BufferingFrameProviderTest() { +} + +void BufferingFrameProviderTest::Configure( + size_t frame_count, + const std::vector<bool>& provider_delayed_pattern, + const std::vector<bool>& consumer_delayed_pattern) { + DCHECK_GE(frame_count, 1u); + + // Frame generation on the producer and consumer side. + std::vector<FrameGeneratorForTest::FrameSpec> frame_specs(frame_count); + for (size_t k = 0; k < frame_specs.size() - 1; k++) { + frame_specs[k].has_config = (k == 0); + frame_specs[k].timestamp = base::TimeDelta::FromMilliseconds(40) * k; + frame_specs[k].size = 512; + frame_specs[k].has_decrypt_config = ((k % 3) == 0); + } + frame_specs[frame_specs.size() - 1].is_eos = true; + + scoped_ptr<FrameGeneratorForTest> frame_generator_provider( + new FrameGeneratorForTest(frame_specs)); + scoped_ptr<FrameGeneratorForTest> frame_generator_consumer( + new FrameGeneratorForTest(frame_specs)); + + scoped_ptr<MockFrameProvider> frame_provider(new MockFrameProvider()); + frame_provider->Configure(provider_delayed_pattern, + frame_generator_provider.Pass()); + + size_t max_frame_size = 10 * 1024; + size_t buffer_size = 10 * max_frame_size; + buffering_frame_provider_.reset( + new BufferingFrameProvider( + scoped_ptr<CodedFrameProvider>(frame_provider.release()), + buffer_size, + max_frame_size, + BufferingFrameProvider::FrameBufferedCB())); + + frame_consumer_.reset( + new MockFrameConsumer(buffering_frame_provider_.get())); + frame_consumer_->Configure( + consumer_delayed_pattern, + false, + frame_generator_consumer.Pass()); +} + +void BufferingFrameProviderTest::Start() { + frame_consumer_->Start( + base::Bind(&BufferingFrameProviderTest::OnTestCompleted, + base::Unretained(this))); +} + +void BufferingFrameProviderTest::OnTestTimeout() { + ADD_FAILURE() << "Test timed out"; + if (base::MessageLoop::current()) + base::MessageLoop::current()->QuitWhenIdle(); +} + +void BufferingFrameProviderTest::OnTestCompleted() { + base::MessageLoop::current()->QuitWhenIdle(); +} + +TEST_F(BufferingFrameProviderTest, FastProviderSlowConsumer) { + bool provider_delayed_pattern[] = { false }; + bool consumer_delayed_pattern[] = { true }; + + const size_t frame_count = 100u; + Configure( + frame_count, + std::vector<bool>( + provider_delayed_pattern, + provider_delayed_pattern + arraysize(provider_delayed_pattern)), + std::vector<bool>( + consumer_delayed_pattern, + consumer_delayed_pattern + arraysize(consumer_delayed_pattern))); + + scoped_ptr<base::MessageLoop> message_loop(new base::MessageLoop()); + message_loop->PostTask( + FROM_HERE, + base::Bind(&BufferingFrameProviderTest::Start, base::Unretained(this))); + message_loop->Run(); +}; + +TEST_F(BufferingFrameProviderTest, SlowProviderFastConsumer) { + bool provider_delayed_pattern[] = { true }; + bool consumer_delayed_pattern[] = { false }; + + const size_t frame_count = 100u; + Configure( + frame_count, + std::vector<bool>( + provider_delayed_pattern, + provider_delayed_pattern + arraysize(provider_delayed_pattern)), + std::vector<bool>( + consumer_delayed_pattern, + consumer_delayed_pattern + arraysize(consumer_delayed_pattern))); + + scoped_ptr<base::MessageLoop> message_loop(new base::MessageLoop()); + message_loop->PostTask( + FROM_HERE, + base::Bind(&BufferingFrameProviderTest::Start, base::Unretained(this))); + message_loop->Run(); +}; + +TEST_F(BufferingFrameProviderTest, SlowFastProducerConsumer) { + // Lengths are prime between each other so we can test a lot of combinations. + bool provider_delayed_pattern[] = { + true, true, true, true, true, + false, false, false, false + }; + bool consumer_delayed_pattern[] = { + true, true, true, true, true, true, true, + false, false, false, false, false, false, false + }; + + const size_t frame_count = 100u; + Configure( + frame_count, + std::vector<bool>( + provider_delayed_pattern, + provider_delayed_pattern + arraysize(provider_delayed_pattern)), + std::vector<bool>( + consumer_delayed_pattern, + consumer_delayed_pattern + arraysize(consumer_delayed_pattern))); + + scoped_ptr<base::MessageLoop> message_loop(new base::MessageLoop()); + message_loop->PostTask( + FROM_HERE, + base::Bind(&BufferingFrameProviderTest::Start, base::Unretained(this))); + message_loop->Run(); +}; + +} // namespace media +} // namespace chromecast diff --git a/chromecast/media/cma/base/buffering_state.cc b/chromecast/media/cma/base/buffering_state.cc new file mode 100644 index 0000000000..e1fc49fe18 --- /dev/null +++ b/chromecast/media/cma/base/buffering_state.cc @@ -0,0 +1,129 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chromecast/media/cma/base/buffering_state.h" + +#include <sstream> + +#include "base/logging.h" +#include "media/base/buffers.h" + +namespace chromecast { +namespace media { + +BufferingConfig::BufferingConfig( + base::TimeDelta low_level_threshold, + base::TimeDelta high_level_threshold) + : low_level_threshold_(low_level_threshold), + high_level_threshold_(high_level_threshold) { +} + +BufferingConfig::~BufferingConfig() { +} + + +BufferingState::BufferingState( + const scoped_refptr<BufferingConfig>& config, + const base::Closure& state_changed_cb, + const HighLevelBufferCB& high_level_buffer_cb) + : config_(config), + state_changed_cb_(state_changed_cb), + high_level_buffer_cb_(high_level_buffer_cb), + state_(kLowLevel), + media_time_(::media::kNoTimestamp()), + max_rendering_time_(::media::kNoTimestamp()), + buffered_time_(::media::kNoTimestamp()) { +} + +BufferingState::~BufferingState() { +} + +void BufferingState::OnConfigChanged() { + state_ = GetBufferLevelState(); +} + +void BufferingState::SetMediaTime(base::TimeDelta media_time) { + media_time_ = media_time; + switch (state_) { + case kLowLevel: + case kMediumLevel: + case kHighLevel: + UpdateState(GetBufferLevelState()); + break; + case kEosReached: + break; + } +} + +void BufferingState::SetMaxRenderingTime(base::TimeDelta max_rendering_time) { + max_rendering_time_ = max_rendering_time; +} + +base::TimeDelta BufferingState::GetMaxRenderingTime() const { + return max_rendering_time_; +} + +void BufferingState::SetBufferedTime(base::TimeDelta buffered_time) { + buffered_time_ = buffered_time; + switch (state_) { + case kLowLevel: + case kMediumLevel: + case kHighLevel: + UpdateState(GetBufferLevelState()); + break; + case kEosReached: + break; + } +} + +void BufferingState::NotifyEos() { + UpdateState(kEosReached); +} + +void BufferingState::NotifyMaxCapacity(base::TimeDelta buffered_time) { + if (media_time_ == ::media::kNoTimestamp() || + buffered_time == ::media::kNoTimestamp()) { + LOG(WARNING) << "Max capacity with no timestamp"; + return; + } + base::TimeDelta buffer_duration = buffered_time - media_time_; + if (buffer_duration < config_->high_level()) + high_level_buffer_cb_.Run(buffer_duration); +} + +std::string BufferingState::ToString() const { + std::ostringstream s; + s << "state=" << state_ + << " media_time_ms=" << media_time_.InMilliseconds() + << " buffered_time_ms=" << buffered_time_.InMilliseconds() + << " low_level_ms=" << config_->low_level().InMilliseconds() + << " high_level_ms=" << config_->high_level().InMilliseconds(); + return s.str(); +} + +BufferingState::State BufferingState::GetBufferLevelState() const { + if (media_time_ == ::media::kNoTimestamp() || + buffered_time_ == ::media::kNoTimestamp()) { + return kLowLevel; + } + + base::TimeDelta buffer_duration = buffered_time_ - media_time_; + if (buffer_duration < config_->low_level()) + return kLowLevel; + if (buffer_duration >= config_->high_level()) + return kHighLevel; + return kMediumLevel; +} + +void BufferingState::UpdateState(State new_state) { + if (new_state == state_) + return; + + state_ = new_state; + if (!state_changed_cb_.is_null()) + state_changed_cb_.Run(); +} + +} // namespace media +} // namespace chromecast diff --git a/chromecast/media/cma/base/buffering_state.h b/chromecast/media/cma/base/buffering_state.h new file mode 100644 index 0000000000..ced8206d8a --- /dev/null +++ b/chromecast/media/cma/base/buffering_state.h @@ -0,0 +1,138 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROMECAST_MEDIA_CMA_BASE_BUFFERING_STATE_H_ +#define CHROMECAST_MEDIA_CMA_BASE_BUFFERING_STATE_H_ + +#include <string> + +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/time/time.h" + +namespace chromecast { +namespace media { + +class BufferingConfig : public base::RefCountedThreadSafe<BufferingConfig> { + public: + BufferingConfig(base::TimeDelta low_level_threshold, + base::TimeDelta high_level_threshold); + + base::TimeDelta low_level() const { return low_level_threshold_; } + base::TimeDelta high_level() const { return high_level_threshold_; } + + void set_low_level(base::TimeDelta low_level) { + low_level_threshold_ = low_level; + } + void set_high_level(base::TimeDelta high_level) { + high_level_threshold_ = high_level; + } + + private: + friend class base::RefCountedThreadSafe<BufferingConfig>; + virtual ~BufferingConfig(); + + base::TimeDelta low_level_threshold_; + base::TimeDelta high_level_threshold_; + + DISALLOW_COPY_AND_ASSIGN(BufferingConfig); +}; + +class BufferingState + : public base::RefCountedThreadSafe<BufferingState> { + public: + typedef base::Callback<void(base::TimeDelta)> HighLevelBufferCB; + + enum State { + kLowLevel, + kMediumLevel, + kHighLevel, + kEosReached, + }; + + // Creates a new buffering state. The initial state is |kLowLevel|. + // |state_changed_cb| is used to notify about possible state changes. + // |high_level_buffer_cb| is used to adjust the high buffer threshold + // when the underlying buffer is not large enough to accomodate + // the current high buffer level. + BufferingState(const scoped_refptr<BufferingConfig>& config, + const base::Closure& state_changed_cb, + const HighLevelBufferCB& high_level_buffer_cb); + + // Returns the buffering state. + State GetState() const { return state_; } + + // Invoked when the buffering configuration has changed. + // Based on the new configuration, the buffering state might change. + // However, |state_changed_cb_| is not triggered in that case. + void OnConfigChanged(); + + // Sets the current rendering time for this stream. + void SetMediaTime(base::TimeDelta media_time); + + // Sets/gets the maximum rendering media time for this stream. + // The maximum rendering time is always lower than the buffered time. + void SetMaxRenderingTime(base::TimeDelta max_rendering_time); + base::TimeDelta GetMaxRenderingTime() const; + + // Sets the buffered time. + void SetBufferedTime(base::TimeDelta buffered_time); + + // Notifies the buffering state that all the frames for this stream have been + // buffered, i.e. the end of stream has been reached. + void NotifyEos(); + + // Notifies the buffering state the underlying buffer has reached + // its maximum capacity. + // The maximum frame timestamp in the buffer is given by |buffered_time|. + // Note: this timestamp can be different from the one provided through + // SetBufferedTime since SetBufferedTime takes the timestamp of a playable + // frame which is not necessarily the case here (e.g. missing key id). + void NotifyMaxCapacity(base::TimeDelta buffered_time); + + // Buffering state as a human readable string, for debugging. + std::string ToString() const; + + private: + friend class base::RefCountedThreadSafe<BufferingState>; + virtual ~BufferingState(); + + // Returns the state solely based on the buffered time. + State GetBufferLevelState() const; + + // Updates the state to |new_state|. + void UpdateState(State new_state); + + scoped_refptr<BufferingConfig> const config_; + + // Callback invoked each time there is a change of state. + base::Closure state_changed_cb_; + + // Callback invoked to adjust the high buffer level. + HighLevelBufferCB high_level_buffer_cb_; + + // State. + State state_; + + // Playback media time. + // Equal to kNoTimestamp() when not known. + base::TimeDelta media_time_; + + // Maximum rendering media time. + // This corresponds to the timestamp of the last frame sent to the hardware + // decoder/renderer. + base::TimeDelta max_rendering_time_; + + // Buffered media time. + // Equal to kNoTimestamp() when not known. + base::TimeDelta buffered_time_; + + DISALLOW_COPY_AND_ASSIGN(BufferingState); +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_BASE_BUFFERING_STATE_H_ diff --git a/chromecast/media/cma/base/cma_logging.h b/chromecast/media/cma/base/cma_logging.h new file mode 100644 index 0000000000..1743cea310 --- /dev/null +++ b/chromecast/media/cma/base/cma_logging.h @@ -0,0 +1,23 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROMECAST_MEDIA_CMA_BASE_CMA_LOGGING_H_ +#define CHROMECAST_MEDIA_CMA_BASE_CMA_LOGGING_H_ + +#include "base/logging.h" + +namespace chromecast { +namespace media { + +#define CMALOG(loglevel) VLOG(loglevel) + +enum { + kLogControl = 2, + kLogFrame = 3 +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_BASE_CMA_LOGGING_H_ diff --git a/chromecast/media/cma/base/coded_frame_provider.cc b/chromecast/media/cma/base/coded_frame_provider.cc new file mode 100644 index 0000000000..56e461cfd8 --- /dev/null +++ b/chromecast/media/cma/base/coded_frame_provider.cc @@ -0,0 +1,17 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chromecast/media/cma/base/coded_frame_provider.h" + +namespace chromecast { +namespace media { + +CodedFrameProvider::CodedFrameProvider() { +} + +CodedFrameProvider::~CodedFrameProvider() { +} + +} // namespace media +} // namespace chromecast diff --git a/chromecast/media/cma/base/coded_frame_provider.h b/chromecast/media/cma/base/coded_frame_provider.h new file mode 100644 index 0000000000..b13231d8e5 --- /dev/null +++ b/chromecast/media/cma/base/coded_frame_provider.h @@ -0,0 +1,51 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROMECAST_MEDIA_CMA_BASE_CODED_FRAME_PROVIDER_H_ +#define CHROMECAST_MEDIA_CMA_BASE_CODED_FRAME_PROVIDER_H_ + +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" + +namespace media { +class AudioDecoderConfig; +class VideoDecoderConfig; +} + +namespace chromecast { +namespace media { +class DecoderBufferBase; + +class CodedFrameProvider { + public: + typedef base::Callback<void(const scoped_refptr<DecoderBufferBase>&, + const ::media::AudioDecoderConfig&, + const ::media::VideoDecoderConfig&)> ReadCB; + + CodedFrameProvider(); + virtual ~CodedFrameProvider(); + + // Request a coded frame which is provided asynchronously through callback + // |read_cb|. + // If the frame is associated with a new video/audio configuration, + // these configurations are returned as part of the |read_cb| callback. + // Invoking the |read_cb| callback with invalid audio/video configurations + // means the configurations have not changed. + virtual void Read(const ReadCB& read_cb) = 0; + + // Flush the coded frames held by the frame provider. + // Invoke callback |flush_cb| when completed. + // Note: any pending read is cancelled, meaning that any pending |read_cb| + // callback will not be invoked. + virtual void Flush(const base::Closure& flush_cb) = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(CodedFrameProvider); +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_BASE_CODED_FRAME_PROVIDER_H_ diff --git a/chromecast/media/cma/base/decoder_buffer_adapter.cc b/chromecast/media/cma/base/decoder_buffer_adapter.cc new file mode 100644 index 0000000000..236505b995 --- /dev/null +++ b/chromecast/media/cma/base/decoder_buffer_adapter.cc @@ -0,0 +1,45 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chromecast/media/cma/base/decoder_buffer_adapter.h" + +#include "media/base/decoder_buffer.h" + +namespace chromecast { +namespace media { + +DecoderBufferAdapter::DecoderBufferAdapter( + const scoped_refptr< ::media::DecoderBuffer>& buffer) + : buffer_(buffer) { +} + +DecoderBufferAdapter::~DecoderBufferAdapter() { +} + +base::TimeDelta DecoderBufferAdapter::timestamp() const { + return buffer_->timestamp(); +} + +const uint8* DecoderBufferAdapter::data() const { + return buffer_->data(); +} + +uint8* DecoderBufferAdapter::writable_data() const { + return buffer_->writable_data(); +} + +int DecoderBufferAdapter::data_size() const { + return buffer_->data_size(); +} + +const ::media::DecryptConfig* DecoderBufferAdapter::decrypt_config() const { + return buffer_->decrypt_config(); +} + +bool DecoderBufferAdapter::end_of_stream() const { + return buffer_->end_of_stream(); +} + +} // namespace media +} // namespace chromecast diff --git a/chromecast/media/cma/base/decoder_buffer_adapter.h b/chromecast/media/cma/base/decoder_buffer_adapter.h new file mode 100644 index 0000000000..9fe6da95ab --- /dev/null +++ b/chromecast/media/cma/base/decoder_buffer_adapter.h @@ -0,0 +1,45 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROMECAST_MEDIA_CMA_BASE_DECODER_BUFFER_ADAPTER_H_ +#define CHROMECAST_MEDIA_CMA_BASE_DECODER_BUFFER_ADAPTER_H_ + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "chromecast/media/cma/base/decoder_buffer_base.h" + +namespace media { +class DecoderBuffer; +} + +namespace chromecast { +namespace media { + +// DecoderBufferAdapter wraps a ::media::DecoderBuffer +// into a DecoderBufferBase. +class DecoderBufferAdapter : public DecoderBufferBase { + public: + explicit DecoderBufferAdapter( + const scoped_refptr< ::media::DecoderBuffer>& buffer); + + // DecoderBufferBase implementation. + virtual base::TimeDelta timestamp() const OVERRIDE; + virtual const uint8* data() const OVERRIDE; + virtual uint8* writable_data() const OVERRIDE; + virtual int data_size() const OVERRIDE; + virtual const ::media::DecryptConfig* decrypt_config() const OVERRIDE; + virtual bool end_of_stream() const OVERRIDE; + + private: + virtual ~DecoderBufferAdapter(); + + scoped_refptr< ::media::DecoderBuffer> const buffer_; + + DISALLOW_COPY_AND_ASSIGN(DecoderBufferAdapter); +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_BASE_DECODER_BUFFER_ADAPTER_H_ diff --git a/chromecast/media/cma/base/decoder_buffer_base.cc b/chromecast/media/cma/base/decoder_buffer_base.cc new file mode 100644 index 0000000000..40c0a7d006 --- /dev/null +++ b/chromecast/media/cma/base/decoder_buffer_base.cc @@ -0,0 +1,17 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chromecast/media/cma/base/decoder_buffer_base.h" + +namespace chromecast { +namespace media { + +DecoderBufferBase::DecoderBufferBase() { +} + +DecoderBufferBase::~DecoderBufferBase() { +} + +} // namespace media +} // namespace chromecast diff --git a/chromecast/media/cma/base/decoder_buffer_base.h b/chromecast/media/cma/base/decoder_buffer_base.h new file mode 100644 index 0000000000..9143104f14 --- /dev/null +++ b/chromecast/media/cma/base/decoder_buffer_base.h @@ -0,0 +1,57 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROMECAST_MEDIA_CMA_BASE_DECODER_BUFFER_BASE_H_ +#define CHROMECAST_MEDIA_CMA_BASE_DECODER_BUFFER_BASE_H_ + +#include "base/basictypes.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/time/time.h" + +namespace media { +class DecryptConfig; +} + +namespace chromecast { +namespace media { + +// DecoderBufferBase exposes only the properties of an audio/video buffer. +// The way a DecoderBufferBase is created and organized in memory +// is left as a detail of the implementation of derived classes. +class DecoderBufferBase + : public base::RefCountedThreadSafe<DecoderBufferBase> { + public: + DecoderBufferBase(); + + // Returns the PTS of the frame. + virtual base::TimeDelta timestamp() const = 0; + + // Gets the frame data. + virtual const uint8* data() const = 0; + virtual uint8* writable_data() const = 0; + + // Returns the size of the frame in bytes. + virtual int data_size() const = 0; + + // Returns the decrypt configuration. + // Returns NULL if the buffer has no decrypt info. + virtual const ::media::DecryptConfig* decrypt_config() const = 0; + + // Indicate if this is a special frame that indicates the end of the stream. + // If true, functions to access the frame content cannot be called. + virtual bool end_of_stream() const = 0; + + protected: + friend class base::RefCountedThreadSafe<DecoderBufferBase>; + virtual ~DecoderBufferBase(); + + private: + DISALLOW_COPY_AND_ASSIGN(DecoderBufferBase); +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_BASE_DECODER_BUFFER_BASE_H_ diff --git a/chromecast/media/cma/base/frame_generator_for_test.cc b/chromecast/media/cma/base/frame_generator_for_test.cc new file mode 100644 index 0000000000..c03fcf8847 --- /dev/null +++ b/chromecast/media/cma/base/frame_generator_for_test.cc @@ -0,0 +1,113 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chromecast/media/cma/base/frame_generator_for_test.h" + +#include "chromecast/media/cma/base/decoder_buffer_adapter.h" +#include "chromecast/media/cma/base/decoder_buffer_base.h" +#include "media/base/decoder_buffer.h" +#include "media/base/decrypt_config.h" + +namespace chromecast { +namespace media { + +FrameGeneratorForTest::FrameSpec::FrameSpec() + : has_config(false), + is_eos(false), + has_decrypt_config(false), + size(0) { +} + +FrameGeneratorForTest::FrameSpec::~FrameSpec() { +} + +FrameGeneratorForTest::FrameGeneratorForTest( + const std::vector<FrameSpec> frame_specs) + : frame_specs_(frame_specs), + frame_idx_(0), + total_buffer_size_(0) { +} + +FrameGeneratorForTest::~FrameGeneratorForTest() { +} + +bool FrameGeneratorForTest::HasDecoderConfig() const { + if (frame_idx_ >= frame_specs_.size()) + return false; + + return frame_specs_[frame_idx_].has_config; +} + +scoped_refptr<DecoderBufferBase> FrameGeneratorForTest::Generate() { + if (frame_idx_ >= frame_specs_.size()) + return scoped_refptr<DecoderBufferBase>(); + + const FrameSpec& frame_spec = frame_specs_[frame_idx_]; + frame_idx_++; + + if (frame_spec.is_eos) { + return scoped_refptr<DecoderBufferBase>( + new DecoderBufferAdapter(::media::DecoderBuffer::CreateEOSBuffer())); + } + + scoped_refptr< ::media::DecoderBuffer> buffer( + new ::media::DecoderBuffer(frame_spec.size)); + + // Timestamp. + buffer->set_timestamp(frame_spec.timestamp); + + // Generate the frame data. + for (size_t k = 0; k < frame_spec.size; k++) { + buffer->writable_data()[k] = total_buffer_size_ & 0xff; + total_buffer_size_++; + } + + // Generate the decrypt configuration. + if (frame_spec.has_decrypt_config) { + uint32 frame_size = buffer->data_size(); + uint32 chunk_size = 1; + std::vector< ::media::SubsampleEntry> subsamples; + while (frame_size > 0) { + ::media::SubsampleEntry subsample; + subsample.clear_bytes = chunk_size; + if (subsample.clear_bytes > frame_size) + subsample.clear_bytes = frame_size; + frame_size -= subsample.clear_bytes; + chunk_size <<= 1; + + subsample.cypher_bytes = chunk_size; + if (subsample.cypher_bytes > frame_size) + subsample.cypher_bytes = frame_size; + frame_size -= subsample.cypher_bytes; + chunk_size <<= 1; + + subsamples.push_back(subsample); + } + + char key_id[] = { + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, + 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf }; + + char iv[] = { + 0x0, 0x2, 0x1, 0x3, 0x5, 0x4, 0x7, 0x6, + 0x9, 0x8, 0xb, 0xa, 0xd, 0xc, 0xf, 0xe }; + + scoped_ptr< ::media::DecryptConfig> decrypt_config( + new ::media::DecryptConfig( + std::string(key_id, arraysize(key_id)), + std::string(iv, arraysize(iv)), + subsamples)); + buffer->set_decrypt_config(decrypt_config.Pass()); + } + + return scoped_refptr<DecoderBufferBase>(new DecoderBufferAdapter(buffer)); +} + +size_t FrameGeneratorForTest::RemainingFrameCount() const { + size_t count = frame_specs_.size() - frame_idx_; + return count; +} + +} // namespace media +} // namespace chromecast diff --git a/chromecast/media/cma/base/frame_generator_for_test.h b/chromecast/media/cma/base/frame_generator_for_test.h new file mode 100644 index 0000000000..1da3fa8518 --- /dev/null +++ b/chromecast/media/cma/base/frame_generator_for_test.h @@ -0,0 +1,59 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROMECAST_MEDIA_CMA_BASE_FRAME_GENERATOR_FOR_TEST_H_ +#define CHROMECAST_MEDIA_CMA_BASE_FRAME_GENERATOR_FOR_TEST_H_ + +#include <vector> + +#include "base/memory/ref_counted.h" +#include "base/time/time.h" + +namespace chromecast { +namespace media { +class DecoderBufferBase; + +class FrameGeneratorForTest { + public: + // Parameters used to generate frames. + struct FrameSpec { + FrameSpec(); + ~FrameSpec(); + + // Indicates whether the frame comes with a new decoder configuration. + bool has_config; + + bool is_eos; + base::TimeDelta timestamp; + bool has_decrypt_config; + size_t size; + }; + + explicit FrameGeneratorForTest(const std::vector<FrameSpec> frame_specs); + ~FrameGeneratorForTest(); + + // Indicates whether the next frame should come with a new decoder config. + bool HasDecoderConfig() const; + + // Generates a frame. + // Returns NULL is there is no frame left to generate. + scoped_refptr<DecoderBufferBase> Generate(); + + // Number of frames not generated yet. + size_t RemainingFrameCount() const; + + private: + std::vector<FrameSpec> frame_specs_; + size_t frame_idx_; + + // Total size of A/V buffers generated so far. + size_t total_buffer_size_; + + DISALLOW_COPY_AND_ASSIGN(FrameGeneratorForTest); +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_BASE_TEST_FRAME_GENERATOR_H_ diff --git a/chromecast/media/cma/base/media_task_runner.cc b/chromecast/media/cma/base/media_task_runner.cc new file mode 100644 index 0000000000..5b5423bbdf --- /dev/null +++ b/chromecast/media/cma/base/media_task_runner.cc @@ -0,0 +1,17 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chromecast/media/cma/base/media_task_runner.h" + +namespace chromecast { +namespace media { + +MediaTaskRunner::MediaTaskRunner() { +} + +MediaTaskRunner::~MediaTaskRunner() { +} + +} // namespace media +} // namespace chromecast diff --git a/chromecast/media/cma/base/media_task_runner.h b/chromecast/media/cma/base/media_task_runner.h new file mode 100644 index 0000000000..c4a3012c94 --- /dev/null +++ b/chromecast/media/cma/base/media_task_runner.h @@ -0,0 +1,44 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROMECAST_MEDIA_CMA_BASE_MEDIA_TASK_RUNNER_H_ +#define CHROMECAST_MEDIA_CMA_BASE_MEDIA_TASK_RUNNER_H_ + +#include "base/callback.h" +#include "base/location.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/time/time.h" + +namespace chromecast { +namespace media { + +class MediaTaskRunner + : public base::RefCountedThreadSafe<MediaTaskRunner> { + public: + MediaTaskRunner(); + + // Post a task with the given media |timestamp|. If |timestamp| is equal to + // |kNoTimestamp()|, the task is scheduled right away. + // How the media timestamp is used to schedule the task is an implementation + // detail of derived classes. + // Returns true if the task may be run at some point in the future, and false + // if the task definitely will not be run. + virtual bool PostMediaTask( + const tracked_objects::Location& from_here, + const base::Closure& task, + base::TimeDelta timestamp) = 0; + + protected: + virtual ~MediaTaskRunner(); + friend class base::RefCountedThreadSafe<MediaTaskRunner>; + + private: + DISALLOW_COPY_AND_ASSIGN(MediaTaskRunner); +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_BASE_MEDIA_TASK_RUNNER_H_ diff --git a/chromecast/media/cma/base/mock_frame_consumer.cc b/chromecast/media/cma/base/mock_frame_consumer.cc new file mode 100644 index 0000000000..a32989dd39 --- /dev/null +++ b/chromecast/media/cma/base/mock_frame_consumer.cc @@ -0,0 +1,116 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chromecast/media/cma/base/mock_frame_consumer.h" + +#include "base/bind.h" +#include "base/location.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/time/time.h" +#include "chromecast/media/cma/base/coded_frame_provider.h" +#include "chromecast/media/cma/base/decoder_buffer_base.h" +#include "chromecast/media/cma/base/frame_generator_for_test.h" +#include "media/base/video_decoder_config.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace chromecast { +namespace media { + +MockFrameConsumer::MockFrameConsumer( + CodedFrameProvider* coded_frame_provider) + : coded_frame_provider_(coded_frame_provider), + pattern_idx_(0), + last_read_aborted_by_flush_(false) { +} + +MockFrameConsumer::~MockFrameConsumer() { +} + +void MockFrameConsumer::Configure( + const std::vector<bool>& delayed_task_pattern, + bool last_read_aborted_by_flush, + scoped_ptr<FrameGeneratorForTest> frame_generator) { + delayed_task_pattern_ = delayed_task_pattern; + last_read_aborted_by_flush_ = last_read_aborted_by_flush; + frame_generator_.reset(frame_generator.release()); +} + +void MockFrameConsumer::Start(const base::Closure& done_cb) { + done_cb_ = done_cb; + + pattern_idx_ = 0; + + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, + base::Bind(&MockFrameConsumer::ReadFrame, base::Unretained(this))); +} + +void MockFrameConsumer::ReadFrame() { + // Once all the frames have been read, flush the frame provider. + if (frame_generator_->RemainingFrameCount() == 0 && + !last_read_aborted_by_flush_) { + coded_frame_provider_->Flush( + base::Bind(&MockFrameConsumer::OnFlushCompleted, + base::Unretained(this))); + return; + } + + coded_frame_provider_->Read( + base::Bind(&MockFrameConsumer::OnNewFrame, + base::Unretained(this))); + + // The last read is right away aborted by a Flush. + if (frame_generator_->RemainingFrameCount() == 0 && + last_read_aborted_by_flush_) { + coded_frame_provider_->Flush( + base::Bind(&MockFrameConsumer::OnFlushCompleted, + base::Unretained(this))); + return; + } +} + +void MockFrameConsumer::OnNewFrame( + const scoped_refptr<DecoderBufferBase>& buffer, + const ::media::AudioDecoderConfig& audio_config, + const ::media::VideoDecoderConfig& video_config) { + bool ref_has_config = frame_generator_->HasDecoderConfig(); + scoped_refptr<DecoderBufferBase> ref_buffer = frame_generator_->Generate(); + + ASSERT_TRUE(buffer.get()); + ASSERT_TRUE(ref_buffer.get()); + + EXPECT_EQ(video_config.IsValidConfig(), ref_has_config); + + EXPECT_EQ(buffer->end_of_stream(), ref_buffer->end_of_stream()); + if (!ref_buffer->end_of_stream()) { + EXPECT_EQ(buffer->timestamp(), ref_buffer->timestamp()); + ASSERT_EQ(buffer->data_size(), ref_buffer->data_size()); + for (size_t k = 0; k < ref_buffer->data_size(); k++) + EXPECT_EQ(buffer->data()[k], ref_buffer->data()[k]); + } + + bool delayed = delayed_task_pattern_[pattern_idx_]; + pattern_idx_ = (pattern_idx_ + 1) % delayed_task_pattern_.size(); + + if (delayed) { + base::MessageLoopProxy::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&MockFrameConsumer::ReadFrame, + base::Unretained(this)), + base::TimeDelta::FromMilliseconds(1)); + } else { + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, + base::Bind(&MockFrameConsumer::ReadFrame, + base::Unretained(this))); + } +} + +void MockFrameConsumer::OnFlushCompleted() { + EXPECT_EQ(frame_generator_->RemainingFrameCount(), 0); + done_cb_.Run(); +} + +} // namespace media +} // namespace chromecast diff --git a/chromecast/media/cma/base/mock_frame_consumer.h b/chromecast/media/cma/base/mock_frame_consumer.h new file mode 100644 index 0000000000..6c84119d15 --- /dev/null +++ b/chromecast/media/cma/base/mock_frame_consumer.h @@ -0,0 +1,69 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROMECAST_MEDIA_CMA_BASE_MOCK_FRAME_CONSUMER_H_ +#define CHROMECAST_MEDIA_CMA_BASE_MOCK_FRAME_CONSUMER_H_ + +#include <vector> + +#include "base/callback.h" +#include "base/macros.h" + +namespace media { +class AudioDecoderConfig; +class VideoDecoderConfig; +} + +namespace chromecast { +namespace media { +class CodedFrameProvider; +class DecoderBufferBase; +class FrameGeneratorForTest; + +class MockFrameConsumer { + public: + explicit MockFrameConsumer(CodedFrameProvider* coded_frame_provider); + ~MockFrameConsumer(); + + void Configure(const std::vector<bool>& delayed_task_pattern, + bool last_read_aborted_by_flush, + scoped_ptr<FrameGeneratorForTest> frame_generator); + + // Starts consuming frames. Invoke |done_cb| when all the expected frames + // have been received. + void Start(const base::Closure& done_cb); + + private: + void ReadFrame(); + void OnNewFrame(const scoped_refptr<DecoderBufferBase>& buffer, + const ::media::AudioDecoderConfig& audio_config, + const ::media::VideoDecoderConfig& video_config); + + void OnFlushCompleted(); + + CodedFrameProvider* const coded_frame_provider_; + + base::Closure done_cb_; + + // Parameterization of the frame consumer: + // |delayed_task_pattern_| indicates the pattern for fetching frames, + // i.e. after receiving a frame, either fetch a frame right away + // or wait some time before fetching another frame. + // |pattern_idx_| is the current index in the pattern. + // |last_read_aborted_by_flush_| indicates whether the last buffer request + // should be aborted by a Flush. + std::vector<bool> delayed_task_pattern_; + size_t pattern_idx_; + bool last_read_aborted_by_flush_; + + // Expected results. + scoped_ptr<FrameGeneratorForTest> frame_generator_; + + DISALLOW_COPY_AND_ASSIGN(MockFrameConsumer); +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_BASE_MOCK_FRAME_CONSUMER_H_ diff --git a/chromecast/media/cma/base/mock_frame_provider.cc b/chromecast/media/cma/base/mock_frame_provider.cc new file mode 100644 index 0000000000..286c50ae2b --- /dev/null +++ b/chromecast/media/cma/base/mock_frame_provider.cc @@ -0,0 +1,95 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chromecast/media/cma/base/mock_frame_provider.h" + +#include "base/bind.h" +#include "base/location.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/time/time.h" +#include "chromecast/media/cma/base/decoder_buffer_base.h" +#include "chromecast/media/cma/base/frame_generator_for_test.h" +#include "media/base/audio_decoder_config.h" +#include "media/base/decoder_buffer.h" +#include "media/base/video_decoder_config.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/rect.h" +#include "ui/gfx/size.h" + +namespace chromecast { +namespace media { + +MockFrameProvider::MockFrameProvider() { +} + +MockFrameProvider::~MockFrameProvider() { +} + +void MockFrameProvider::Configure( + const std::vector<bool>& delayed_task_pattern, + scoped_ptr<FrameGeneratorForTest> frame_generator) { + delayed_task_pattern_ = delayed_task_pattern; + pattern_idx_ = 0; + + frame_generator_.reset(frame_generator.release()); +} + +void MockFrameProvider::Read(const ReadCB& read_cb) { + bool delayed = delayed_task_pattern_[pattern_idx_]; + pattern_idx_ = (pattern_idx_ + 1) % delayed_task_pattern_.size(); + + if (delayed) { + base::MessageLoopProxy::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&MockFrameProvider::DoRead, + base::Unretained(this), read_cb), + base::TimeDelta::FromMilliseconds(1)); + } else { + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, + base::Bind(&MockFrameProvider::DoRead, + base::Unretained(this), read_cb)); + } +} + +void MockFrameProvider::Flush(const base::Closure& flush_cb) { + flush_cb.Run(); +} + +void MockFrameProvider::DoRead(const ReadCB& read_cb) { + bool has_config = frame_generator_->HasDecoderConfig(); + + scoped_refptr<DecoderBufferBase> buffer(frame_generator_->Generate()); + ASSERT_TRUE(buffer.get()); + + ::media::AudioDecoderConfig audio_config; + ::media::VideoDecoderConfig video_config; + if (has_config) { + gfx::Size coded_size(640, 480); + gfx::Rect visible_rect(640, 480); + gfx::Size natural_size(640, 480); + video_config = ::media::VideoDecoderConfig( + ::media::kCodecH264, + ::media::VIDEO_CODEC_PROFILE_UNKNOWN, + ::media::VideoFrame::YV12, + coded_size, + visible_rect, + natural_size, + NULL, 0, + false); + + audio_config = ::media::AudioDecoderConfig( + ::media::kCodecAAC, + ::media::kSampleFormatS16, + ::media::CHANNEL_LAYOUT_STEREO, + 44100, + NULL, 0, + false); + } + + read_cb.Run(buffer, audio_config, video_config); +} + +} // namespace media +} // namespace chromecast diff --git a/chromecast/media/cma/base/mock_frame_provider.h b/chromecast/media/cma/base/mock_frame_provider.h new file mode 100644 index 0000000000..fbea1cf61e --- /dev/null +++ b/chromecast/media/cma/base/mock_frame_provider.h @@ -0,0 +1,49 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROMECAST_MEDIA_CMA_BASE_MOCK_FRAME_PROVIDER_H_ +#define CHROMECAST_MEDIA_CMA_BASE_MOCK_FRAME_PROVIDER_H_ + +#include <vector> + +#include "base/macros.h" +#include "chromecast/media/cma/base/coded_frame_provider.h" + +namespace chromecast { +namespace media { +class FrameGeneratorForTest; + +class MockFrameProvider : public CodedFrameProvider { + public: + MockFrameProvider(); + virtual ~MockFrameProvider(); + + void Configure( + const std::vector<bool>& delayed_task_pattern, + scoped_ptr<FrameGeneratorForTest> frame_generator); + + // CodedFrameProvider implementation. + virtual void Read(const ReadCB& read_cb) OVERRIDE; + virtual void Flush(const base::Closure& flush_cb) OVERRIDE; + + private: + void DoRead(const ReadCB& read_cb); + + // Parameterization of the frame provider. + // |delayed_task_pattern_| indicates the pattern for delivering frames, + // i.e. after receiving a Read request, either delivers a frame right away + // or wait some time before delivering the frame. + // |pattern_idx_| is the current index in the pattern. + std::vector<bool> delayed_task_pattern_; + size_t pattern_idx_; + + scoped_ptr<FrameGeneratorForTest> frame_generator_; + + DISALLOW_COPY_AND_ASSIGN(MockFrameProvider); +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_BASE_MOCK_FRAME_PROVIDER_H_ diff --git a/chromecast/media/cma/base/run_all_unittests.cc b/chromecast/media/cma/base/run_all_unittests.cc new file mode 100644 index 0000000000..07b9e2bfb8 --- /dev/null +++ b/chromecast/media/cma/base/run_all_unittests.cc @@ -0,0 +1,40 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/bind.h" +#include "base/test/launcher/unit_test_launcher.h" +#include "base/test/test_suite.h" +#include "build/build_config.h" +#include "media/base/media.h" + +#if defined(OS_ANDROID) +#error "CMA not supported on Android" +#endif + +class CmaTestSuite : public base::TestSuite { + public: + // Note: the base class constructor creates an AtExitManager. + CmaTestSuite(int argc, char** argv) : TestSuite(argc, argv) {} + virtual ~CmaTestSuite() {} + + protected: + virtual void Initialize() OVERRIDE; +}; + +void CmaTestSuite::Initialize() { + // Run TestSuite::Initialize first so that logging is initialized. + base::TestSuite::Initialize(); + + // Initialize the FFMpeg library. + // Note: at this time, AtExitManager is already present. + media::InitializeMediaLibraryForTesting(); +} + +int main(int argc, char** argv) { + CmaTestSuite test_suite(argc, argv); + + return base::LaunchUnitTests( + argc, argv, base::Bind(&CmaTestSuite::Run, + base::Unretained(&test_suite))); +} diff --git a/chromecast/media/cma/filters/demuxer_stream_adapter.cc b/chromecast/media/cma/filters/demuxer_stream_adapter.cc new file mode 100644 index 0000000000..2f27e78fe4 --- /dev/null +++ b/chromecast/media/cma/filters/demuxer_stream_adapter.cc @@ -0,0 +1,207 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chromecast/media/cma/filters/demuxer_stream_adapter.h" + +#include "base/bind.h" +#include "base/callback_helpers.h" +#include "base/single_thread_task_runner.h" +#include "chromecast/media/cma/base/balanced_media_task_runner_factory.h" +#include "chromecast/media/cma/base/cma_logging.h" +#include "chromecast/media/cma/base/decoder_buffer_adapter.h" +#include "chromecast/media/cma/base/media_task_runner.h" +#include "media/base/bind_to_current_loop.h" +#include "media/base/buffers.h" +#include "media/base/decoder_buffer.h" +#include "media/base/demuxer_stream.h" + +namespace chromecast { +namespace media { + +namespace { + +class DummyMediaTaskRunner : public MediaTaskRunner { + public: + DummyMediaTaskRunner( + const scoped_refptr<base::SingleThreadTaskRunner>& task_runner); + + // MediaTaskRunner implementation. + virtual bool PostMediaTask( + const tracked_objects::Location& from_here, + const base::Closure& task, + base::TimeDelta timestamp) OVERRIDE; + + private: + virtual ~DummyMediaTaskRunner(); + + scoped_refptr<base::SingleThreadTaskRunner> const task_runner_; + + DISALLOW_COPY_AND_ASSIGN(DummyMediaTaskRunner); +}; + +DummyMediaTaskRunner::DummyMediaTaskRunner( + const scoped_refptr<base::SingleThreadTaskRunner>& task_runner) + : task_runner_(task_runner) { +} + +DummyMediaTaskRunner::~DummyMediaTaskRunner() { +} + +bool DummyMediaTaskRunner::PostMediaTask( + const tracked_objects::Location& from_here, + const base::Closure& task, + base::TimeDelta timestamp) { + return task_runner_->PostTask(from_here, task); +} + +} // namespace + +DemuxerStreamAdapter::DemuxerStreamAdapter( + const scoped_refptr<base::SingleThreadTaskRunner>& task_runner, + const scoped_refptr<BalancedMediaTaskRunnerFactory>& + media_task_runner_factory, + ::media::DemuxerStream* demuxer_stream) + : task_runner_(task_runner), + media_task_runner_factory_(media_task_runner_factory), + media_task_runner_(new DummyMediaTaskRunner(task_runner)), + demuxer_stream_(demuxer_stream), + is_pending_read_(false), + is_pending_demuxer_read_(false), + weak_factory_(this), + weak_this_(weak_factory_.GetWeakPtr()) { + ResetMediaTaskRunner(); + thread_checker_.DetachFromThread(); +} + +DemuxerStreamAdapter::~DemuxerStreamAdapter() { + // Needed since we use weak pointers: + // weak pointers must be invalidated on the same thread. + DCHECK(thread_checker_.CalledOnValidThread()); +} + +void DemuxerStreamAdapter::Read(const ReadCB& read_cb) { + DCHECK(thread_checker_.CalledOnValidThread()); + + DCHECK(flush_cb_.is_null()); + + // Support only one read at a time. + DCHECK(!is_pending_read_); + is_pending_read_ = true; + ReadInternal(read_cb); +} + +void DemuxerStreamAdapter::ReadInternal(const ReadCB& read_cb) { + bool may_run_in_future = media_task_runner_->PostMediaTask( + FROM_HERE, + base::Bind(&DemuxerStreamAdapter::RequestBuffer, weak_this_, read_cb), + max_pts_); + DCHECK(may_run_in_future); +} + +void DemuxerStreamAdapter::Flush(const base::Closure& flush_cb) { + DCHECK(thread_checker_.CalledOnValidThread()); + CMALOG(kLogControl) << __FUNCTION__; + + // Flush cancels any pending read. + is_pending_read_ = false; + + // Reset the decoder configurations. + audio_config_ = ::media::AudioDecoderConfig(); + video_config_ = ::media::VideoDecoderConfig(); + + // Create a new media task runner for the upcoming media timeline. + ResetMediaTaskRunner(); + + DCHECK(flush_cb_.is_null()); + if (is_pending_demuxer_read_) { + // If there is a pending demuxer read, the implicit contract + // is that the pending read must be completed before invoking the + // flush callback. + flush_cb_ = flush_cb; + return; + } + + // At this point, there is no more pending demuxer read, + // so all the previous tasks associated with the current timeline + // can be cancelled. + weak_factory_.InvalidateWeakPtrs(); + weak_this_ = weak_factory_.GetWeakPtr(); + + CMALOG(kLogControl) << "Flush done"; + flush_cb.Run(); +} + +void DemuxerStreamAdapter::ResetMediaTaskRunner() { + DCHECK(thread_checker_.CalledOnValidThread()); + + max_pts_ = ::media::kNoTimestamp(); + if (media_task_runner_factory_.get()) { + media_task_runner_ = + media_task_runner_factory_->CreateMediaTaskRunner(task_runner_); + } +} + +void DemuxerStreamAdapter::RequestBuffer(const ReadCB& read_cb) { + DCHECK(thread_checker_.CalledOnValidThread()); + is_pending_demuxer_read_ = true; + demuxer_stream_->Read(::media::BindToCurrentLoop( + base::Bind(&DemuxerStreamAdapter::OnNewBuffer, weak_this_, read_cb))); +} + +void DemuxerStreamAdapter::OnNewBuffer( + const ReadCB& read_cb, + ::media::DemuxerStream::Status status, + const scoped_refptr< ::media::DecoderBuffer>& input) { + DCHECK(thread_checker_.CalledOnValidThread()); + + is_pending_demuxer_read_ = false; + + // Just discard the buffer in the flush stage. + if (!flush_cb_.is_null()) { + CMALOG(kLogControl) << "Flush done"; + base::ResetAndReturn(&flush_cb_).Run(); + return; + } + + if (status == ::media::DemuxerStream::kAborted) { + DCHECK(input.get() == NULL); + return; + } + + if (status == ::media::DemuxerStream::kConfigChanged) { + DCHECK(input.get() == NULL); + if (demuxer_stream_->type() == ::media::DemuxerStream::VIDEO) + video_config_ = demuxer_stream_->video_decoder_config(); + if (demuxer_stream_->type() == ::media::DemuxerStream::AUDIO) + audio_config_ = demuxer_stream_->audio_decoder_config(); + + // Got a new config, but we still need to get a frame. + ReadInternal(read_cb); + return; + } + + DCHECK_EQ(status, ::media::DemuxerStream::kOk); + + // Updates the timestamp used for task scheduling. + if (!input->end_of_stream() && + input->timestamp() != ::media::kNoTimestamp() && + (max_pts_ == ::media::kNoTimestamp() || input->timestamp() > max_pts_)) { + max_pts_ = input->timestamp(); + } + + // Provides the buffer as well as possibly valid audio and video configs. + is_pending_read_ = false; + scoped_refptr<DecoderBufferBase> buffer(new DecoderBufferAdapter(input)); + read_cb.Run(buffer, audio_config_, video_config_); + + // Back to the default audio/video config: + // an invalid audio/video config means there is no config update. + if (audio_config_.IsValidConfig()) + audio_config_ = ::media::AudioDecoderConfig(); + if (video_config_.IsValidConfig()) + video_config_ = ::media::VideoDecoderConfig(); +} + +} // namespace media +} // namespace chromecast diff --git a/chromecast/media/cma/filters/demuxer_stream_adapter.h b/chromecast/media/cma/filters/demuxer_stream_adapter.h new file mode 100644 index 0000000000..4729628816 --- /dev/null +++ b/chromecast/media/cma/filters/demuxer_stream_adapter.h @@ -0,0 +1,93 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROMECAST_MEDIA_CMA_FILTERS_DEMUXER_STREAM_ADAPTER_H_ +#define CHROMECAST_MEDIA_CMA_FILTERS_DEMUXER_STREAM_ADAPTER_H_ + +#include "base/callback.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/threading/thread_checker.h" +#include "base/time/time.h" +#include "chromecast/media/cma/base/coded_frame_provider.h" +#include "media/base/audio_decoder_config.h" +#include "media/base/demuxer_stream.h" +#include "media/base/video_decoder_config.h" + +namespace base { +class SingleThreadTaskRunner; +} + +namespace media { +class DemuxerStream; +} + +namespace chromecast { +namespace media { +class BalancedMediaTaskRunnerFactory; +class MediaTaskRunner; + +// DemuxerStreamAdapter wraps a DemuxerStream into a CodedFrameProvider. +class DemuxerStreamAdapter : public CodedFrameProvider { + public: + DemuxerStreamAdapter( + const scoped_refptr<base::SingleThreadTaskRunner>& task_runner, + const scoped_refptr<BalancedMediaTaskRunnerFactory>& + media_task_runner_factory, + ::media::DemuxerStream* demuxer_stream); + virtual ~DemuxerStreamAdapter(); + + // CodedFrameProvider implementation. + virtual void Read(const ReadCB& read_cb) OVERRIDE; + virtual void Flush(const base::Closure& flush_cb) OVERRIDE; + + private: + void ResetMediaTaskRunner(); + + void ReadInternal(const ReadCB& read_cb); + void RequestBuffer(const ReadCB& read_cb); + + // Callback invoked from the demuxer stream to signal a buffer is ready. + void OnNewBuffer(const ReadCB& read_cb, + ::media::DemuxerStream::Status status, + const scoped_refptr< ::media::DecoderBuffer>& input); + + base::ThreadChecker thread_checker_; + + // Task runner DemuxerStreamAdapter is running on. + scoped_refptr<base::SingleThreadTaskRunner> const task_runner_; + + // Media task runner to pace requests to the DemuxerStream. + scoped_refptr<BalancedMediaTaskRunnerFactory> const + media_task_runner_factory_; + scoped_refptr<MediaTaskRunner> media_task_runner_; + base::TimeDelta max_pts_; + + // Frames are provided by |demuxer_stream_|. + ::media::DemuxerStream* const demuxer_stream_; + + // Indicate if there is a pending read. + bool is_pending_read_; + + // Indicate if |demuxer_stream_| has a pending read. + bool is_pending_demuxer_read_; + + // In case of a pending flush operation, this is the callback + // that is invoked when flush is completed. + base::Closure flush_cb_; + + // Audio/video configuration that applies to the next frame. + ::media::AudioDecoderConfig audio_config_; + ::media::VideoDecoderConfig video_config_; + + base::WeakPtrFactory<DemuxerStreamAdapter> weak_factory_; + base::WeakPtr<DemuxerStreamAdapter> weak_this_; + + DISALLOW_COPY_AND_ASSIGN(DemuxerStreamAdapter); +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_FILTERS_DEMUXER_STREAM_ADAPTER_H_ diff --git a/chromecast/media/cma/filters/demuxer_stream_adapter_unittest.cc b/chromecast/media/cma/filters/demuxer_stream_adapter_unittest.cc new file mode 100644 index 0000000000..cc1a6b0e9c --- /dev/null +++ b/chromecast/media/cma/filters/demuxer_stream_adapter_unittest.cc @@ -0,0 +1,345 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <list> + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/threading/thread.h" +#include "base/time/time.h" +#include "chromecast/media/cma/base/balanced_media_task_runner_factory.h" +#include "chromecast/media/cma/base/decoder_buffer_base.h" +#include "chromecast/media/cma/filters/demuxer_stream_adapter.h" +#include "media/base/audio_decoder_config.h" +#include "media/base/decoder_buffer.h" +#include "media/base/demuxer_stream.h" +#include "media/base/video_decoder_config.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace chromecast { +namespace media { + +namespace { + +class DummyDemuxerStream : public ::media::DemuxerStream { + public: + // Creates a demuxer stream which provides frames either with a delay + // or instantly. The scheduling pattern is the following: + // - provides |delayed_frame_count| frames with a delay, + // - then provides the following |cycle_count| - |delayed_frame_count| + // instantly, + // - then provides |delayed_frame_count| frames with a delay, + // - ... and so on. + // Special cases: + // - all frames are delayed: |delayed_frame_count| = |cycle_count| + // - all frames are provided instantly: |delayed_frame_count| = 0 + // |config_idx| is a list of frame index before which there is + // a change of decoder configuration. + DummyDemuxerStream(int cycle_count, + int delayed_frame_count, + const std::list<int>& config_idx); + virtual ~DummyDemuxerStream(); + + // ::media::DemuxerStream implementation. + virtual void Read(const ReadCB& read_cb) OVERRIDE; + virtual ::media::AudioDecoderConfig audio_decoder_config() OVERRIDE; + virtual ::media::VideoDecoderConfig video_decoder_config() OVERRIDE; + virtual Type type() OVERRIDE; + virtual bool SupportsConfigChanges() OVERRIDE; + virtual ::media::VideoRotation video_rotation() OVERRIDE; + + bool has_pending_read() const { + return has_pending_read_; + } + + private: + void DoRead(const ReadCB& read_cb); + + // Demuxer configuration. + const int cycle_count_; + const int delayed_frame_count_; + std::list<int> config_idx_; + + // Number of frames sent so far. + int frame_count_; + + bool has_pending_read_; + + DISALLOW_COPY_AND_ASSIGN(DummyDemuxerStream); +}; + +DummyDemuxerStream::DummyDemuxerStream( + int cycle_count, + int delayed_frame_count, + const std::list<int>& config_idx) + : cycle_count_(cycle_count), + delayed_frame_count_(delayed_frame_count), + config_idx_(config_idx), + frame_count_(0), + has_pending_read_(false) { + DCHECK_LE(delayed_frame_count, cycle_count); +} + +DummyDemuxerStream::~DummyDemuxerStream() { +} + +void DummyDemuxerStream::Read(const ReadCB& read_cb) { + has_pending_read_ = true; + if (!config_idx_.empty() && config_idx_.front() == frame_count_) { + config_idx_.pop_front(); + has_pending_read_ = false; + read_cb.Run(kConfigChanged, + scoped_refptr< ::media::DecoderBuffer>()); + return; + } + + if ((frame_count_ % cycle_count_) < delayed_frame_count_) { + base::MessageLoopProxy::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&DummyDemuxerStream::DoRead, base::Unretained(this), + read_cb), + base::TimeDelta::FromMilliseconds(20)); + return; + } + DoRead(read_cb); +} + +::media::AudioDecoderConfig DummyDemuxerStream::audio_decoder_config() { + LOG(FATAL) << "DummyDemuxerStream is a video DemuxerStream"; + return ::media::AudioDecoderConfig(); +} + +::media::VideoDecoderConfig DummyDemuxerStream::video_decoder_config() { + gfx::Size coded_size(640, 480); + gfx::Rect visible_rect(640, 480); + gfx::Size natural_size(640, 480); + return ::media::VideoDecoderConfig( + ::media::kCodecH264, + ::media::VIDEO_CODEC_PROFILE_UNKNOWN, + ::media::VideoFrame::YV12, + coded_size, + visible_rect, + natural_size, + NULL, 0, + false); +} + +::media::DemuxerStream::Type DummyDemuxerStream::type() { + return VIDEO; +} + +bool DummyDemuxerStream::SupportsConfigChanges() { + return true; +} + +::media::VideoRotation DummyDemuxerStream::video_rotation() { + return ::media::VIDEO_ROTATION_0; +} + +void DummyDemuxerStream::DoRead(const ReadCB& read_cb) { + has_pending_read_ = false; + scoped_refptr< ::media::DecoderBuffer> buffer( + new ::media::DecoderBuffer(16)); + buffer->set_timestamp(frame_count_ * base::TimeDelta::FromMilliseconds(40)); + frame_count_++; + read_cb.Run(kOk, buffer); +} + +} // namespace + +class DemuxerStreamAdapterTest : public testing::Test { + public: + DemuxerStreamAdapterTest(); + virtual ~DemuxerStreamAdapterTest(); + + void Initialize(::media::DemuxerStream* demuxer_stream); + void Start(); + + protected: + void OnTestTimeout(); + void OnNewFrame(const scoped_refptr<DecoderBufferBase>& buffer, + const ::media::AudioDecoderConfig& audio_config, + const ::media::VideoDecoderConfig& video_config); + void OnFlushCompleted(); + + // Total number of frames to request. + int total_frames_; + + // Number of demuxer read before issuing an early flush. + int early_flush_idx_; + bool use_post_task_for_flush_; + + // Number of expected read frames. + int total_expected_frames_; + + // Number of frames actually read so far. + int frame_received_count_; + + // List of expected frame indices with decoder config changes. + std::list<int> config_idx_; + + scoped_ptr<DummyDemuxerStream> demuxer_stream_; + + scoped_ptr<CodedFrameProvider> coded_frame_provider_; + + DISALLOW_COPY_AND_ASSIGN(DemuxerStreamAdapterTest); +}; + +DemuxerStreamAdapterTest::DemuxerStreamAdapterTest() + : use_post_task_for_flush_(false) { +} + +DemuxerStreamAdapterTest::~DemuxerStreamAdapterTest() { +} + +void DemuxerStreamAdapterTest::Initialize( + ::media::DemuxerStream* demuxer_stream) { + coded_frame_provider_.reset( + new DemuxerStreamAdapter( + base::MessageLoopProxy::current(), + scoped_refptr<BalancedMediaTaskRunnerFactory>(), + demuxer_stream)); +} + +void DemuxerStreamAdapterTest::Start() { + frame_received_count_ = 0; + + // TODO(damienv): currently, test assertions which fail do not trigger the + // exit of the unit test, the message loop is still running. Find a different + // way to exit the unit test. + base::MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&DemuxerStreamAdapterTest::OnTestTimeout, + base::Unretained(this)), + base::TimeDelta::FromSeconds(5)); + + coded_frame_provider_->Read( + base::Bind(&DemuxerStreamAdapterTest::OnNewFrame, + base::Unretained(this))); +} + +void DemuxerStreamAdapterTest::OnTestTimeout() { + ADD_FAILURE() << "Test timed out"; + if (base::MessageLoop::current()) + base::MessageLoop::current()->QuitWhenIdle(); +} + +void DemuxerStreamAdapterTest::OnNewFrame( + const scoped_refptr<DecoderBufferBase>& buffer, + const ::media::AudioDecoderConfig& audio_config, + const ::media::VideoDecoderConfig& video_config) { + if (video_config.IsValidConfig()) { + ASSERT_GT(config_idx_.size(), 0); + ASSERT_EQ(frame_received_count_, config_idx_.front()); + config_idx_.pop_front(); + } + + ASSERT_TRUE(buffer.get() != NULL); + ASSERT_EQ(buffer->timestamp(), + frame_received_count_ * base::TimeDelta::FromMilliseconds(40)); + frame_received_count_++; + + if (frame_received_count_ >= total_frames_) { + coded_frame_provider_->Flush( + base::Bind(&DemuxerStreamAdapterTest::OnFlushCompleted, + base::Unretained(this))); + return; + } + + coded_frame_provider_->Read( + base::Bind(&DemuxerStreamAdapterTest::OnNewFrame, + base::Unretained(this))); + + ASSERT_LE(frame_received_count_, early_flush_idx_); + if (frame_received_count_ == early_flush_idx_) { + base::Closure flush_cb = + base::Bind(&DemuxerStreamAdapterTest::OnFlushCompleted, + base::Unretained(this)); + if (use_post_task_for_flush_) { + base::MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&CodedFrameProvider::Flush, + base::Unretained(coded_frame_provider_.get()), + flush_cb)); + } else { + coded_frame_provider_->Flush(flush_cb); + } + return; + } +} + +void DemuxerStreamAdapterTest::OnFlushCompleted() { + ASSERT_EQ(frame_received_count_, total_expected_frames_); + ASSERT_FALSE(demuxer_stream_->has_pending_read()); + base::MessageLoop::current()->QuitWhenIdle(); +} + +TEST_F(DemuxerStreamAdapterTest, NoDelay) { + total_frames_ = 10; + early_flush_idx_ = total_frames_; // No early flush. + total_expected_frames_ = 10; + config_idx_.push_back(0); + config_idx_.push_back(5); + + int cycle_count = 1; + int delayed_frame_count = 0; + demuxer_stream_.reset( + new DummyDemuxerStream( + cycle_count, delayed_frame_count, config_idx_)); + + scoped_ptr<base::MessageLoop> message_loop(new base::MessageLoop()); + Initialize(demuxer_stream_.get()); + message_loop->PostTask( + FROM_HERE, + base::Bind(&DemuxerStreamAdapterTest::Start, base::Unretained(this))); + message_loop->Run(); +} + +TEST_F(DemuxerStreamAdapterTest, AllDelayed) { + total_frames_ = 10; + early_flush_idx_ = total_frames_; // No early flush. + total_expected_frames_ = 10; + config_idx_.push_back(0); + config_idx_.push_back(5); + + int cycle_count = 1; + int delayed_frame_count = 1; + demuxer_stream_.reset( + new DummyDemuxerStream( + cycle_count, delayed_frame_count, config_idx_)); + + scoped_ptr<base::MessageLoop> message_loop(new base::MessageLoop()); + Initialize(demuxer_stream_.get()); + message_loop->PostTask( + FROM_HERE, + base::Bind(&DemuxerStreamAdapterTest::Start, base::Unretained(this))); + message_loop->Run(); +} + +TEST_F(DemuxerStreamAdapterTest, AllDelayedEarlyFlush) { + total_frames_ = 10; + early_flush_idx_ = 5; + use_post_task_for_flush_ = true; + total_expected_frames_ = 5; + config_idx_.push_back(0); + config_idx_.push_back(3); + + int cycle_count = 1; + int delayed_frame_count = 1; + demuxer_stream_.reset( + new DummyDemuxerStream( + cycle_count, delayed_frame_count, config_idx_)); + + scoped_ptr<base::MessageLoop> message_loop(new base::MessageLoop()); + Initialize(demuxer_stream_.get()); + message_loop->PostTask( + FROM_HERE, + base::Bind(&DemuxerStreamAdapterTest::Start, base::Unretained(this))); + message_loop->Run(); +} + +} // namespace media +} // namespace chromecast diff --git a/chromecast/media/cma/ipc/media_memory_chunk.cc b/chromecast/media/cma/ipc/media_memory_chunk.cc new file mode 100644 index 0000000000..0d6896958b --- /dev/null +++ b/chromecast/media/cma/ipc/media_memory_chunk.cc @@ -0,0 +1,14 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chromecast/media/cma/ipc/media_memory_chunk.h" + +namespace chromecast { +namespace media { + +MediaMemoryChunk::~MediaMemoryChunk() { +} + +} // namespace media +} // namespace chromecast diff --git a/chromecast/media/cma/ipc/media_memory_chunk.h b/chromecast/media/cma/ipc/media_memory_chunk.h new file mode 100644 index 0000000000..1b91c840a9 --- /dev/null +++ b/chromecast/media/cma/ipc/media_memory_chunk.h @@ -0,0 +1,39 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROMECAST_MEDIA_CMA_IPC_MEDIA_MEMORY_CHUNK_H_ +#define CHROMECAST_MEDIA_CMA_IPC_MEDIA_MEMORY_CHUNK_H_ + +#include "base/basictypes.h" + +namespace chromecast { +namespace media { + +// MediaMemoryChunk represents a block of memory without doing any assumption +// about the type of memory (e.g. shared memory) nor about the underlying +// memory ownership. +// The block of memory can be invalidated under the cover (e.g. if the derived +// class does not own the underlying memory), +// in that case, MediaMemoryChunk::valid() will return false. +class MediaMemoryChunk { + public: + virtual ~MediaMemoryChunk(); + + // Returns the start of the block of memory. + virtual void* data() const = 0; + + // Returns the size of the block of memory. + virtual size_t size() const = 0; + + // Returns whether the underlying block of memory is valid. + // Since MediaMemoryChunk does not specify a memory ownership model, + // the underlying block of memory might be invalidated by a third party. + // In that case, valid() will return false. + virtual bool valid() const = 0; +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_IPC_MEDIA_MEMORY_CHUNK_H_ diff --git a/chromecast/media/cma/ipc/media_message.cc b/chromecast/media/cma/ipc/media_message.cc new file mode 100644 index 0000000000..8dfcd7cb88 --- /dev/null +++ b/chromecast/media/cma/ipc/media_message.cc @@ -0,0 +1,198 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chromecast/media/cma/ipc/media_message.h" + +#include <limits> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "chromecast/media/cma/ipc/media_memory_chunk.h" + +namespace chromecast { +namespace media { + +// static +scoped_ptr<MediaMessage> MediaMessage::CreateDummyMessage( + uint32 type) { + return scoped_ptr<MediaMessage>( + new MediaMessage(type, std::numeric_limits<size_t>::max())); +} + +// static +scoped_ptr<MediaMessage> MediaMessage::CreateMessage( + uint32 type, + const MemoryAllocatorCB& memory_allocator, + size_t msg_content_capacity) { + size_t msg_size = minimum_msg_size() + msg_content_capacity; + + // Make the message size a multiple of the alignment + // so that if we have proper alignment for array of messages. + size_t end_alignment = msg_size % ALIGNOF(SerializedMsg); + if (end_alignment != 0) + msg_size += ALIGNOF(SerializedMsg) - end_alignment; + + scoped_ptr<MediaMemoryChunk> memory(memory_allocator.Run(msg_size)); + if (!memory) + return scoped_ptr<MediaMessage>(); + + return scoped_ptr<MediaMessage>(new MediaMessage(type, memory.Pass())); +} + +// static +scoped_ptr<MediaMessage> MediaMessage::CreateMessage( + uint32 type, + scoped_ptr<MediaMemoryChunk> memory) { + return scoped_ptr<MediaMessage>(new MediaMessage(type, memory.Pass())); +} + +// static +scoped_ptr<MediaMessage> MediaMessage::MapMessage( + scoped_ptr<MediaMemoryChunk> memory) { + return scoped_ptr<MediaMessage>(new MediaMessage(memory.Pass())); +} + +MediaMessage::MediaMessage(uint32 type, size_t msg_size) + : is_dummy_msg_(true), + cached_header_(&cached_msg_.header), + msg_(&cached_msg_), + msg_read_only_(&cached_msg_), + rd_offset_(0) { + cached_header_->size = msg_size; + cached_header_->type = type; + cached_header_->content_size = 0; +} + +MediaMessage::MediaMessage(uint32 type, scoped_ptr<MediaMemoryChunk> memory) + : is_dummy_msg_(false), + cached_header_(&cached_msg_.header), + msg_(static_cast<SerializedMsg*>(memory->data())), + msg_read_only_(msg_), + mem_(memory.Pass()), + rd_offset_(0) { + CHECK(mem_->valid()); + CHECK_GE(mem_->size(), minimum_msg_size()); + + // Check memory alignment: + // needed to cast properly |msg_dst| to a SerializedMsg. + CHECK_EQ( + reinterpret_cast<uintptr_t>(mem_->data()) % ALIGNOF(SerializedMsg), 0u); + + // Make sure that |mem_->data()| + |mem_->size()| is also aligned correctly. + // This is needed if we append a second serialized message next to this one. + // The second serialized message must be aligned correctly. + // It is similar to what a compiler is doing for arrays of structures. + CHECK_EQ(mem_->size() % ALIGNOF(SerializedMsg), 0u); + + cached_header_->size = mem_->size(); + cached_header_->type = type; + cached_header_->content_size = 0; + msg_->header = *cached_header_; +} + +MediaMessage::MediaMessage(scoped_ptr<MediaMemoryChunk> memory) + : is_dummy_msg_(false), + cached_header_(&cached_msg_.header), + msg_(NULL), + msg_read_only_(static_cast<SerializedMsg*>(memory->data())), + mem_(memory.Pass()), + rd_offset_(0) { + CHECK(mem_->valid()); + + // Check memory alignment. + CHECK_EQ( + reinterpret_cast<uintptr_t>(mem_->data()) % ALIGNOF(SerializedMsg), 0u); + + // Cache the message header which cannot be modified while reading. + CHECK_GE(mem_->size(), minimum_msg_size()); + *cached_header_ = msg_read_only_->header; + CHECK_GE(cached_header_->size, minimum_msg_size()); + + // Make sure if we have 2 consecutive serialized messages in memory, + // the 2nd message is also aligned correctly. + CHECK_EQ(cached_header_->size % ALIGNOF(SerializedMsg), 0u); + + size_t max_content_size = cached_header_->size - minimum_msg_size(); + CHECK_LE(cached_header_->content_size, max_content_size); +} + +MediaMessage::~MediaMessage() { +} + +bool MediaMessage::IsSerializedMsgAvailable() const { + return !is_dummy_msg_ && mem_->valid(); +} + +bool MediaMessage::WriteBuffer(const void* src, size_t size) { + // No message to write into. + if (!msg_) + return false; + + // The underlying memory was invalidated. + if (!is_dummy_msg_ && !mem_->valid()) + return false; + + size_t max_content_size = cached_header_->size - minimum_msg_size(); + if (cached_header_->content_size + size > max_content_size) { + cached_header_->content_size = max_content_size; + msg_->header.content_size = cached_header_->content_size; + return false; + } + + // Write the message only for non-dummy messages. + if (!is_dummy_msg_) { + uint8* wr_ptr = &msg_->content + cached_header_->content_size; + memcpy(wr_ptr, src, size); + } + + cached_header_->content_size += size; + msg_->header.content_size = cached_header_->content_size; + return true; +} + +bool MediaMessage::ReadBuffer(void* dst, size_t size) { + // No read possible for a dummy message. + if (is_dummy_msg_) + return false; + + // The underlying memory was invalidated. + if (!mem_->valid()) + return false; + + if (rd_offset_ + size > cached_header_->content_size) { + rd_offset_ = cached_header_->content_size; + return false; + } + + const uint8* rd_ptr = &msg_read_only_->content + rd_offset_; + memcpy(dst, rd_ptr, size); + rd_offset_ += size; + return true; +} + +void* MediaMessage::GetWritableBuffer(size_t size) { + // No read possible for a dummy message. + if (is_dummy_msg_) + return NULL; + + // The underlying memory was invalidated. + if (!mem_->valid()) + return NULL; + + if (rd_offset_ + size > cached_header_->content_size) { + rd_offset_ = cached_header_->content_size; + return NULL; + } + + uint8* rd_ptr = &msg_read_only_->content + rd_offset_; + rd_offset_ += size; + return rd_ptr; +} + +const void* MediaMessage::GetBuffer(size_t size) { + return GetWritableBuffer(size); +} + +} // namespace media +} // namespace chromecast diff --git a/chromecast/media/cma/ipc/media_message.h b/chromecast/media/cma/ipc/media_message.h new file mode 100644 index 0000000000..066409be3d --- /dev/null +++ b/chromecast/media/cma/ipc/media_message.h @@ -0,0 +1,165 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROMECAST_MEDIA_CMA_IPC_MEDIA_MESSAGE_H_ +#define CHROMECAST_MEDIA_CMA_IPC_MEDIA_MESSAGE_H_ + +#include <stddef.h> + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" + +namespace chromecast { +namespace media { +class MediaMemoryChunk; + +// MediaMessage - +// Represents a media message, including: +// - a message header that gives for example the message size or its type, +// - the content of the message, +// - and some possible padding if the content does not occupy the whole +// reserved space. +// +class MediaMessage { + public: + // Memory allocator: given a number of bytes to allocate, + // return the pointer to the allocated block if successful + // or NULL if allocation failed. + typedef base::Callback<scoped_ptr<MediaMemoryChunk>(size_t)> + MemoryAllocatorCB; + + // Creates a message with no associated memory for its content, i.e. + // each write on this message is a dummy operation. + // This type of message can be useful to calculate first the size of the + // message, before allocating the real message. + static scoped_ptr<MediaMessage> CreateDummyMessage(uint32 type); + + // Creates a message with a capacity of at least |msg_content_capacity| + // bytes. The actual content size can be smaller than its capacity. + // The message can be populated with some Write functions. + static scoped_ptr<MediaMessage> CreateMessage( + uint32 type, + const MemoryAllocatorCB& memory_allocator, + size_t msg_content_capacity); + + // Creates a message of type |type| whose serialized structure is stored + // in |mem|. + static scoped_ptr<MediaMessage> CreateMessage( + uint32 type, + scoped_ptr<MediaMemoryChunk> mem); + + // Creates a message from a memory area which already contains + // the serialized structure of the message. + // Only Read functions can be invoked on this type of message. + static scoped_ptr<MediaMessage> MapMessage( + scoped_ptr<MediaMemoryChunk> mem); + + // Return the minimum size of a message. + static size_t minimum_msg_size() { + return offsetof(SerializedMsg, content); + } + + ~MediaMessage(); + + // Indicate whether the underlying serialized structure of the message is + // available. + // Note: the serialized structure might be unavailable in case of a dummy + // message or if the underlying memory has been invalidated. + bool IsSerializedMsgAvailable() const; + + // Return the message and the total size of the message + // incuding the header, the content and the possible padding. + const void* msg() const { return msg_read_only_; } + size_t size() const { return cached_msg_.header.size; } + + // Return the size of the message without padding. + size_t actual_size() const { + return minimum_msg_size() + cached_msg_.header.content_size; + } + + // Return the size of the content of the message. + size_t content_size() const { return cached_msg_.header.content_size; } + + // Return the type of the message. + uint32 type() const { return cached_msg_.header.type; } + + // Append a POD to the message. + // Return true if the POD has been succesfully written. + template<typename T> bool WritePod(T* const& pod); + template<typename T> bool WritePod(const T& pod) { + return WriteBuffer(&pod, sizeof(T)); + } + + // Append a raw buffer to the message. + bool WriteBuffer(const void* src, size_t size); + + // Read a POD from the message. + template<typename T> bool ReadPod(T* pod) { + return ReadBuffer(pod, sizeof(T)); + } + + // Read |size| bytes from the message from the last read position + // and write it to |dst|. + bool ReadBuffer(void* dst, size_t size); + + // Return a pointer to a buffer of size |size|. + // Return NULL if not successful. + const void* GetBuffer(size_t size); + void* GetWritableBuffer(size_t size); + + private: + MediaMessage(uint32 type, size_t msg_size); + MediaMessage(uint32 type, scoped_ptr<MediaMemoryChunk> memory); + MediaMessage(scoped_ptr<MediaMemoryChunk> memory); + + struct Header { + // Total size of the message (including both header & content). + uint32 size; + // Indicate the message type. + uint32 type; + // Actual size of the content in the message. + uint32 content_size; + }; + + struct SerializedMsg { + // Message header. + Header header; + + // Start of the content of the message. + // Use uint8_t since no special alignment is needed. + uint8 content; + }; + + // Indicate whether the message is a dummy message, i.e. a message without + // a complete underlying serialized structure: only the message header is + // available. + bool is_dummy_msg_; + + // |cached_msg_| is used for 2 purposes: + // - to create a dummy message + // - for security purpose: cache the msg header to avoid browser security + // issues. + SerializedMsg cached_msg_; + Header* const cached_header_; + + SerializedMsg* msg_; + SerializedMsg* msg_read_only_; + + // Memory allocated to store the underlying serialized structure into memory. + // Note: a dummy message has no underlying serialized structure: + // |mem_| is a null pointer in that case. + scoped_ptr<MediaMemoryChunk> mem_; + + // Read iterator into the message. + size_t rd_offset_; + + DISALLOW_COPY_AND_ASSIGN(MediaMessage); +}; + +} // namespace media +} // namespace chromecast + +#endif diff --git a/chromecast/media/cma/ipc/media_message_fifo.cc b/chromecast/media/cma/ipc/media_message_fifo.cc new file mode 100644 index 0000000000..413afd267c --- /dev/null +++ b/chromecast/media/cma/ipc/media_message_fifo.cc @@ -0,0 +1,401 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chromecast/media/cma/ipc/media_message_fifo.h" + +#include "base/atomicops.h" +#include "base/bind.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/message_loop/message_loop_proxy.h" +#include "chromecast/media/cma/base/cma_logging.h" +#include "chromecast/media/cma/ipc/media_memory_chunk.h" +#include "chromecast/media/cma/ipc/media_message.h" +#include "chromecast/media/cma/ipc/media_message_type.h" + +namespace chromecast { +namespace media { + +class MediaMessageFlag + : public base::RefCountedThreadSafe<MediaMessageFlag> { + public: + // |offset| is the offset in the fifo of the media message. + explicit MediaMessageFlag(size_t offset); + + bool IsValid() const; + + void Invalidate(); + + size_t offset() const { return offset_; } + + private: + friend class base::RefCountedThreadSafe<MediaMessageFlag>; + virtual ~MediaMessageFlag(); + + const size_t offset_; + bool flag_; + + DISALLOW_COPY_AND_ASSIGN(MediaMessageFlag); +}; + +MediaMessageFlag::MediaMessageFlag(size_t offset) + : offset_(offset), + flag_(true) { +} + +MediaMessageFlag::~MediaMessageFlag() { +} + +bool MediaMessageFlag::IsValid() const { + return flag_; +} + +void MediaMessageFlag::Invalidate() { + flag_ = false; +} + +class FifoOwnedMemory : public MediaMemoryChunk { + public: + FifoOwnedMemory(void* data, size_t size, + const scoped_refptr<MediaMessageFlag>& flag, + const base::Closure& release_msg_cb); + virtual ~FifoOwnedMemory(); + + // MediaMemoryChunk implementation. + virtual void* data() const OVERRIDE { return data_; } + virtual size_t size() const OVERRIDE { return size_; } + virtual bool valid() const OVERRIDE { return flag_->IsValid(); } + + private: + scoped_refptr<base::SingleThreadTaskRunner> task_runner_; + base::Closure release_msg_cb_; + + void* const data_; + const size_t size_; + scoped_refptr<MediaMessageFlag> flag_; + + DISALLOW_COPY_AND_ASSIGN(FifoOwnedMemory); +}; + +FifoOwnedMemory::FifoOwnedMemory( + void* data, size_t size, + const scoped_refptr<MediaMessageFlag>& flag, + const base::Closure& release_msg_cb) + : task_runner_(base::MessageLoopProxy::current()), + release_msg_cb_(release_msg_cb), + data_(data), + size_(size), + flag_(flag) { +} + +FifoOwnedMemory::~FifoOwnedMemory() { + // Release the flag before notifying that the message has been released. + flag_ = scoped_refptr<MediaMessageFlag>(); + if (!release_msg_cb_.is_null()) { + if (task_runner_->BelongsToCurrentThread()) { + release_msg_cb_.Run(); + } else { + task_runner_->PostTask(FROM_HERE, release_msg_cb_); + } + } +} + +MediaMessageFifo::MediaMessageFifo( + scoped_ptr<MediaMemoryChunk> mem, bool init) + : mem_(mem.Pass()), + weak_factory_(this) { + CHECK_EQ(reinterpret_cast<uintptr_t>(mem_->data()) % ALIGNOF(Descriptor), + 0u); + CHECK_GE(mem_->size(), sizeof(Descriptor)); + Descriptor* desc = static_cast<Descriptor*>(mem_->data()); + base_ = static_cast<void*>(&desc->first_item); + + // TODO(damienv): remove cast when atomic size_t is defined in Chrome. + // Currently, the sign differs. + rd_offset_ = reinterpret_cast<AtomicSize*>(&(desc->rd_offset)); + wr_offset_ = reinterpret_cast<AtomicSize*>(&(desc->wr_offset)); + + size_t max_size = mem_->size() - + (static_cast<char*>(base_) - static_cast<char*>(mem_->data())); + if (init) { + size_ = max_size; + desc->size = size_; + internal_rd_offset_ = 0; + internal_wr_offset_ = 0; + base::subtle::Acquire_Store(rd_offset_, 0); + base::subtle::Acquire_Store(wr_offset_, 0); + } else { + size_ = desc->size; + CHECK_LE(size_, max_size); + internal_rd_offset_ = current_rd_offset(); + internal_wr_offset_ = current_wr_offset(); + } + CMALOG(kLogControl) + << "MediaMessageFifo:" << " init=" << init << " size=" << size_; + CHECK_GT(size_, 0) << size_; + + weak_this_ = weak_factory_.GetWeakPtr(); + thread_checker_.DetachFromThread(); +} + +MediaMessageFifo::~MediaMessageFifo() { + DCHECK(thread_checker_.CalledOnValidThread()); +} + +void MediaMessageFifo::ObserveReadActivity( + const base::Closure& read_event_cb) { + read_event_cb_ = read_event_cb; +} + +void MediaMessageFifo::ObserveWriteActivity( + const base::Closure& write_event_cb) { + write_event_cb_ = write_event_cb; +} + +scoped_ptr<MediaMemoryChunk> MediaMessageFifo::ReserveMemory( + size_t size_to_reserve) { + DCHECK(thread_checker_.CalledOnValidThread()); + + // Capture first both the read and write offsets. + // and exit right away if not enough free space. + size_t wr_offset = internal_wr_offset(); + size_t rd_offset = current_rd_offset(); + size_t allocated_size = (size_ + wr_offset - rd_offset) % size_; + size_t free_size = size_ - 1 - allocated_size; + if (free_size < size_to_reserve) + return scoped_ptr<MediaMemoryChunk>(); + CHECK_LE(MediaMessage::minimum_msg_size(), size_to_reserve); + + // Note: in the next 2 conditions, we have: + // trailing_byte_count < size_to_reserve + // and since at this stage: size_to_reserve <= free_size + // we also have trailing_byte_count <= free_size + // which means that all the trailing bytes are free space in the fifo. + size_t trailing_byte_count = size_ - wr_offset; + if (trailing_byte_count < MediaMessage::minimum_msg_size()) { + // If there is no space to even write the smallest message, + // skip the trailing bytes and come back to the beginning of the fifo. + // (no way to insert a padding message). + if (free_size < trailing_byte_count) + return scoped_ptr<MediaMemoryChunk>(); + wr_offset = 0; + CommitInternalWrite(wr_offset); + + } else if (trailing_byte_count < size_to_reserve) { + // At this point, we know we have at least the space to write a message. + // However, to avoid splitting a message, a padding message is needed. + scoped_ptr<MediaMemoryChunk> mem( + ReserveMemoryNoCheck(trailing_byte_count)); + scoped_ptr<MediaMessage> padding_message( + MediaMessage::CreateMessage(PaddingMediaMsg, mem.Pass())); + } + + // Recalculate the free size and exit if not enough free space. + wr_offset = internal_wr_offset(); + allocated_size = (size_ + wr_offset - rd_offset) % size_; + free_size = size_ - 1 - allocated_size; + if (free_size < size_to_reserve) + return scoped_ptr<MediaMemoryChunk>(); + + return ReserveMemoryNoCheck(size_to_reserve); +} + +scoped_ptr<MediaMessage> MediaMessageFifo::Pop() { + DCHECK(thread_checker_.CalledOnValidThread()); + + // Capture the read and write offsets. + size_t rd_offset = internal_rd_offset(); + size_t wr_offset = current_wr_offset(); + size_t allocated_size = (size_ + wr_offset - rd_offset) % size_; + + if (allocated_size < MediaMessage::minimum_msg_size()) + return scoped_ptr<MediaMessage>(); + + size_t trailing_byte_count = size_ - rd_offset; + if (trailing_byte_count < MediaMessage::minimum_msg_size()) { + // If there is no space to even have the smallest message, + // skip the trailing bytes and come back to the beginning of the fifo. + // Note: all the trailing bytes correspond to allocated bytes since: + // trailing_byte_count < MediaMessage::minimum_msg_size() <= allocated_size + rd_offset = 0; + allocated_size -= trailing_byte_count; + trailing_byte_count = size_; + CommitInternalRead(rd_offset); + } + + // The message should not be longer than the allocated size + // but since a message is a contiguous area of memory, it should also be + // smaller than |trailing_byte_count|. + size_t max_msg_size = std::min(allocated_size, trailing_byte_count); + if (max_msg_size < MediaMessage::minimum_msg_size()) + return scoped_ptr<MediaMessage>(); + void* msg_src = static_cast<uint8*>(base_) + rd_offset; + + // Create a flag to protect the serialized structure of the message + // from being overwritten. + // The serialized structure starts at offset |rd_offset|. + scoped_refptr<MediaMessageFlag> rd_flag(new MediaMessageFlag(rd_offset)); + rd_flags_.push_back(rd_flag); + scoped_ptr<MediaMemoryChunk> mem( + new FifoOwnedMemory( + msg_src, max_msg_size, rd_flag, + base::Bind(&MediaMessageFifo::OnRdMemoryReleased, weak_this_))); + + // Create the message which wraps its the serialized structure. + scoped_ptr<MediaMessage> message(MediaMessage::MapMessage(mem.Pass())); + CHECK(message); + + // Update the internal read pointer. + rd_offset = (rd_offset + message->size()) % size_; + CommitInternalRead(rd_offset); + + return message.Pass(); +} + +void MediaMessageFifo::Flush() { + DCHECK(thread_checker_.CalledOnValidThread()); + + size_t wr_offset = current_wr_offset(); + + // Invalidate every memory region before flushing. + while (!rd_flags_.empty()) { + CMALOG(kLogControl) << "Invalidate flag"; + rd_flags_.front()->Invalidate(); + rd_flags_.pop_front(); + } + + // Flush by setting the read pointer to the value of the write pointer. + // Update first the internal read pointer then the public one. + CommitInternalRead(wr_offset); + CommitRead(wr_offset); +} + +scoped_ptr<MediaMemoryChunk> MediaMessageFifo::ReserveMemoryNoCheck( + size_t size_to_reserve) { + size_t wr_offset = internal_wr_offset(); + + // Memory block corresponding to the serialized structure of the message. + void* msg_start = static_cast<uint8*>(base_) + wr_offset; + scoped_refptr<MediaMessageFlag> wr_flag(new MediaMessageFlag(wr_offset)); + wr_flags_.push_back(wr_flag); + scoped_ptr<MediaMemoryChunk> mem( + new FifoOwnedMemory( + msg_start, size_to_reserve, wr_flag, + base::Bind(&MediaMessageFifo::OnWrMemoryReleased, weak_this_))); + + // Update the internal write pointer. + wr_offset = (wr_offset + size_to_reserve) % size_; + CommitInternalWrite(wr_offset); + + return mem.Pass(); +} + +void MediaMessageFifo::OnWrMemoryReleased() { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (wr_flags_.empty()) { + // Sanity check: when there is no protected memory area, + // the external write offset has no reason to be different from + // the internal write offset. + DCHECK_EQ(current_wr_offset(), internal_wr_offset()); + return; + } + + // Update the external write offset. + while (!wr_flags_.empty() && + (!wr_flags_.front()->IsValid() || wr_flags_.front()->HasOneRef())) { + // TODO(damienv): Could add a sanity check to make sure the offset is + // between the external write offset and the read offset (not included). + wr_flags_.pop_front(); + } + + // Update the read offset to the first locked memory area + // or to the internal read pointer if nothing prevents it. + size_t external_wr_offset = internal_wr_offset(); + if (!wr_flags_.empty()) + external_wr_offset = wr_flags_.front()->offset(); + CommitWrite(external_wr_offset); +} + +void MediaMessageFifo::OnRdMemoryReleased() { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (rd_flags_.empty()) { + // Sanity check: when there is no protected memory area, + // the external read offset has no reason to be different from + // the internal read offset. + DCHECK_EQ(current_rd_offset(), internal_rd_offset()); + return; + } + + // Update the external read offset. + while (!rd_flags_.empty() && + (!rd_flags_.front()->IsValid() || rd_flags_.front()->HasOneRef())) { + // TODO(damienv): Could add a sanity check to make sure the offset is + // between the external read offset and the write offset. + rd_flags_.pop_front(); + } + + // Update the read offset to the first locked memory area + // or to the internal read pointer if nothing prevents it. + size_t external_rd_offset = internal_rd_offset(); + if (!rd_flags_.empty()) + external_rd_offset = rd_flags_.front()->offset(); + CommitRead(external_rd_offset); +} + +size_t MediaMessageFifo::current_rd_offset() const { + DCHECK_EQ(sizeof(size_t), sizeof(AtomicSize)); + size_t rd_offset = base::subtle::Acquire_Load(rd_offset_); + CHECK_LT(rd_offset, size_); + return rd_offset; +} + +size_t MediaMessageFifo::current_wr_offset() const { + DCHECK_EQ(sizeof(size_t), sizeof(AtomicSize)); + + // When the fifo consumer acquires the write offset, + // we have to make sure that any possible following reads are actually + // returning results at least inline with the memory snapshot taken + // when the write offset was sampled. + // That's why an Acquire_Load is used here. + size_t wr_offset = base::subtle::Acquire_Load(wr_offset_); + CHECK_LT(wr_offset, size_); + return wr_offset; +} + +void MediaMessageFifo::CommitRead(size_t new_rd_offset) { + // Add a memory fence to ensure the message content is completely read + // before updating the read offset. + base::subtle::Release_Store(rd_offset_, new_rd_offset); + + // Make sure the read pointer has been updated before sending a notification. + if (!read_event_cb_.is_null()) { + base::subtle::MemoryBarrier(); + read_event_cb_.Run(); + } +} + +void MediaMessageFifo::CommitWrite(size_t new_wr_offset) { + // Add a memory fence to ensure the message content is written + // before updating the write offset. + base::subtle::Release_Store(wr_offset_, new_wr_offset); + + // Make sure the write pointer has been updated before sending a notification. + if (!write_event_cb_.is_null()) { + base::subtle::MemoryBarrier(); + write_event_cb_.Run(); + } +} + +void MediaMessageFifo::CommitInternalRead(size_t new_rd_offset) { + internal_rd_offset_ = new_rd_offset; +} + +void MediaMessageFifo::CommitInternalWrite(size_t new_wr_offset) { + internal_wr_offset_ = new_wr_offset; +} + +} // namespace media +} // namespace chromecast diff --git a/chromecast/media/cma/ipc/media_message_fifo.h b/chromecast/media/cma/ipc/media_message_fifo.h new file mode 100644 index 0000000000..fe82721a9b --- /dev/null +++ b/chromecast/media/cma/ipc/media_message_fifo.h @@ -0,0 +1,208 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROMECAST_MEDIA_CMA_IPC_MEDIA_MESSAGE_FIFO_H_ +#define CHROMECAST_MEDIA_CMA_IPC_MEDIA_MESSAGE_FIFO_H_ + +#include <list> + +#include "base/atomicops.h" +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/threading/thread_checker.h" + +namespace chromecast { +namespace media { +class MediaMemoryChunk; +class MediaMessage; +class MediaMessageFlag; + +// MediaMessageFifo is a lock free fifo implementation +// to pass audio/video data from one thread to another or from one process +// to another one (in that case using shared memory). +// +// Assuming the feeder and the consumer have a common block of shared memory +// (representing the serialized structure of the fifo), +// the feeder (which must be running on a single thread) instantiates its own +// instance of MediaMessageFifo, same applies to the consumer. +// +// Example: assuming the block of shared memory is given by |mem|, a typical +// feeder (using MediaMessageFifo instance fifo_feeder) will push messages +// in the following way: +// // Create a dummy message to calculate the size of the serialized message. +// scoped_ptr<MediaMessage> dummy_msg( +// MediaMessage::CreateDummyMessage(msg_type)); +// // ... +// // Write all the fields to the dummy message. +// // ... +// +// // Create the real message, once the size of the serialized message +// // is known. +// scoped_ptr<MediaMessage> msg( +// MediaMessage::CreateMessage( +// msg_type, +// base::Bind(&MediaMessageFifo::ReserveMemory, +// base::Unretained(fifo_feeder.get())), +// dummy_msg->content_size())); +// if (!msg) { +// // Not enough space for the message: +// // retry later (e.g. when receiving a read activity event, meaning +// // some enough space might have been release). +// return; +// } +// // ... +// // Write all the fields to the real message +// // in exactly the same way it was done for the dummy message. +// // ... +// // Once message |msg| is going out of scope, then MediaMessageFifo +// // fifo_feeder is informed that the message is not needed anymore +// // (i.e. it was fully written), and fifo_feeder can then update +// // the external write pointer of the fifo so that the consumer +// // can start consuming this message. +// +// A typical consumer (using MediaMessageFifo instance fifo_consumer) +// will retrive messages in the following way: +// scoped_ptr<MediaMessage> msg(fifo_consumer->Pop()); +// if (!msg) { +// // The fifo is empty, i.e. no message left. +// // Try reading again later (e.g. after receiving a write activity event. +// return; +// } +// // Parse the message using Read functions of MediaMessage: +// // ... +// // Once the message is going out of scope, MediaMessageFifo will receive +// // a notification that the underlying memory can be released +// // (i.e. the external read pointer can be updated). +// +// +class MediaMessageFifo { + public: + // Creates a media message fifo using |mem| as the underlying serialized + // structure. + // If |init| is true, the underlying fifo structure is initialized. + MediaMessageFifo(scoped_ptr<MediaMemoryChunk> mem, bool init); + ~MediaMessageFifo(); + + // When the consumer and the feeder are living in two different processes, + // we might want to convey some messages between these two processes to notify + // about some fifo activity. + void ObserveReadActivity(const base::Closure& read_event_cb); + void ObserveWriteActivity(const base::Closure& write_event_cb); + + // Reserves a writeable block of memory at the back of the fifo, + // corresponding to the serialized structure of the message. + // Returns NULL if the required size cannot be allocated. + scoped_ptr<MediaMemoryChunk> ReserveMemory(size_t size); + + // Pop a message from the queue. + // Returns a null pointer if there is no message left. + scoped_ptr<MediaMessage> Pop(); + + // Flush the fifo. + void Flush(); + + private: + struct Descriptor { + size_t size; + size_t rd_offset; + size_t wr_offset; + + // Ensure the first item has the same alignment as an int64. + int64 first_item; + }; + + // Add some accessors to ensure security on the browser process side. + size_t current_rd_offset() const; + size_t current_wr_offset() const; + size_t internal_rd_offset() const { + DCHECK_LT(internal_rd_offset_, size_); + return internal_rd_offset_; + } + size_t internal_wr_offset() const { + DCHECK_LT(internal_wr_offset_, size_); + return internal_wr_offset_; + } + + // Reserve a block of free memory without doing any check on the available + // space. Invoke this function only when all the checks have been done. + scoped_ptr<MediaMemoryChunk> ReserveMemoryNoCheck(size_t size); + + // Invoked each time there is a memory region in the free space of the fifo + // that has possibly been written. + void OnWrMemoryReleased(); + + // Invoked each time there is a memory region in the allocated space + // of the fifo that has possibly been released. + void OnRdMemoryReleased(); + + // Functions to modify the internal/external read/write pointers. + void CommitRead(size_t new_rd_offset); + void CommitWrite(size_t new_wr_offset); + void CommitInternalRead(size_t new_rd_offset); + void CommitInternalWrite(size_t new_wr_offset); + + // An instance of MediaMessageFifo must be running on a single thread. + // If the fifo feeder and consumer are living on 2 different threads + // or 2 different processes, they must create their own instance + // of MediaMessageFifo using the same underlying block of (shared) memory + // in the constructor. + base::ThreadChecker thread_checker_; + + // Callbacks invoked to notify either of some read or write activity on the + // fifo. This is especially useful when the feeder and consumer are living in + // two different processes. + base::Closure read_event_cb_; + base::Closure write_event_cb_; + + // The serialized structure of the fifo. + scoped_ptr<MediaMemoryChunk> mem_; + + // The size in bytes of the fifo is cached locally for security purpose. + // (the renderer process cannot modify the size and make the browser process + // access out of range addresses). + size_t size_; + + // TODO(damienv): This is a work-around since atomicops.h does not define + // an atomic size_t type. +#if SIZE_MAX == UINT32_MAX + typedef base::subtle::Atomic32 AtomicSize; +#elif SIZE_MAX == UINT64_MAX + typedef base::subtle::Atomic64 AtomicSize; +#elif +#error "Unsupported size_t" +#endif + AtomicSize* rd_offset_; + AtomicSize* wr_offset_; + + // Internal read offset: this is where data is actually read from. + // The external offset |rd_offset_| is only used to protect data from being + // overwritten by the feeder. + // At any time, the internal read pointer must be between the external read + // offset and the write offset (circular fifo definition of "between"). + size_t internal_rd_offset_; + size_t internal_wr_offset_; + + // Note: all the memory read/write are followed by a memory fence before + // updating the rd/wr pointer. + void* base_; + + // Protects the messages that are either being read or written. + std::list<scoped_refptr<MediaMessageFlag> > rd_flags_; + std::list<scoped_refptr<MediaMessageFlag> > wr_flags_; + + base::WeakPtrFactory<MediaMessageFifo> weak_factory_; + base::WeakPtr<MediaMessageFifo> weak_this_; + + DISALLOW_COPY_AND_ASSIGN(MediaMessageFifo); +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_IPC_MEDIA_MESSAGE_FIFO_H_ diff --git a/chromecast/media/cma/ipc/media_message_fifo_unittest.cc b/chromecast/media/cma/ipc/media_message_fifo_unittest.cc new file mode 100644 index 0000000000..bb1eef3848 --- /dev/null +++ b/chromecast/media/cma/ipc/media_message_fifo_unittest.cc @@ -0,0 +1,195 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/memory/scoped_ptr.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/thread.h" +#include "chromecast/media/cma/ipc/media_memory_chunk.h" +#include "chromecast/media/cma/ipc/media_message.h" +#include "chromecast/media/cma/ipc/media_message_fifo.h" +#include "chromecast/media/cma/ipc/media_message_type.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace chromecast { +namespace media { + +namespace { + +class FifoMemoryChunk : public MediaMemoryChunk { + public: + FifoMemoryChunk(void* mem, size_t size) + : mem_(mem), size_(size) {} + virtual ~FifoMemoryChunk() {} + + virtual void* data() const OVERRIDE { return mem_; } + virtual size_t size() const OVERRIDE { return size_; } + virtual bool valid() const OVERRIDE { return true; } + + private: + void* mem_; + size_t size_; + + DISALLOW_COPY_AND_ASSIGN(FifoMemoryChunk); +}; + +void MsgProducer(scoped_ptr<MediaMessageFifo> fifo, + int msg_count, + base::WaitableEvent* event) { + + for (int k = 0; k < msg_count; k++) { + uint32 msg_type = 0x2 + (k % 5); + uint32 max_msg_content_size = k % 64; + do { + scoped_ptr<MediaMessage> msg1( + MediaMessage::CreateMessage( + msg_type, + base::Bind(&MediaMessageFifo::ReserveMemory, + base::Unretained(fifo.get())), + max_msg_content_size)); + if (msg1) + break; + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(10)); + } while(true); + } + + fifo.reset(); + + event->Signal(); +} + +void MsgConsumer(scoped_ptr<MediaMessageFifo> fifo, + int msg_count, + base::WaitableEvent* event) { + + int k = 0; + while (k < msg_count) { + uint32 msg_type = 0x2 + (k % 5); + do { + scoped_ptr<MediaMessage> msg2(fifo->Pop()); + if (msg2) { + if (msg2->type() != PaddingMediaMsg) { + EXPECT_EQ(msg2->type(), msg_type); + k++; + } + break; + } + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(10)); + } while(true); + } + + fifo.reset(); + + event->Signal(); +} + +void MsgProducerConsumer( + scoped_ptr<MediaMessageFifo> producer_fifo, + scoped_ptr<MediaMessageFifo> consumer_fifo, + base::WaitableEvent* event) { + for (int k = 0; k < 2048; k++) { + // Should have enough space to create a message. + uint32 msg_type = 0x2 + (k % 5); + uint32 max_msg_content_size = k % 64; + scoped_ptr<MediaMessage> msg1( + MediaMessage::CreateMessage( + msg_type, + base::Bind(&MediaMessageFifo::ReserveMemory, + base::Unretained(producer_fifo.get())), + max_msg_content_size)); + EXPECT_TRUE(msg1); + + // Make sure the message is commited. + msg1.reset(); + + // At this point, we should have a message to read. + scoped_ptr<MediaMessage> msg2(consumer_fifo->Pop()); + EXPECT_TRUE(msg2); + } + + producer_fifo.reset(); + consumer_fifo.reset(); + + event->Signal(); +} + +} // namespace + +TEST(MediaMessageFifoTest, AlternateWriteRead) { + size_t buffer_size = 64 * 1024; + scoped_ptr<uint64[]> buffer(new uint64[buffer_size / sizeof(uint64)]); + + scoped_ptr<base::Thread> thread( + new base::Thread("FeederConsumerThread")); + thread->Start(); + + scoped_ptr<MediaMessageFifo> producer_fifo(new MediaMessageFifo( + scoped_ptr<MediaMemoryChunk>( + new FifoMemoryChunk(&buffer[0], buffer_size)), + true)); + scoped_ptr<MediaMessageFifo> consumer_fifo(new MediaMessageFifo( + scoped_ptr<MediaMemoryChunk>( + new FifoMemoryChunk(&buffer[0], buffer_size)), + false)); + + base::WaitableEvent event(false, false); + thread->message_loop_proxy()->PostTask( + FROM_HERE, + base::Bind(&MsgProducerConsumer, + base::Passed(&producer_fifo), + base::Passed(&consumer_fifo), + &event)); + event.Wait(); + + thread.reset(); +} + +TEST(MediaMessageFifoTest, MultiThreaded) { + size_t buffer_size = 64 * 1024; + scoped_ptr<uint64[]> buffer(new uint64[buffer_size / sizeof(uint64)]); + + scoped_ptr<base::Thread> producer_thread( + new base::Thread("FeederThread")); + scoped_ptr<base::Thread> consumer_thread( + new base::Thread("ConsumerThread")); + producer_thread->Start(); + consumer_thread->Start(); + + scoped_ptr<MediaMessageFifo> producer_fifo(new MediaMessageFifo( + scoped_ptr<MediaMemoryChunk>( + new FifoMemoryChunk(&buffer[0], buffer_size)), + true)); + scoped_ptr<MediaMessageFifo> consumer_fifo(new MediaMessageFifo( + scoped_ptr<MediaMemoryChunk>( + new FifoMemoryChunk(&buffer[0], buffer_size)), + false)); + + base::WaitableEvent producer_event_done(false, false); + base::WaitableEvent consumer_event_done(false, false); + + const int msg_count = 2048; + producer_thread->message_loop_proxy()->PostTask( + FROM_HERE, + base::Bind(&MsgProducer, + base::Passed(&producer_fifo), + msg_count, + &producer_event_done)); + consumer_thread->message_loop_proxy()->PostTask( + FROM_HERE, + base::Bind(&MsgConsumer, + base::Passed(&consumer_fifo), + msg_count, + &consumer_event_done)); + + producer_event_done.Wait(); + consumer_event_done.Wait(); + + producer_thread.reset(); + consumer_thread.reset(); +} + +} // namespace media +} // namespace chromecast + diff --git a/chromecast/media/cma/ipc/media_message_type.h b/chromecast/media/cma/ipc/media_message_type.h new file mode 100644 index 0000000000..23ff8474d3 --- /dev/null +++ b/chromecast/media/cma/ipc/media_message_type.h @@ -0,0 +1,15 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROMECAST_MEDIA_CMA_IPC_MEDIA_MESSAGE_TYPE_H_ +#define CHROMECAST_MEDIA_CMA_IPC_MEDIA_MESSAGE_TYPE_H_ + +enum MediaMessageType { + PaddingMediaMsg = 1, + FrameMediaMsg, + AudioConfigMediaMsg, + VideoConfigMediaMsg, +}; + +#endif diff --git a/chromecast/media/cma/ipc/media_message_unittest.cc b/chromecast/media/cma/ipc/media_message_unittest.cc new file mode 100644 index 0000000000..07d9655bfb --- /dev/null +++ b/chromecast/media/cma/ipc/media_message_unittest.cc @@ -0,0 +1,146 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/memory/scoped_ptr.h" +#include "chromecast/media/cma/ipc/media_memory_chunk.h" +#include "chromecast/media/cma/ipc/media_message.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace chromecast { +namespace media { + +namespace { + +class ExternalMemoryBlock + : public MediaMemoryChunk { + public: + ExternalMemoryBlock(void* data, size_t size) + : data_(data), size_(size) {} + virtual ~ExternalMemoryBlock() {} + + // MediaMemoryChunk implementation. + virtual void* data() const OVERRIDE { return data_; } + virtual size_t size() const OVERRIDE { return size_; } + virtual bool valid() const OVERRIDE { return true; } + + private: + void* const data_; + const size_t size_; +}; + +scoped_ptr<MediaMemoryChunk> DummyAllocator( + void* data, size_t size, size_t alloc_size) { + CHECK_LE(alloc_size, size); + return scoped_ptr<MediaMemoryChunk>( + new ExternalMemoryBlock(data, alloc_size)); +} + +} + +TEST(MediaMessageTest, WriteRead) { + int buffer_size = 1024; + scoped_ptr<uint8[]> buffer(new uint8[buffer_size]); + MediaMessage::MemoryAllocatorCB mem_alloc_cb( + base::Bind(&DummyAllocator, buffer.get(), buffer_size)); + uint32 type = 0x1; + int msg_content_capacity = 512; + + // Write a message. + int count = 64; + scoped_ptr<MediaMessage> msg1( + MediaMessage::CreateMessage(type, mem_alloc_cb, msg_content_capacity)); + for (int k = 0; k < count; k++) { + int v1 = 2 * k + 1; + EXPECT_TRUE(msg1->WritePod(v1)); + uint8 v2 = k; + EXPECT_TRUE(msg1->WritePod(v2)); + } + EXPECT_EQ(msg1->content_size(), count * (sizeof(int) + sizeof(uint8))); + + // Verify the integrity of the message. + scoped_ptr<MediaMessage> msg2( + MediaMessage::MapMessage(scoped_ptr<MediaMemoryChunk>( + new ExternalMemoryBlock(&buffer[0], buffer_size)))); + for (int k = 0; k < count; k++) { + int v1; + int expected_v1 = 2 * k + 1; + EXPECT_TRUE(msg2->ReadPod(&v1)); + EXPECT_EQ(v1, expected_v1); + uint8 v2; + uint8 expected_v2 = k; + EXPECT_TRUE(msg2->ReadPod(&v2)); + EXPECT_EQ(v2, expected_v2); + } +} + +TEST(MediaMessageTest, WriteOverflow) { + int buffer_size = 1024; + scoped_ptr<uint8[]> buffer(new uint8[buffer_size]); + MediaMessage::MemoryAllocatorCB mem_alloc_cb( + base::Bind(&DummyAllocator, buffer.get(), buffer_size)); + uint32 type = 0x1; + int msg_content_capacity = 8; + + scoped_ptr<MediaMessage> msg1( + MediaMessage::CreateMessage(type, mem_alloc_cb, msg_content_capacity)); + uint32 v1 = 0; + uint8 v2 = 0; + EXPECT_TRUE(msg1->WritePod(v1)); + EXPECT_TRUE(msg1->WritePod(v1)); + + EXPECT_FALSE(msg1->WritePod(v1)); + EXPECT_FALSE(msg1->WritePod(v2)); +} + +TEST(MediaMessageTest, ReadOverflow) { + int buffer_size = 1024; + scoped_ptr<uint8[]> buffer(new uint8[buffer_size]); + MediaMessage::MemoryAllocatorCB mem_alloc_cb( + base::Bind(&DummyAllocator, buffer.get(), buffer_size)); + uint32 type = 0x1; + int msg_content_capacity = 8; + + scoped_ptr<MediaMessage> msg1( + MediaMessage::CreateMessage(type, mem_alloc_cb, msg_content_capacity)); + uint32 v1 = 0xcd; + EXPECT_TRUE(msg1->WritePod(v1)); + EXPECT_TRUE(msg1->WritePod(v1)); + + scoped_ptr<MediaMessage> msg2( + MediaMessage::MapMessage(scoped_ptr<MediaMemoryChunk>( + new ExternalMemoryBlock(&buffer[0], buffer_size)))); + uint32 v2; + EXPECT_TRUE(msg2->ReadPod(&v2)); + EXPECT_EQ(v2, v1); + EXPECT_TRUE(msg2->ReadPod(&v2)); + EXPECT_EQ(v2, v1); + EXPECT_FALSE(msg2->ReadPod(&v2)); +} + +TEST(MediaMessageTest, DummyMessage) { + int buffer_size = 1024; + scoped_ptr<uint8[]> buffer(new uint8[buffer_size]); + MediaMessage::MemoryAllocatorCB mem_alloc_cb( + base::Bind(&DummyAllocator, buffer.get(), buffer_size)); + uint32 type = 0x1; + + // Create first a dummy message to estimate the content size. + scoped_ptr<MediaMessage> msg1( + MediaMessage::CreateDummyMessage(type)); + uint32 v1 = 0xcd; + EXPECT_TRUE(msg1->WritePod(v1)); + EXPECT_TRUE(msg1->WritePod(v1)); + + // Create the real message and write the actual content. + scoped_ptr<MediaMessage> msg2( + MediaMessage::CreateMessage(type, mem_alloc_cb, msg1->content_size())); + EXPECT_TRUE(msg2->WritePod(v1)); + EXPECT_TRUE(msg2->WritePod(v1)); + EXPECT_FALSE(msg2->WritePod(v1)); +} + +} // namespace media +} // namespace chromecast diff --git a/chromecast/media/cma/ipc_streamer/audio_decoder_config_marshaller.cc b/chromecast/media/cma/ipc_streamer/audio_decoder_config_marshaller.cc new file mode 100644 index 0000000000..d595bdaac3 --- /dev/null +++ b/chromecast/media/cma/ipc_streamer/audio_decoder_config_marshaller.cc @@ -0,0 +1,70 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chromecast/media/cma/ipc_streamer/audio_decoder_config_marshaller.h" + +#include "base/basictypes.h" +#include "base/logging.h" +#include "chromecast/media/cma/ipc/media_message.h" +#include "media/base/audio_decoder_config.h" + +namespace chromecast { +namespace media { + +namespace { +const size_t kMaxExtraDataSize = 16 * 1024; +} + +// static +void AudioDecoderConfigMarshaller::Write( + const ::media::AudioDecoderConfig& config, MediaMessage* msg) { + CHECK(msg->WritePod(config.codec())); + CHECK(msg->WritePod(config.channel_layout())); + CHECK(msg->WritePod(config.samples_per_second())); + CHECK(msg->WritePod(config.sample_format())); + CHECK(msg->WritePod(config.is_encrypted())); + CHECK(msg->WritePod(config.extra_data_size())); + if (config.extra_data_size() > 0) + CHECK(msg->WriteBuffer(config.extra_data(), config.extra_data_size())); +} + +// static +::media::AudioDecoderConfig AudioDecoderConfigMarshaller::Read( + MediaMessage* msg) { + ::media::AudioCodec codec; + ::media::SampleFormat sample_format; + ::media::ChannelLayout channel_layout; + int samples_per_second; + bool is_encrypted; + size_t extra_data_size; + scoped_ptr<uint8[]> extra_data; + + CHECK(msg->ReadPod(&codec)); + CHECK(msg->ReadPod(&channel_layout)); + CHECK(msg->ReadPod(&samples_per_second)); + CHECK(msg->ReadPod(&sample_format)); + CHECK(msg->ReadPod(&is_encrypted)); + CHECK(msg->ReadPod(&extra_data_size)); + + CHECK_GE(codec, ::media::kUnknownAudioCodec); + CHECK_LE(codec, ::media::kAudioCodecMax); + CHECK_GE(channel_layout, ::media::CHANNEL_LAYOUT_NONE); + CHECK_LE(channel_layout, ::media::CHANNEL_LAYOUT_MAX); + CHECK_GE(sample_format, ::media::kUnknownSampleFormat); + CHECK_LE(sample_format, ::media::kSampleFormatMax); + CHECK_LT(extra_data_size, kMaxExtraDataSize); + if (extra_data_size > 0) { + extra_data.reset(new uint8[extra_data_size]); + CHECK(msg->ReadBuffer(extra_data.get(), extra_data_size)); + } + + return ::media::AudioDecoderConfig( + codec, sample_format, + channel_layout, samples_per_second, + extra_data.get(), extra_data_size, + is_encrypted); +} + +} // namespace media +} // namespace chromecast diff --git a/chromecast/media/cma/ipc_streamer/audio_decoder_config_marshaller.h b/chromecast/media/cma/ipc_streamer/audio_decoder_config_marshaller.h new file mode 100644 index 0000000000..33f960e0c8 --- /dev/null +++ b/chromecast/media/cma/ipc_streamer/audio_decoder_config_marshaller.h @@ -0,0 +1,27 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROMECAST_MEDIA_CMA_IPC_STREAMER_AUDIO_DECODER_CONFIG_MARSHALLER_H_ +#define CHROMECAST_MEDIA_CMA_IPC_STREAMER_AUDIO_DECODER_CONFIG_MARSHALLER_H_ + +#include "media/base/audio_decoder_config.h" + +namespace chromecast { +namespace media { +class MediaMessage; + +class AudioDecoderConfigMarshaller { + public: + // Writes the serialized structure of |config| into |msg|. + static void Write( + const ::media::AudioDecoderConfig& config, MediaMessage* msg); + + // Returns an AudioDecoderConfig from its serialized structure. + static ::media::AudioDecoderConfig Read(MediaMessage* msg); +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_IPC_STREAMER_AUDIO_DECODER_CONFIG_MARSHALLER_H_ diff --git a/chromecast/media/cma/ipc_streamer/av_streamer_proxy.cc b/chromecast/media/cma/ipc_streamer/av_streamer_proxy.cc new file mode 100644 index 0000000000..de8dd54f6b --- /dev/null +++ b/chromecast/media/cma/ipc_streamer/av_streamer_proxy.cc @@ -0,0 +1,200 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chromecast/media/cma/ipc_streamer/av_streamer_proxy.h" + +#include "base/bind.h" +#include "base/location.h" +#include "base/message_loop/message_loop_proxy.h" +#include "chromecast/media/cma/base/coded_frame_provider.h" +#include "chromecast/media/cma/base/decoder_buffer_base.h" +#include "chromecast/media/cma/ipc/media_memory_chunk.h" +#include "chromecast/media/cma/ipc/media_message.h" +#include "chromecast/media/cma/ipc/media_message_fifo.h" +#include "chromecast/media/cma/ipc/media_message_type.h" +#include "chromecast/media/cma/ipc_streamer/audio_decoder_config_marshaller.h" +#include "chromecast/media/cma/ipc_streamer/decoder_buffer_base_marshaller.h" +#include "chromecast/media/cma/ipc_streamer/video_decoder_config_marshaller.h" + +namespace chromecast { +namespace media { + +AvStreamerProxy::AvStreamerProxy() + : is_running_(false), + pending_read_(false), + pending_av_data_(false), + weak_factory_(this), + weak_this_(weak_factory_.GetWeakPtr()) { + thread_checker_.DetachFromThread(); +} + +AvStreamerProxy::~AvStreamerProxy() { + DCHECK(thread_checker_.CalledOnValidThread()); +} + +void AvStreamerProxy::SetCodedFrameProvider( + scoped_ptr<CodedFrameProvider> frame_provider) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!frame_provider_); + frame_provider_.reset(frame_provider.release()); +} + +void AvStreamerProxy::SetMediaMessageFifo( + scoped_ptr<MediaMessageFifo> fifo) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!fifo_); + fifo_.reset(fifo.release()); +} + +void AvStreamerProxy::Start() { + DCHECK(!is_running_); + + is_running_ = true; + RequestBufferIfNeeded(); +} + +void AvStreamerProxy::StopAndFlush(const base::Closure& done_cb) { + is_running_ = false; + + pending_av_data_ = false; + pending_audio_config_ = ::media::AudioDecoderConfig(); + pending_video_config_ = ::media::VideoDecoderConfig(); + pending_buffer_ = scoped_refptr<DecoderBufferBase>(); + + pending_read_ = false; + frame_provider_->Flush(done_cb); +} + +void AvStreamerProxy::OnFifoReadEvent() { + DCHECK(thread_checker_.CalledOnValidThread()); + + // Some enough space might have been released + // to accommodate the pending data. + if (pending_av_data_) + ProcessPendingData(); +} + +void AvStreamerProxy::RequestBufferIfNeeded() { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (!is_running_ || pending_read_ || pending_av_data_) + return; + + // |frame_provider_| is assumed to run on the same message loop. + // Add a BindToCurrentLoop if that's not the case in the future. + pending_read_ = true; + frame_provider_->Read(base::Bind(&AvStreamerProxy::OnNewBuffer, weak_this_)); +} + +void AvStreamerProxy::OnNewBuffer( + const scoped_refptr<DecoderBufferBase>& buffer, + const ::media::AudioDecoderConfig& audio_config, + const ::media::VideoDecoderConfig& video_config) { + DCHECK(thread_checker_.CalledOnValidThread()); + + pending_read_ = false; + + if (buffer->end_of_stream()) + is_running_ = false; + + DCHECK(!pending_av_data_); + pending_av_data_ = true; + + pending_buffer_ = buffer; + pending_audio_config_ = audio_config; + pending_video_config_ = video_config; + + ProcessPendingData(); +} + +void AvStreamerProxy::ProcessPendingData() { + if (pending_audio_config_.IsValidConfig()) { + if (!SendAudioDecoderConfig(pending_audio_config_)) + return; + pending_audio_config_ = ::media::AudioDecoderConfig(); + } + + if (pending_video_config_.IsValidConfig()) { + if (!SendVideoDecoderConfig(pending_video_config_)) + return; + pending_video_config_ = ::media::VideoDecoderConfig(); + } + + if (pending_buffer_.get()) { + if (!SendBuffer(pending_buffer_)) + return; + pending_buffer_ = scoped_refptr<DecoderBufferBase>(); + } + + pending_av_data_ = false; + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, + base::Bind(&AvStreamerProxy::RequestBufferIfNeeded, weak_this_)); +} + +bool AvStreamerProxy::SendAudioDecoderConfig( + const ::media::AudioDecoderConfig& config) { + // Create a dummy message to calculate first the message size. + scoped_ptr<MediaMessage> dummy_msg( + MediaMessage::CreateDummyMessage(AudioConfigMediaMsg)); + AudioDecoderConfigMarshaller::Write(config, dummy_msg.get()); + + // Create the real message and write the actual content. + scoped_ptr<MediaMessage> msg( + MediaMessage::CreateMessage( + AudioConfigMediaMsg, + base::Bind(&MediaMessageFifo::ReserveMemory, + base::Unretained(fifo_.get())), + dummy_msg->content_size())); + if (!msg) + return false; + + AudioDecoderConfigMarshaller::Write(config, msg.get()); + return true; +} + +bool AvStreamerProxy::SendVideoDecoderConfig( + const ::media::VideoDecoderConfig& config) { + // Create a dummy message to calculate first the message size. + scoped_ptr<MediaMessage> dummy_msg( + MediaMessage::CreateDummyMessage(VideoConfigMediaMsg)); + VideoDecoderConfigMarshaller::Write(config, dummy_msg.get()); + + // Create the real message and write the actual content. + scoped_ptr<MediaMessage> msg( + MediaMessage::CreateMessage( + VideoConfigMediaMsg, + base::Bind(&MediaMessageFifo::ReserveMemory, + base::Unretained(fifo_.get())), + dummy_msg->content_size())); + if (!msg) + return false; + + VideoDecoderConfigMarshaller::Write(config, msg.get()); + return true; +} + +bool AvStreamerProxy::SendBuffer( + const scoped_refptr<DecoderBufferBase>& buffer) { + // Create a dummy message to calculate first the message size. + scoped_ptr<MediaMessage> dummy_msg( + MediaMessage::CreateDummyMessage(FrameMediaMsg)); + DecoderBufferBaseMarshaller::Write(buffer, dummy_msg.get()); + + // Create the real message and write the actual content. + scoped_ptr<MediaMessage> msg( + MediaMessage::CreateMessage( + FrameMediaMsg, + base::Bind(&MediaMessageFifo::ReserveMemory, + base::Unretained(fifo_.get())), + dummy_msg->content_size())); + if (!msg) + return false; + + DecoderBufferBaseMarshaller::Write(buffer, msg.get()); + return true; +} + +} // namespace media +} // namespace chromecast diff --git a/chromecast/media/cma/ipc_streamer/av_streamer_proxy.h b/chromecast/media/cma/ipc_streamer/av_streamer_proxy.h new file mode 100644 index 0000000000..2e3dcc17d7 --- /dev/null +++ b/chromecast/media/cma/ipc_streamer/av_streamer_proxy.h @@ -0,0 +1,88 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROMECAST_MEDIA_CMA_IPC_STREAMER_AV_STREAMER_PROXY_H_ +#define CHROMECAST_MEDIA_CMA_IPC_STREAMER_AV_STREAMER_PROXY_H_ + +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/threading/thread_checker.h" +#include "media/base/audio_decoder_config.h" +#include "media/base/video_decoder_config.h" + +namespace chromecast { +namespace media { +class CodedFrameProvider; +class DecoderBufferBase; +class MediaMessageFifo; + +// AvStreamerProxy streams audio/video data from a coded frame provider +// to a media message fifo. +class AvStreamerProxy { + public: + AvStreamerProxy(); + ~AvStreamerProxy(); + + // AvStreamerProxy gets frames and audio/video config + // from the |frame_provider| and feed them into the |fifo|. + void SetCodedFrameProvider(scoped_ptr<CodedFrameProvider> frame_provider); + void SetMediaMessageFifo(scoped_ptr<MediaMessageFifo> fifo); + + // Starts fetching A/V buffers. + void Start(); + + // Stops fetching A/V buffers and flush the pending buffers, + // invoking |flush_cb| when done. + void StopAndFlush(const base::Closure& flush_cb); + + // Event invoked when some data have been read from the fifo. + // This means some data can now possibly be written into the fifo. + void OnFifoReadEvent(); + + private: + void RequestBufferIfNeeded(); + + void OnNewBuffer(const scoped_refptr<DecoderBufferBase>& buffer, + const ::media::AudioDecoderConfig& audio_config, + const ::media::VideoDecoderConfig& video_config); + + void ProcessPendingData(); + + bool SendAudioDecoderConfig(const ::media::AudioDecoderConfig& config); + bool SendVideoDecoderConfig(const ::media::VideoDecoderConfig& config); + bool SendBuffer(const scoped_refptr<DecoderBufferBase>& buffer); + + base::ThreadChecker thread_checker_; + + scoped_ptr<CodedFrameProvider> frame_provider_; + + // Fifo used to convey A/V configs and buffers. + scoped_ptr<MediaMessageFifo> fifo_; + + // State. + bool is_running_; + + // Indicates if there is a pending request to the coded frame provider. + bool pending_read_; + + // Pending config & buffer. + // |pending_av_data_| is set as long as one of the pending audio/video + // config or buffer is valid. + bool pending_av_data_; + ::media::AudioDecoderConfig pending_audio_config_; + ::media::VideoDecoderConfig pending_video_config_; + scoped_refptr<DecoderBufferBase> pending_buffer_; + + base::WeakPtrFactory<AvStreamerProxy> weak_factory_; + base::WeakPtr<AvStreamerProxy> weak_this_; + + DISALLOW_COPY_AND_ASSIGN(AvStreamerProxy); +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_IPC_STREAMER_AV_STREAMER_PROXY_H_ diff --git a/chromecast/media/cma/ipc_streamer/av_streamer_unittest.cc b/chromecast/media/cma/ipc_streamer/av_streamer_unittest.cc new file mode 100644 index 0000000000..6480701573 --- /dev/null +++ b/chromecast/media/cma/ipc_streamer/av_streamer_unittest.cc @@ -0,0 +1,253 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <list> +#include <vector> + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/threading/thread.h" +#include "base/time/time.h" +#include "chromecast/media/cma/base/decoder_buffer_base.h" +#include "chromecast/media/cma/base/frame_generator_for_test.h" +#include "chromecast/media/cma/base/mock_frame_consumer.h" +#include "chromecast/media/cma/base/mock_frame_provider.h" +#include "chromecast/media/cma/ipc/media_memory_chunk.h" +#include "chromecast/media/cma/ipc/media_message_fifo.h" +#include "chromecast/media/cma/ipc_streamer/av_streamer_proxy.h" +#include "chromecast/media/cma/ipc_streamer/coded_frame_provider_host.h" +#include "media/base/audio_decoder_config.h" +#include "media/base/decoder_buffer.h" +#include "media/base/video_decoder_config.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace chromecast { +namespace media { + +namespace { + +class FifoMemoryChunk : public MediaMemoryChunk { + public: + FifoMemoryChunk(void* mem, size_t size) + : mem_(mem), size_(size) {} + virtual ~FifoMemoryChunk() {} + + virtual void* data() const OVERRIDE { return mem_; } + virtual size_t size() const OVERRIDE { return size_; } + virtual bool valid() const OVERRIDE { return true; } + + private: + void* mem_; + size_t size_; + + DISALLOW_COPY_AND_ASSIGN(FifoMemoryChunk); +}; + +} // namespace + +class AvStreamerTest : public testing::Test { + public: + AvStreamerTest(); + virtual ~AvStreamerTest(); + + // Setups the test. + void Configure( + size_t frame_count, + const std::vector<bool>& provider_delayed_pattern, + const std::vector<bool>& consumer_delayed_pattern); + + // Starts the test. + void Start(); + + protected: + scoped_ptr<uint64[]> fifo_mem_; + + scoped_ptr<AvStreamerProxy> av_buffer_proxy_; + scoped_ptr<CodedFrameProviderHost> coded_frame_provider_host_; + scoped_ptr<MockFrameConsumer> frame_consumer_; + + private: + void OnTestTimeout(); + void OnTestCompleted(); + + void OnFifoRead(); + void OnFifoWrite(); + + DISALLOW_COPY_AND_ASSIGN(AvStreamerTest); +}; + +AvStreamerTest::AvStreamerTest() { +} + +AvStreamerTest::~AvStreamerTest() { +} + +void AvStreamerTest::Configure( + size_t frame_count, + const std::vector<bool>& provider_delayed_pattern, + const std::vector<bool>& consumer_delayed_pattern) { + // Frame generation on the producer and consumer side. + std::vector<FrameGeneratorForTest::FrameSpec> frame_specs; + frame_specs.resize(frame_count); + for (size_t k = 0; k < frame_specs.size() - 1; k++) { + frame_specs[k].has_config = (k == 0); + frame_specs[k].timestamp = base::TimeDelta::FromMilliseconds(40) * k; + frame_specs[k].size = 512; + frame_specs[k].has_decrypt_config = ((k % 3) == 0); + } + frame_specs[frame_specs.size() - 1].is_eos = true; + + scoped_ptr<FrameGeneratorForTest> frame_generator_provider( + new FrameGeneratorForTest(frame_specs)); + scoped_ptr<FrameGeneratorForTest> frame_generator_consumer( + new FrameGeneratorForTest(frame_specs)); + + scoped_ptr<MockFrameProvider> frame_provider(new MockFrameProvider()); + frame_provider->Configure(provider_delayed_pattern, + frame_generator_provider.Pass()); + + size_t fifo_size_div_8 = 512; + fifo_mem_.reset(new uint64[fifo_size_div_8]); + scoped_ptr<MediaMessageFifo> producer_fifo( + new MediaMessageFifo( + scoped_ptr<MediaMemoryChunk>( + new FifoMemoryChunk(&fifo_mem_[0], fifo_size_div_8 * 8)), + true)); + scoped_ptr<MediaMessageFifo> consumer_fifo( + new MediaMessageFifo( + scoped_ptr<MediaMemoryChunk>( + new FifoMemoryChunk(&fifo_mem_[0], fifo_size_div_8 * 8)), + false)); + producer_fifo->ObserveWriteActivity( + base::Bind(&AvStreamerTest::OnFifoWrite, base::Unretained(this))); + consumer_fifo->ObserveReadActivity( + base::Bind(&AvStreamerTest::OnFifoRead, base::Unretained(this))); + + av_buffer_proxy_.reset( + new AvStreamerProxy()); + av_buffer_proxy_->SetCodedFrameProvider( + scoped_ptr<CodedFrameProvider>(frame_provider.release())); + av_buffer_proxy_->SetMediaMessageFifo(producer_fifo.Pass()); + + coded_frame_provider_host_.reset( + new CodedFrameProviderHost(consumer_fifo.Pass())); + + frame_consumer_.reset( + new MockFrameConsumer(coded_frame_provider_host_.get())); + frame_consumer_->Configure( + consumer_delayed_pattern, + false, + frame_generator_consumer.Pass()); +} + +void AvStreamerTest::Start() { + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, + base::Bind(&AvStreamerProxy::Start, + base::Unretained(av_buffer_proxy_.get()))); + + frame_consumer_->Start( + base::Bind(&AvStreamerTest::OnTestCompleted, + base::Unretained(this))); +} + +void AvStreamerTest::OnTestTimeout() { + ADD_FAILURE() << "Test timed out"; + if (base::MessageLoop::current()) + base::MessageLoop::current()->QuitWhenIdle(); +} + +void AvStreamerTest::OnTestCompleted() { + base::MessageLoop::current()->QuitWhenIdle(); +} + +void AvStreamerTest::OnFifoWrite() { + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, + base::Bind(&CodedFrameProviderHost::OnFifoWriteEvent, + base::Unretained(coded_frame_provider_host_.get()))); +} + +void AvStreamerTest::OnFifoRead() { + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, + base::Bind(&AvStreamerProxy::OnFifoReadEvent, + base::Unretained(av_buffer_proxy_.get()))); +} + +TEST_F(AvStreamerTest, FastProviderSlowConsumer) { + bool provider_delayed_pattern[] = { false }; + bool consumer_delayed_pattern[] = { true }; + + const size_t frame_count = 100u; + Configure( + frame_count, + std::vector<bool>( + provider_delayed_pattern, + provider_delayed_pattern + arraysize(provider_delayed_pattern)), + std::vector<bool>( + consumer_delayed_pattern, + consumer_delayed_pattern + arraysize(consumer_delayed_pattern))); + + scoped_ptr<base::MessageLoop> message_loop(new base::MessageLoop()); + message_loop->PostTask( + FROM_HERE, + base::Bind(&AvStreamerTest::Start, base::Unretained(this))); + message_loop->Run(); +}; + +TEST_F(AvStreamerTest, SlowProviderFastConsumer) { + bool provider_delayed_pattern[] = { true }; + bool consumer_delayed_pattern[] = { false }; + + const size_t frame_count = 100u; + Configure( + frame_count, + std::vector<bool>( + provider_delayed_pattern, + provider_delayed_pattern + arraysize(provider_delayed_pattern)), + std::vector<bool>( + consumer_delayed_pattern, + consumer_delayed_pattern + arraysize(consumer_delayed_pattern))); + + scoped_ptr<base::MessageLoop> message_loop(new base::MessageLoop()); + message_loop->PostTask( + FROM_HERE, + base::Bind(&AvStreamerTest::Start, base::Unretained(this))); + message_loop->Run(); +}; + +TEST_F(AvStreamerTest, SlowFastProducerConsumer) { + // Pattern lengths are prime between each other + // so that a lot of combinations can be tested. + bool provider_delayed_pattern[] = { + true, true, true, true, true, + false, false, false, false + }; + bool consumer_delayed_pattern[] = { + true, true, true, true, true, true, true, + false, false, false, false, false, false, false + }; + + const size_t frame_count = 100u; + Configure( + frame_count, + std::vector<bool>( + provider_delayed_pattern, + provider_delayed_pattern + arraysize(provider_delayed_pattern)), + std::vector<bool>( + consumer_delayed_pattern, + consumer_delayed_pattern + arraysize(consumer_delayed_pattern))); + + scoped_ptr<base::MessageLoop> message_loop(new base::MessageLoop()); + message_loop->PostTask( + FROM_HERE, + base::Bind(&AvStreamerTest::Start, base::Unretained(this))); + message_loop->Run(); +}; + +} // namespace media +} // namespace chromecast diff --git a/chromecast/media/cma/ipc_streamer/coded_frame_provider_host.cc b/chromecast/media/cma/ipc_streamer/coded_frame_provider_host.cc new file mode 100644 index 0000000000..19c57bdb7a --- /dev/null +++ b/chromecast/media/cma/ipc_streamer/coded_frame_provider_host.cc @@ -0,0 +1,96 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chromecast/media/cma/ipc_streamer/coded_frame_provider_host.h" + +#include "base/bind.h" +#include "base/callback_helpers.h" +#include "chromecast/media/cma/base/decoder_buffer_base.h" +#include "chromecast/media/cma/ipc/media_message.h" +#include "chromecast/media/cma/ipc/media_message_fifo.h" +#include "chromecast/media/cma/ipc/media_message_type.h" +#include "chromecast/media/cma/ipc_streamer/audio_decoder_config_marshaller.h" +#include "chromecast/media/cma/ipc_streamer/decoder_buffer_base_marshaller.h" +#include "chromecast/media/cma/ipc_streamer/video_decoder_config_marshaller.h" +#include "media/base/buffers.h" +#include "media/base/decrypt_config.h" + +namespace chromecast { +namespace media { + +CodedFrameProviderHost::CodedFrameProviderHost( + scoped_ptr<MediaMessageFifo> media_message_fifo) + : fifo_(media_message_fifo.Pass()), + weak_factory_(this), + weak_this_(weak_factory_.GetWeakPtr()) { + thread_checker_.DetachFromThread(); +} + +CodedFrameProviderHost::~CodedFrameProviderHost() { + DCHECK(thread_checker_.CalledOnValidThread()); +} + +void CodedFrameProviderHost::Read(const ReadCB& read_cb) { + DCHECK(thread_checker_.CalledOnValidThread()); + + // Cannot be called if there is already a pending read. + DCHECK(read_cb_.is_null()); + read_cb_ = read_cb; + + ReadMessages(); +} + +void CodedFrameProviderHost::Flush(const base::Closure& flush_cb) { + DCHECK(thread_checker_.CalledOnValidThread()); + audio_config_ = ::media::AudioDecoderConfig(); + video_config_ = ::media::VideoDecoderConfig(); + read_cb_.Reset(); + fifo_->Flush(); + flush_cb.Run(); +} + +void CodedFrameProviderHost::OnFifoWriteEvent() { + DCHECK(thread_checker_.CalledOnValidThread()); + ReadMessages(); +} + +base::Closure CodedFrameProviderHost::GetFifoWriteEventCb() { + return base::Bind(&CodedFrameProviderHost::OnFifoWriteEvent, weak_this_); +} + +void CodedFrameProviderHost::ReadMessages() { + // Read messages until a frame is provided (i.e. not just the audio/video + // configurations). + while (!read_cb_.is_null()) { + scoped_ptr<MediaMessage> msg(fifo_->Pop()); + if (!msg) + break; + + if (msg->type() == PaddingMediaMsg) { + // Ignore the message. + } else if (msg->type() == AudioConfigMediaMsg) { + audio_config_ = AudioDecoderConfigMarshaller::Read(msg.get()); + } else if (msg->type() == VideoConfigMediaMsg) { + video_config_ = VideoDecoderConfigMarshaller::Read(msg.get()); + } else if (msg->type() == FrameMediaMsg) { + scoped_refptr<DecoderBufferBase> buffer = + DecoderBufferBaseMarshaller::Read(msg.Pass()); + base::ResetAndReturn(&read_cb_).Run( + buffer, audio_config_, video_config_); + audio_config_ = ::media::AudioDecoderConfig(); + video_config_ = ::media::VideoDecoderConfig(); + } else { + // Receiving an unexpected message. + // Possible use case (except software bugs): the renderer process has + // been compromised and an invalid message value has been written to + // the fifo. Crash the browser process in this case to avoid further + // security implications (so do not use NOTREACHED which crashes only + // in debug builds). + LOG(FATAL) << "Unknown media message"; + } + } +} + +} // namespace media +} // namespace chromecast diff --git a/chromecast/media/cma/ipc_streamer/coded_frame_provider_host.h b/chromecast/media/cma/ipc_streamer/coded_frame_provider_host.h new file mode 100644 index 0000000000..3dcd329259 --- /dev/null +++ b/chromecast/media/cma/ipc_streamer/coded_frame_provider_host.h @@ -0,0 +1,61 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROMECAST_MEDIA_CMA_IPC_STREAMER_CODED_FRAME_PROVIDER_HOST_H_ +#define CHROMECAST_MEDIA_CMA_IPC_STREAMER_CODED_FRAME_PROVIDER_HOST_H_ + +#include "base/callback.h" +#include "base/memory/weak_ptr.h" +#include "base/threading/thread_checker.h" +#include "chromecast/media/cma/base/coded_frame_provider.h" +#include "media/base/audio_decoder_config.h" +#include "media/base/video_decoder_config.h" + +namespace chromecast { +namespace media { +class MediaMessageFifo; + +// CodedFrameProviderHost is a frame provider that gets the frames +// from a media message fifo. +class CodedFrameProviderHost : public CodedFrameProvider { + public: + // Note: if the media message fifo is located into shared memory, + // the caller must make sure the shared memory segment is valid + // during the whole lifetime of this object. + explicit CodedFrameProviderHost( + scoped_ptr<MediaMessageFifo> media_message_fifo); + virtual ~CodedFrameProviderHost(); + + // CodedFrameProvider implementation. + virtual void Read(const ReadCB& read_cb) OVERRIDE; + virtual void Flush(const base::Closure& flush_cb) OVERRIDE; + + // Invoked when some data has been written into the fifo. + void OnFifoWriteEvent(); + base::Closure GetFifoWriteEventCb(); + + private: + void ReadMessages(); + + base::ThreadChecker thread_checker_; + + // Fifo holding the frames. + scoped_ptr<MediaMessageFifo> fifo_; + + ReadCB read_cb_; + + // Audio/video configuration for the next A/V buffer. + ::media::AudioDecoderConfig audio_config_; + ::media::VideoDecoderConfig video_config_; + + base::WeakPtrFactory<CodedFrameProviderHost> weak_factory_; + base::WeakPtr<CodedFrameProviderHost> weak_this_; + + DISALLOW_COPY_AND_ASSIGN(CodedFrameProviderHost); +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_IPC_STREAMER_CODED_FRAME_PROVIDER_HOST_H_ diff --git a/chromecast/media/cma/ipc_streamer/decoder_buffer_base_marshaller.cc b/chromecast/media/cma/ipc_streamer/decoder_buffer_base_marshaller.cc new file mode 100644 index 0000000000..bfdb7c121c --- /dev/null +++ b/chromecast/media/cma/ipc_streamer/decoder_buffer_base_marshaller.cc @@ -0,0 +1,161 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chromecast/media/cma/ipc_streamer/decoder_buffer_base_marshaller.h" + +#include "base/logging.h" +#include "chromecast/media/cma/base/decoder_buffer_base.h" +#include "chromecast/media/cma/ipc/media_message.h" +#include "chromecast/media/cma/ipc/media_message_type.h" +#include "chromecast/media/cma/ipc_streamer/decrypt_config_marshaller.h" +#include "media/base/decrypt_config.h" + +namespace chromecast { +namespace media { + +namespace { +const size_t kMaxFrameSize = 4 * 1024 * 1024; + +class DecoderBufferFromMsg : public DecoderBufferBase { + public: + explicit DecoderBufferFromMsg(scoped_ptr<MediaMessage> msg); + + void Initialize(); + + // DecoderBufferBase implementation. + virtual base::TimeDelta timestamp() const OVERRIDE; + virtual const uint8* data() const OVERRIDE; + virtual uint8* writable_data() const OVERRIDE; + virtual int data_size() const OVERRIDE; + virtual const ::media::DecryptConfig* decrypt_config() const OVERRIDE; + virtual bool end_of_stream() const OVERRIDE; + + private: + virtual ~DecoderBufferFromMsg(); + + // Indicates whether this is an end of stream frame. + bool is_eos_; + + // Frame timestamp. + base::TimeDelta pts_; + + // CENC parameters. + scoped_ptr< ::media::DecryptConfig> decrypt_config_; + + // Size of the frame. + int data_size_; + + // Keeps the message since frame data is not copied. + scoped_ptr<MediaMessage> msg_; + uint8* data_; + + DISALLOW_COPY_AND_ASSIGN(DecoderBufferFromMsg); +}; + +DecoderBufferFromMsg::DecoderBufferFromMsg( + scoped_ptr<MediaMessage> msg) + : msg_(msg.Pass()), + is_eos_(true), + data_(NULL) { + CHECK(msg_); +} + +DecoderBufferFromMsg::~DecoderBufferFromMsg() { +} + +void DecoderBufferFromMsg::Initialize() { + CHECK_EQ(msg_->type(), FrameMediaMsg); + + CHECK(msg_->ReadPod(&is_eos_)); + if (is_eos_) + return; + + int64 pts_internal = 0; + CHECK(msg_->ReadPod(&pts_internal)); + pts_ = base::TimeDelta::FromInternalValue(pts_internal); + + bool has_decrypt_config = false; + CHECK(msg_->ReadPod(&has_decrypt_config)); + if (has_decrypt_config) + decrypt_config_.reset(DecryptConfigMarshaller::Read(msg_.get()).release()); + + CHECK(msg_->ReadPod(&data_size_)); + CHECK_GT(data_size_, 0); + CHECK_LT(data_size_, kMaxFrameSize); + + // Get a pointer to the frame data inside the message. + // Avoid copying the frame data here. + data_ = static_cast<uint8*>(msg_->GetWritableBuffer(data_size_)); + CHECK(data_); + + if (decrypt_config_) { + uint32 subsample_total_size = 0; + for (size_t k = 0; k < decrypt_config_->subsamples().size(); k++) { + subsample_total_size += decrypt_config_->subsamples()[k].clear_bytes; + subsample_total_size += decrypt_config_->subsamples()[k].cypher_bytes; + } + CHECK_EQ(subsample_total_size, data_size_); + } +} + +base::TimeDelta DecoderBufferFromMsg::timestamp() const { + return pts_; +} + +const uint8* DecoderBufferFromMsg::data() const { + CHECK(msg_->IsSerializedMsgAvailable()); + return data_; +} + +uint8* DecoderBufferFromMsg::writable_data() const { + CHECK(msg_->IsSerializedMsgAvailable()); + return data_; +} + +int DecoderBufferFromMsg::data_size() const { + return data_size_; +} + +const ::media::DecryptConfig* DecoderBufferFromMsg::decrypt_config() const { + return decrypt_config_.get(); +} + +bool DecoderBufferFromMsg::end_of_stream() const { + return is_eos_; +} + +} // namespace + +// static +void DecoderBufferBaseMarshaller::Write( + const scoped_refptr<DecoderBufferBase>& buffer, + MediaMessage* msg) { + CHECK(msg->WritePod(buffer->end_of_stream())); + if (buffer->end_of_stream()) + return; + + CHECK(msg->WritePod(buffer->timestamp().ToInternalValue())); + + bool has_decrypt_config = + (buffer->decrypt_config() != NULL && + buffer->decrypt_config()->iv().size() > 0); + CHECK(msg->WritePod(has_decrypt_config)); + if (has_decrypt_config) + DecryptConfigMarshaller::Write(*buffer->decrypt_config(), msg); + + CHECK(msg->WritePod(buffer->data_size())); + CHECK(msg->WriteBuffer(buffer->data(), buffer->data_size())); +} + +// static +scoped_refptr<DecoderBufferBase> DecoderBufferBaseMarshaller::Read( + scoped_ptr<MediaMessage> msg) { + scoped_refptr<DecoderBufferFromMsg> buffer( + new DecoderBufferFromMsg(msg.Pass())); + buffer->Initialize(); + return buffer; +} + +} // namespace media +} // namespace chromecast diff --git a/chromecast/media/cma/ipc_streamer/decoder_buffer_base_marshaller.h b/chromecast/media/cma/ipc_streamer/decoder_buffer_base_marshaller.h new file mode 100644 index 0000000000..bd5dbfe9b1 --- /dev/null +++ b/chromecast/media/cma/ipc_streamer/decoder_buffer_base_marshaller.h @@ -0,0 +1,29 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROMECAST_MEDIA_CMA_IPC_STREAMER_DECODER_BUFFER_BASE_MARSHALLER_H_ +#define CHROMECAST_MEDIA_CMA_IPC_STREAMER_DECODER_BUFFER_BASE_MARSHALLER_H_ + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" + +namespace chromecast { +namespace media { +class DecoderBufferBase; +class MediaMessage; + +class DecoderBufferBaseMarshaller { + public: + // Writes the serialized structure of |config| into |msg|. + static void Write( + const scoped_refptr<DecoderBufferBase>& buffer, MediaMessage* msg); + + // Returns a decoder buffer from its serialized structure. + static scoped_refptr<DecoderBufferBase> Read(scoped_ptr<MediaMessage> msg); +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_IPC_STREAMER_DECODER_BUFFER_BASE_MARSHALLER_H_ diff --git a/chromecast/media/cma/ipc_streamer/decrypt_config_marshaller.cc b/chromecast/media/cma/ipc_streamer/decrypt_config_marshaller.cc new file mode 100644 index 0000000000..9e3de954cc --- /dev/null +++ b/chromecast/media/cma/ipc_streamer/decrypt_config_marshaller.cc @@ -0,0 +1,75 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chromecast/media/cma/ipc_streamer/decrypt_config_marshaller.h" + +#include "base/logging.h" +#include "chromecast/media/cma/ipc/media_message.h" +#include "media/base/decrypt_config.h" + +namespace chromecast { +namespace media { + +namespace { +const size_t kMaxKeyIdSize = 256; +const size_t kMaxIvSize = 256; +const size_t kMaxSubsampleCount = 1024; +} + +// static +void DecryptConfigMarshaller::Write( + const ::media::DecryptConfig& config, MediaMessage* msg) { + CHECK_GT(config.key_id().size(), 0); + CHECK_GT(config.iv().size(), 0); + CHECK_GT(config.subsamples().size(), 0); + + CHECK(msg->WritePod(config.key_id().size())); + CHECK(msg->WriteBuffer(config.key_id().data(), config.key_id().size())); + CHECK(msg->WritePod(config.iv().size())); + CHECK(msg->WriteBuffer(config.iv().data(), config.iv().size())); + CHECK(msg->WritePod(config.subsamples().size())); + for (size_t k = 0; k < config.subsamples().size(); k++) { + CHECK(msg->WritePod(config.subsamples()[k].clear_bytes)); + CHECK(msg->WritePod(config.subsamples()[k].cypher_bytes)); + } +} + +// static +scoped_ptr< ::media::DecryptConfig> DecryptConfigMarshaller::Read( + MediaMessage* msg) { + size_t key_id_size = 0; + CHECK(msg->ReadPod(&key_id_size)); + CHECK_GT(key_id_size, 0); + CHECK_LT(key_id_size, kMaxKeyIdSize); + scoped_ptr<char[]> key_id(new char[key_id_size]); + CHECK(msg->ReadBuffer(key_id.get(), key_id_size)); + + size_t iv_size = 0; + CHECK(msg->ReadPod(&iv_size)); + CHECK_GT(iv_size, 0); + CHECK_LT(iv_size, kMaxIvSize); + scoped_ptr<char[]> iv(new char[iv_size]); + CHECK(msg->ReadBuffer(iv.get(), iv_size)); + + size_t subsample_count = 0; + CHECK(msg->ReadPod(&subsample_count)); + CHECK_GT(subsample_count, 0); + CHECK_LT(subsample_count, kMaxSubsampleCount); + std::vector< ::media::SubsampleEntry> subsamples(subsample_count); + for (size_t k = 0; k < subsample_count; k++) { + subsamples[k].clear_bytes = 0; + subsamples[k].cypher_bytes = 0; + CHECK(msg->ReadPod(&subsamples[k].clear_bytes)); + CHECK(msg->ReadPod(&subsamples[k].cypher_bytes)); + } + + return scoped_ptr< ::media::DecryptConfig>( + new ::media::DecryptConfig( + std::string(key_id.get(), key_id_size), + std::string(iv.get(), iv_size), + subsamples)); +} + +} // namespace media +} // namespace chromecast diff --git a/chromecast/media/cma/ipc_streamer/decrypt_config_marshaller.h b/chromecast/media/cma/ipc_streamer/decrypt_config_marshaller.h new file mode 100644 index 0000000000..b600ba41c1 --- /dev/null +++ b/chromecast/media/cma/ipc_streamer/decrypt_config_marshaller.h @@ -0,0 +1,31 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROMECAST_MEDIA_CMA_IPC_STREAMER_DECRYPT_CONFIG_MARSHALLER_H_ +#define CHROMECAST_MEDIA_CMA_IPC_STREAMER_DECRYPT_CONFIG_MARSHALLER_H_ + +#include "base/memory/scoped_ptr.h" + +namespace media { +class DecryptConfig; +} + +namespace chromecast { +namespace media { +class MediaMessage; + +class DecryptConfigMarshaller { + public: + // Writes the serialized structure of |config| into |msg|. + static void Write( + const ::media::DecryptConfig& config, MediaMessage* msg); + + // Returns a DecryptConfig from its serialized structure. + static scoped_ptr< ::media::DecryptConfig> Read(MediaMessage* msg); +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_IPC_STREAMER_DECRYPT_CONFIG_MARSHALLER_H_ diff --git a/chromecast/media/cma/ipc_streamer/video_decoder_config_marshaller.cc b/chromecast/media/cma/ipc_streamer/video_decoder_config_marshaller.cc new file mode 100644 index 0000000000..645056ce22 --- /dev/null +++ b/chromecast/media/cma/ipc_streamer/video_decoder_config_marshaller.cc @@ -0,0 +1,113 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chromecast/media/cma/ipc_streamer/video_decoder_config_marshaller.h" + +#include "base/basictypes.h" +#include "base/logging.h" +#include "chromecast/media/cma/ipc/media_message.h" +#include "media/base/video_decoder_config.h" +#include "ui/gfx/rect.h" +#include "ui/gfx/size.h" + +namespace chromecast { +namespace media { + +namespace { +const size_t kMaxExtraDataSize = 16 * 1024; + +class SizeMarshaller { + public: + static void Write(const gfx::Size& size, MediaMessage* msg) { + CHECK(msg->WritePod(size.width())); + CHECK(msg->WritePod(size.height())); + } + + static gfx::Size Read(MediaMessage* msg) { + int w, h; + CHECK(msg->ReadPod(&w)); + CHECK(msg->ReadPod(&h)); + return gfx::Size(w, h); + } +}; + +class RectMarshaller { + public: + static void Write(const gfx::Rect& rect, MediaMessage* msg) { + CHECK(msg->WritePod(rect.x())); + CHECK(msg->WritePod(rect.y())); + CHECK(msg->WritePod(rect.width())); + CHECK(msg->WritePod(rect.height())); + } + + static gfx::Rect Read(MediaMessage* msg) { + int x, y, w, h; + CHECK(msg->ReadPod(&x)); + CHECK(msg->ReadPod(&y)); + CHECK(msg->ReadPod(&w)); + CHECK(msg->ReadPod(&h)); + return gfx::Rect(x, y, w, h); + } +}; + +} // namespace + +// static +void VideoDecoderConfigMarshaller::Write( + const ::media::VideoDecoderConfig& config, MediaMessage* msg) { + CHECK(msg->WritePod(config.codec())); + CHECK(msg->WritePod(config.profile())); + CHECK(msg->WritePod(config.format())); + SizeMarshaller::Write(config.coded_size(), msg); + RectMarshaller::Write(config.visible_rect(), msg); + SizeMarshaller::Write(config.natural_size(), msg); + CHECK(msg->WritePod(config.is_encrypted())); + CHECK(msg->WritePod(config.extra_data_size())); + if (config.extra_data_size() > 0) + CHECK(msg->WriteBuffer(config.extra_data(), config.extra_data_size())); +} + +// static +::media::VideoDecoderConfig VideoDecoderConfigMarshaller::Read( + MediaMessage* msg) { + ::media::VideoCodec codec; + ::media::VideoCodecProfile profile; + ::media::VideoFrame::Format format; + gfx::Size coded_size; + gfx::Rect visible_rect; + gfx::Size natural_size; + bool is_encrypted; + size_t extra_data_size; + scoped_ptr<uint8[]> extra_data; + + CHECK(msg->ReadPod(&codec)); + CHECK(msg->ReadPod(&profile)); + CHECK(msg->ReadPod(&format)); + coded_size = SizeMarshaller::Read(msg); + visible_rect = RectMarshaller::Read(msg); + natural_size = SizeMarshaller::Read(msg); + CHECK(msg->ReadPod(&is_encrypted)); + CHECK(msg->ReadPod(&extra_data_size)); + + CHECK_GE(codec, ::media::kUnknownVideoCodec); + CHECK_LE(codec, ::media::kVideoCodecMax); + CHECK_GE(profile, ::media::VIDEO_CODEC_PROFILE_UNKNOWN); + CHECK_LE(profile, ::media::VIDEO_CODEC_PROFILE_MAX); + CHECK_GE(format, ::media::VideoFrame::UNKNOWN); + CHECK_LE(format, ::media::VideoFrame::FORMAT_MAX); + CHECK_LT(extra_data_size, kMaxExtraDataSize); + if (extra_data_size > 0) { + extra_data.reset(new uint8[extra_data_size]); + CHECK(msg->ReadBuffer(extra_data.get(), extra_data_size)); + } + + return ::media::VideoDecoderConfig( + codec, profile, format, + coded_size, visible_rect, natural_size, + extra_data.get(), extra_data_size, + is_encrypted); +} + +} // namespace media +} // namespace chromecast diff --git a/chromecast/media/cma/ipc_streamer/video_decoder_config_marshaller.h b/chromecast/media/cma/ipc_streamer/video_decoder_config_marshaller.h new file mode 100644 index 0000000000..5ba7baec06 --- /dev/null +++ b/chromecast/media/cma/ipc_streamer/video_decoder_config_marshaller.h @@ -0,0 +1,27 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROMECAST_MEDIA_CMA_IPC_STREAMER_VIDEO_DECODER_CONFIG_MARSHALLER_H_ +#define CHROMECAST_MEDIA_CMA_IPC_STREAMER_VIDEO_DECODER_CONFIG_MARSHALLER_H_ + +#include "media/base/video_decoder_config.h" + +namespace chromecast { +namespace media { +class MediaMessage; + +class VideoDecoderConfigMarshaller { + public: + // Writes the serialized structure of |config| into |msg|. + static void Write( + const ::media::VideoDecoderConfig& config, MediaMessage* msg); + + // Returns a VideoDecoderConfig from its serialized structure. + static ::media::VideoDecoderConfig Read(MediaMessage* msg); +}; + +} // namespace media +} // namespace chromecast + +#endif // CHROMECAST_MEDIA_CMA_IPC_STREAMER_VIDEO_DECODER_CONFIG_MARSHALLER_H_ diff --git a/chromecast/media/media.gyp b/chromecast/media/media.gyp new file mode 100644 index 0000000000..3cebc65631 --- /dev/null +++ b/chromecast/media/media.gyp @@ -0,0 +1,154 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +{ + 'variables': { + 'chromecast_branding%': 'Chromium', + }, + 'targets': [ + { + 'target_name': 'media_base', + 'type': '<(component)', + 'dependencies': [ + '../../base/base.gyp:base', + '../../third_party/widevine/cdm/widevine_cdm.gyp:widevine_cdm_version_h', + ], + 'sources': [ + 'base/key_systems_common.cc', + 'base/key_systems_common.h', + ], + 'conditions': [ + ['chromecast_branding=="Chrome"', { + 'dependencies': [ + 'internal/chromecast_internal.gyp:media_base_internal', + ], + }, { + 'sources': [ + 'base/key_systems_common_simple.cc', + ], + }], + ], + }, + { + 'target_name': 'cma_base', + 'type': '<(component)', + 'dependencies': [ + '../../base/base.gyp:base', + '../../media/media.gyp:media', + ], + 'include_dirs': [ + '../..', + ], + 'sources': [ + 'cma/base/balanced_media_task_runner_factory.cc', + 'cma/base/balanced_media_task_runner_factory.h', + 'cma/base/buffering_controller.cc', + 'cma/base/buffering_controller.h', + 'cma/base/buffering_frame_provider.cc', + 'cma/base/buffering_frame_provider.h', + 'cma/base/buffering_state.cc', + 'cma/base/buffering_state.h', + 'cma/base/cma_logging.h', + 'cma/base/coded_frame_provider.cc', + 'cma/base/coded_frame_provider.h', + 'cma/base/decoder_buffer_adapter.cc', + 'cma/base/decoder_buffer_adapter.h', + 'cma/base/decoder_buffer_base.cc', + 'cma/base/decoder_buffer_base.h', + 'cma/base/media_task_runner.cc', + 'cma/base/media_task_runner.h', + ], + }, + { + 'target_name': 'cma_ipc', + 'type': '<(component)', + 'dependencies': [ + '../../base/base.gyp:base', + ], + 'sources': [ + 'cma/ipc/media_memory_chunk.cc', + 'cma/ipc/media_memory_chunk.h', + 'cma/ipc/media_message.cc', + 'cma/ipc/media_message.h', + 'cma/ipc/media_message_fifo.cc', + 'cma/ipc/media_message_fifo.h', + ], + }, + { + 'target_name': 'cma_ipc_streamer', + 'type': '<(component)', + 'dependencies': [ + '../../base/base.gyp:base', + '../../media/media.gyp:media', + 'cma_base', + ], + 'sources': [ + 'cma/ipc_streamer/audio_decoder_config_marshaller.cc', + 'cma/ipc_streamer/audio_decoder_config_marshaller.h', + 'cma/ipc_streamer/av_streamer_proxy.cc', + 'cma/ipc_streamer/av_streamer_proxy.h', + 'cma/ipc_streamer/coded_frame_provider_host.cc', + 'cma/ipc_streamer/coded_frame_provider_host.h', + 'cma/ipc_streamer/decoder_buffer_base_marshaller.cc', + 'cma/ipc_streamer/decoder_buffer_base_marshaller.h', + 'cma/ipc_streamer/decrypt_config_marshaller.cc', + 'cma/ipc_streamer/decrypt_config_marshaller.h', + 'cma/ipc_streamer/video_decoder_config_marshaller.cc', + 'cma/ipc_streamer/video_decoder_config_marshaller.h', + ], + }, + { + 'target_name': 'cma_filters', + 'type': '<(component)', + 'dependencies': [ + '../../base/base.gyp:base', + '../../media/media.gyp:media', + 'cma_base', + ], + 'sources': [ + 'cma/filters/demuxer_stream_adapter.cc', + 'cma/filters/demuxer_stream_adapter.h', + ], + }, + { + 'target_name': 'cast_media', + 'type': 'none', + 'dependencies': [ + 'cma_base', + 'cma_filters', + 'cma_ipc', + 'cma_ipc_streamer', + ], + }, + { + 'target_name': 'cast_media_unittests', + 'type': '<(gtest_target_type)', + 'dependencies': [ + 'cast_media', + '../../base/base.gyp:base', + '../../base/base.gyp:base_i18n', + '../../base/base.gyp:test_support_base', + '../../testing/gmock.gyp:gmock', + '../../testing/gtest.gyp:gtest', + '../../testing/gtest.gyp:gtest_main', + ], + 'sources': [ + 'cma/base/balanced_media_task_runner_unittest.cc', + 'cma/base/buffering_controller_unittest.cc', + 'cma/base/buffering_frame_provider_unittest.cc', + 'cma/base/frame_generator_for_test.cc', + 'cma/base/frame_generator_for_test.h', + 'cma/base/mock_frame_consumer.cc', + 'cma/base/mock_frame_consumer.h', + 'cma/base/mock_frame_provider.cc', + 'cma/base/mock_frame_provider.h', + 'cma/base/run_all_unittests.cc', + 'cma/filters/demuxer_stream_adapter_unittest.cc', + 'cma/ipc/media_message_fifo_unittest.cc', + 'cma/ipc/media_message_unittest.cc', + 'cma/ipc_streamer/av_streamer_unittest.cc', + ], + }, + ], +} diff --git a/chromecast/metrics/DEPS b/chromecast/metrics/DEPS new file mode 100644 index 0000000000..d9ef433896 --- /dev/null +++ b/chromecast/metrics/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+components/metrics", +] diff --git a/chromecast/metrics/OWNERS b/chromecast/metrics/OWNERS new file mode 100644 index 0000000000..2518ed9062 --- /dev/null +++ b/chromecast/metrics/OWNERS @@ -0,0 +1,5 @@ +# components/metrics OWNERS for review of Chromecast metrics code +asvitkine@chromium.org +mpearson@chromium.org +jar@chromium.org +isherman@chromium.org diff --git a/chromecast/metrics/cast_metrics_prefs.cc b/chromecast/metrics/cast_metrics_prefs.cc new file mode 100644 index 0000000000..9138aa74dd --- /dev/null +++ b/chromecast/metrics/cast_metrics_prefs.cc @@ -0,0 +1,17 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chromecast/metrics/cast_metrics_prefs.h" + +#include "components/metrics/metrics_service.h" + +namespace chromecast { +namespace metrics { + +void RegisterPrefs(PrefRegistrySimple* registry) { + ::metrics::MetricsService::RegisterPrefs(registry); +} + +} // namespace metrics +} // namespace chromecast diff --git a/chromecast/metrics/cast_metrics_prefs.h b/chromecast/metrics/cast_metrics_prefs.h new file mode 100644 index 0000000000..ff7a973cc3 --- /dev/null +++ b/chromecast/metrics/cast_metrics_prefs.h @@ -0,0 +1,18 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROMECAST_METRICS_CAST_METRICS_PREFS_H_ +#define CHROMECAST_METRICS_CAST_METRICS_PREFS_H_ + +class PrefRegistrySimple; + +namespace chromecast { +namespace metrics { + +void RegisterPrefs(PrefRegistrySimple* registry); + +} // namespace metrics +} // namespace chromecast + +#endif // CHROMECAST_METRICS_CAST_METRICS_PREFS_H_ diff --git a/chromecast/metrics/cast_metrics_service_client.cc b/chromecast/metrics/cast_metrics_service_client.cc new file mode 100644 index 0000000000..637cd400b8 --- /dev/null +++ b/chromecast/metrics/cast_metrics_service_client.cc @@ -0,0 +1,164 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chromecast/metrics/cast_metrics_service_client.h" + +#include "base/command_line.h" +#include "base/i18n/rtl.h" +#include "chromecast/common/chromecast_config.h" +#include "chromecast/common/chromecast_switches.h" +#include "chromecast/metrics/platform_metrics_providers.h" +#include "components/metrics/client_info.h" +#include "components/metrics/gpu/gpu_metrics_provider.h" +#include "components/metrics/metrics_provider.h" +#include "components/metrics/metrics_service.h" +#include "components/metrics/metrics_state_manager.h" +#include "components/metrics/net/net_metrics_log_uploader.h" +#include "components/metrics/net/network_metrics_provider.h" +#include "components/metrics/profiler/profiler_metrics_provider.h" + +namespace chromecast { +namespace metrics { + +namespace { + +void StoreClientInfo(const ::metrics::ClientInfo& client_info) { +} + +scoped_ptr<::metrics::ClientInfo> LoadClientInfo() { + return scoped_ptr<::metrics::ClientInfo>(); +} + +} // namespace + +// static +CastMetricsServiceClient* CastMetricsServiceClient::Create( + base::TaskRunner* io_task_runner, + PrefService* pref_service, + net::URLRequestContextGetter* request_context) { + return new CastMetricsServiceClient(io_task_runner, + pref_service, + request_context); +} + +void CastMetricsServiceClient::SetMetricsClientId( + const std::string& client_id) { + LOG(INFO) << "Metrics client ID set: " << client_id; + PlatformSetClientID(client_id); +} + +bool CastMetricsServiceClient::IsOffTheRecordSessionActive() { + // Chromecast behaves as "off the record" w/r/t recording browsing state, + // but this value is about not disabling metrics because of it. + return false; +} + +std::string CastMetricsServiceClient::GetApplicationLocale() { + return base::i18n::GetConfiguredLocale(); +} + +bool CastMetricsServiceClient::GetBrand(std::string* brand_code) { + return false; +} + +::metrics::SystemProfileProto::Channel CastMetricsServiceClient::GetChannel() { + return GetPlatformReleaseChannel(); +} + +std::string CastMetricsServiceClient::GetVersionString() { + return GetPlatformVersionString(); +} + +void CastMetricsServiceClient::OnLogUploadComplete() { + PlatformOnLogUploadComplete(); +} + +void CastMetricsServiceClient::StartGatheringMetrics( + const base::Closure& done_callback) { + done_callback.Run(); +} + +void CastMetricsServiceClient::CollectFinalMetrics( + const base::Closure& done_callback) { + done_callback.Run(); +} + +scoped_ptr< ::metrics::MetricsLogUploader> +CastMetricsServiceClient::CreateUploader( + const std::string& server_url, + const std::string& mime_type, + const base::Callback<void(int)>& on_upload_complete) { + std::string uma_server_url(server_url); + base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); + if (command_line->HasSwitch(switches::kOverrideMetricsUploadUrl)) { + uma_server_url.assign( + command_line->GetSwitchValueASCII(switches::kOverrideMetricsUploadUrl)); + } + DCHECK(!uma_server_url.empty()); + return scoped_ptr< ::metrics::MetricsLogUploader>( + new ::metrics::NetMetricsLogUploader( + request_context_, + uma_server_url, + mime_type, + on_upload_complete)); +} + +void CastMetricsServiceClient::EnableMetricsService(bool enabled) { + if (enabled) { + metrics_service_->Start(); + } else { + metrics_service_->Stop(); + } +} + +CastMetricsServiceClient::CastMetricsServiceClient( + base::TaskRunner* io_task_runner, + PrefService* pref_service, + net::URLRequestContextGetter* request_context) + : metrics_state_manager_(::metrics::MetricsStateManager::Create( + pref_service, + base::Bind(&CastMetricsServiceClient::IsReportingEnabled, + base::Unretained(this)), + base::Bind(&StoreClientInfo), + base::Bind(&LoadClientInfo))), + metrics_service_(new ::metrics::MetricsService( + metrics_state_manager_.get(), + this, + pref_service)), + request_context_(request_context) { + // Always create a client id as it may also be used by crash reporting, + // (indirectly) included in feedback, and can be queried during setup. + // For UMA and crash reporting, associated opt-in settings will control + // sending reports as directed by the user. + // For Setup (which also communicates the user's opt-in preferences), + // report the client-id and expect that setup will handle the current opt-in + // value. + metrics_state_manager_->ForceClientIdCreation(); + + metrics_service_->RegisterMetricsProvider( + scoped_ptr< ::metrics::MetricsProvider>( + new ::metrics::GPUMetricsProvider)); + metrics_service_->RegisterMetricsProvider( + scoped_ptr< ::metrics::MetricsProvider>( + new NetworkMetricsProvider(io_task_runner))); + metrics_service_->RegisterMetricsProvider( + scoped_ptr< ::metrics::MetricsProvider>( + new ::metrics::ProfilerMetricsProvider)); + RegisterPlatformMetricsProviders(metrics_service_.get()); + + metrics_service_->InitializeMetricsRecordingState(); + + if (IsReportingEnabled()) + metrics_service_->Start(); +} + +CastMetricsServiceClient::~CastMetricsServiceClient() { +} + +bool CastMetricsServiceClient::IsReportingEnabled() { + return PlatformIsReportingEnabled(); +} + +} // namespace metrics +} // namespace chromecast diff --git a/chromecast/metrics/cast_metrics_service_client.h b/chromecast/metrics/cast_metrics_service_client.h new file mode 100644 index 0000000000..fc55aea882 --- /dev/null +++ b/chromecast/metrics/cast_metrics_service_client.h @@ -0,0 +1,80 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROMECAST_METRICS_CAST_METRICS_SERVICE_CLIENT_H_ +#define CHROMECAST_METRICS_CAST_METRICS_SERVICE_CLIENT_H_ + +#include <string> + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "components/metrics/metrics_service_client.h" + +class PrefService; + +namespace base { +class TaskRunner; +} + +namespace metrics { +class MetricsService; +class MetricsStateManager; +} // namespace metrics + +namespace net { +class URLRequestContextGetter; +} // namespace net + +namespace chromecast { +namespace metrics { + +class CastMetricsServiceClient : public ::metrics::MetricsServiceClient { + public: + virtual ~CastMetricsServiceClient(); + + static CastMetricsServiceClient* Create( + base::TaskRunner* io_task_runner, + PrefService* pref_service, + net::URLRequestContextGetter* request_context); + + // metrics::MetricsServiceClient implementation: + virtual void SetMetricsClientId(const std::string& client_id) OVERRIDE; + virtual bool IsOffTheRecordSessionActive() OVERRIDE; + virtual std::string GetApplicationLocale() OVERRIDE; + virtual bool GetBrand(std::string* brand_code) OVERRIDE; + virtual ::metrics::SystemProfileProto::Channel GetChannel() OVERRIDE; + virtual std::string GetVersionString() OVERRIDE; + virtual void OnLogUploadComplete() OVERRIDE; + virtual void StartGatheringMetrics( + const base::Closure& done_callback) OVERRIDE; + virtual void CollectFinalMetrics(const base::Closure& done_callback) OVERRIDE; + virtual scoped_ptr< ::metrics::MetricsLogUploader> CreateUploader( + const std::string& server_url, + const std::string& mime_type, + const base::Callback<void(int)>& on_upload_complete) OVERRIDE; + + // Starts/stops the metrics service. + void EnableMetricsService(bool enabled); + + private: + CastMetricsServiceClient( + base::TaskRunner* io_task_runner, + PrefService* pref_service, + net::URLRequestContextGetter* request_context); + + // Returns whether or not metrics reporting is enabled. + bool IsReportingEnabled(); + + scoped_ptr< ::metrics::MetricsStateManager> metrics_state_manager_; + scoped_ptr< ::metrics::MetricsService> metrics_service_; + net::URLRequestContextGetter* request_context_; + + DISALLOW_COPY_AND_ASSIGN(CastMetricsServiceClient); +}; + +} // namespace metrics +} // namespace chromecast + +#endif // CHROMECAST_METRICS_CAST_METRICS_SERVICE_CLIENT_H_ diff --git a/chromecast/metrics/cast_metrics_service_client_unittest.cc b/chromecast/metrics/cast_metrics_service_client_unittest.cc new file mode 100644 index 0000000000..55e59b001a --- /dev/null +++ b/chromecast/metrics/cast_metrics_service_client_unittest.cc @@ -0,0 +1,42 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chromecast/metrics/cast_metrics_service_client.h" + +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/prefs/testing_pref_service.h" +#include "components/metrics/metrics_service.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace chromecast { + +class CastMetricsTest : public testing::Test { + public: + CastMetricsTest() {} + virtual ~CastMetricsTest() {} + + protected: + virtual void SetUp() OVERRIDE { + message_loop_.reset(new base::MessageLoop()); + prefs_.reset(new TestingPrefServiceSimple()); + ::metrics::MetricsService::RegisterPrefs(prefs_->registry()); + } + + TestingPrefServiceSimple* prefs() { return prefs_.get(); } + + private: + scoped_ptr<base::MessageLoop> message_loop_; + scoped_ptr<TestingPrefServiceSimple> prefs_; + + DISALLOW_COPY_AND_ASSIGN(CastMetricsTest); +}; + +TEST_F(CastMetricsTest, CreateMetricsServiceClient) { + // Create and expect this to not crash. + metrics::CastMetricsServiceClient::Create(prefs(), NULL); +} + +} // namespace chromecast diff --git a/chromecast/metrics/platform_metrics_providers.h b/chromecast/metrics/platform_metrics_providers.h new file mode 100644 index 0000000000..9d847e5c39 --- /dev/null +++ b/chromecast/metrics/platform_metrics_providers.h @@ -0,0 +1,39 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROMECAST_METRICS_PLATFORM_METRICS_PROVIDERS_H_ +#define CHROMECAST_METRICS_PLATFORM_METRICS_PROVIDERS_H_ + +#include "components/metrics/proto/system_profile.pb.h" + +namespace metrics { +class MetricsService; +} + +namespace chromecast { +namespace metrics { + +// Build-level hook for different platforms to provide data to MetricsService. +void RegisterPlatformMetricsProviders( + ::metrics::MetricsService* metrics_service); + +// Returns the current release channel. +::metrics::SystemProfileProto::Channel GetPlatformReleaseChannel(); + +// Returns a string representing this build's version. +std::string GetPlatformVersionString(); + +// Returns whether or not metrics reporting should be on. +bool PlatformIsReportingEnabled(); + +// Called when the UMA client ID has been set. +void PlatformSetClientID(const std::string& client_id); + +// Called when an upload has completed. +void PlatformOnLogUploadComplete(); + +} // namespace metrics +} // namespace chromecast + +#endif // CHROMECAST_METRICS_PLATFORM_METRICS_PROVIDERS_H_ diff --git a/chromecast/metrics/platform_metrics_providers_simple.cc b/chromecast/metrics/platform_metrics_providers_simple.cc new file mode 100644 index 0000000000..d312ec6017 --- /dev/null +++ b/chromecast/metrics/platform_metrics_providers_simple.cc @@ -0,0 +1,33 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chromecast/metrics/platform_metrics_providers.h" + +namespace chromecast { +namespace metrics { + +void RegisterPlatformMetricsProviders( + ::metrics::MetricsService* metrics_service) { +} + +::metrics::SystemProfileProto::Channel GetPlatformReleaseChannel() { + return ::metrics::SystemProfileProto::CHANNEL_STABLE; +} + +std::string GetPlatformVersionString() { + return ""; +} + +bool PlatformIsReportingEnabled() { + return false; +} + +void PlatformSetClientID(const std::string& client_id) { +} + +void PlatformOnLogUploadComplete() { +} + +} // namespace metrics +} // namespace chromecast diff --git a/chromecast/net/network_change_notifier_factory_cast.cc b/chromecast/net/network_change_notifier_factory_cast.cc index 1c41171119..c446b1e576 100644 --- a/chromecast/net/network_change_notifier_factory_cast.cc +++ b/chromecast/net/network_change_notifier_factory_cast.cc @@ -4,28 +4,16 @@ #include "chromecast/net/network_change_notifier_factory_cast.h" -#include "base/lazy_instance.h" #include "chromecast/net/network_change_notifier_cast.h" namespace chromecast { -namespace { - -base::LazyInstance<NetworkChangeNotifierCast> g_network_change_notifier_cast = - LAZY_INSTANCE_INITIALIZER; - -} // namespace - net::NetworkChangeNotifier* NetworkChangeNotifierFactoryCast::CreateInstance() { - return g_network_change_notifier_cast.Pointer(); + // Caller assumes ownership. + return new NetworkChangeNotifierCast(); } NetworkChangeNotifierFactoryCast::~NetworkChangeNotifierFactoryCast() { } -// static -NetworkChangeNotifierCast* NetworkChangeNotifierFactoryCast::GetInstance() { - return g_network_change_notifier_cast.Pointer(); -} - } // namespace chromecast diff --git a/chromecast/service/cast_service.h b/chromecast/service/cast_service.h index 606f8f7c87..bfc5d7a67f 100644 --- a/chromecast/service/cast_service.h +++ b/chromecast/service/cast_service.h @@ -12,15 +12,27 @@ namespace base { class ThreadChecker; } -namespace content{ +namespace content { class BrowserContext; } +namespace net { +class URLRequestContextGetter; +} + namespace chromecast { class CastService { public: - static CastService* Create(content::BrowserContext* browser_context); + // Create() takes a separate url request context getter because the request + // context getter obtained through the browser context might not be + // appropriate for the url requests made by the cast service/reciever. + // For example, on Chromecast, it is needed to pass in a system url request + // context getter that would set the request context for NSS, which the main + // getter doesn't do. + static CastService* Create( + content::BrowserContext* browser_context, + net::URLRequestContextGetter* request_context_getter); virtual ~CastService(); diff --git a/chromecast/service/cast_service_android.cc b/chromecast/service/cast_service_android.cc new file mode 100644 index 0000000000..4c8f30a44a --- /dev/null +++ b/chromecast/service/cast_service_android.cc @@ -0,0 +1,36 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chromecast/service/cast_service_android.h" + +#include "chromecast/android/chromecast_config_android.h" + +namespace chromecast { + +// static +CastService* CastService::Create( + content::BrowserContext* browser_context, + net::URLRequestContextGetter* request_context_getter) { + return new CastServiceAndroid(browser_context); +} + +CastServiceAndroid::CastServiceAndroid(content::BrowserContext* browser_context) + : CastService(browser_context) { +} + +CastServiceAndroid::~CastServiceAndroid() { +} + +void CastServiceAndroid::Initialize() { + // TODO(gunsch): Wire this the SendUsageStatsChanged callback once + // CastService::Delegate is added. +} + +void CastServiceAndroid::StartInternal() { +} + +void CastServiceAndroid::StopInternal() { +} + +} // namespace chromecast diff --git a/chromecast/service/cast_service_android.h b/chromecast/service/cast_service_android.h new file mode 100644 index 0000000000..234b012947 --- /dev/null +++ b/chromecast/service/cast_service_android.h @@ -0,0 +1,30 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROMECAST_SERVICE_CAST_SERVICE_ANDROID_H_ +#define CHROMECAST_SERVICE_CAST_SERVICE_ANDROID_H_ + +#include "base/macros.h" +#include "chromecast/service/cast_service.h" + +namespace chromecast { + +class CastServiceAndroid : public CastService { + public: + explicit CastServiceAndroid(content::BrowserContext* browser_context); + virtual ~CastServiceAndroid(); + + protected: + // CastService implementation. + virtual void Initialize() OVERRIDE; + virtual void StartInternal() OVERRIDE; + virtual void StopInternal() OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(CastServiceAndroid); +}; + +} // namespace chromecast + +#endif // CHROMECAST_SERVICE_CAST_SERVICE_ANDROID_H_ diff --git a/chromecast/service/cast_service_simple.cc b/chromecast/service/cast_service_simple.cc index 16b14b6016..c278667595 100644 --- a/chromecast/service/cast_service_simple.cc +++ b/chromecast/service/cast_service_simple.cc @@ -10,6 +10,7 @@ #include "content/public/browser/render_view_host.h" #include "content/public/browser/web_contents.h" #include "net/base/filename_util.h" +#include "net/url_request/url_request_context_getter.h" #include "ui/aura/env.h" #include "ui/aura/layout_manager.h" #include "ui/aura/test/test_screen.h" @@ -69,7 +70,9 @@ class FillLayout : public aura::LayoutManager { } // namespace // static -CastService* CastService::Create(content::BrowserContext* browser_context) { +CastService* CastService::Create( + content::BrowserContext* browser_context, + net::URLRequestContextGetter* request_context_getter) { return new CastServiceSimple(browser_context); } @@ -115,7 +118,7 @@ void CastServiceSimple::StartInternal() { web_contents_->GetController().LoadURL(GetStartupURL(), content::Referrer(), - content::PAGE_TRANSITION_TYPED, + ui::PAGE_TRANSITION_TYPED, std::string()); } diff --git a/chromecast/service/cast_service_simple.h b/chromecast/service/cast_service_simple.h index 40c037c4bc..534b2c06d4 100644 --- a/chromecast/service/cast_service_simple.h +++ b/chromecast/service/cast_service_simple.h @@ -24,7 +24,7 @@ class CastServiceSimple : public CastService { virtual ~CastServiceSimple(); protected: - // CastService implementation. + // CastService implementation: virtual void Initialize() OVERRIDE; virtual void StartInternal() OVERRIDE; virtual void StopInternal() OVERRIDE; diff --git a/chromecast/shell/android/DEPS b/chromecast/shell/android/DEPS new file mode 100644 index 0000000000..69269bce9e --- /dev/null +++ b/chromecast/shell/android/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+content/public/android", +] diff --git a/chromecast/shell/android/apk/AndroidManifest.xml b/chromecast/shell/android/apk/AndroidManifest.xml new file mode 100644 index 0000000000..63ad17c99e --- /dev/null +++ b/chromecast/shell/android/apk/AndroidManifest.xml @@ -0,0 +1,142 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- Copyright 2014 The Chromium Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="org.chromium.chromecast.shell"> + + <permission android:name="org.chromium.chromecast.shell.permission.SANDBOX" + android:protectionLevel="signature" /> + + <application android:name="org.chromium.chromecast.shell.CastApplication" + android:icon="@mipmap/app_icon" + android:label="@string/app_name"> + <activity android:name="org.chromium.chromecast.shell.CastShellActivity" + android:theme="@style/CastShellTheme" + android:exported="true" + android:hardwareAccelerated="true" + android:taskAffinity=".CastShellActivity" + android:configChanges="orientation|keyboardHidden|keyboard|screenSize" + android:excludeFromRecents="true" + android:noHistory="true" + android:permission="android.permission.INTERNET"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> + + <!-- The following service entries exist in order to allow us to + start more than one sandboxed process. --> + <service android:name="org.chromium.content.app.SandboxedProcessService0" + android:process=":sandboxed_process0" + android:permission="org.chromium.chromecast.shell.permission.SANDBOX" + android:isolatedProcess="true" + android:exported="false" /> + <service android:name="org.chromium.content.app.SandboxedProcessService1" + android:process=":sandboxed_process1" + android:permission="org.chromium.chromecast.shell.permission.SANDBOX" + android:isolatedProcess="true" + android:exported="false" /> + <service android:name="org.chromium.content.app.SandboxedProcessService2" + android:process=":sandboxed_process2" + android:permission="org.chromium.chromecast.shell.permission.SANDBOX" + android:isolatedProcess="true" + android:exported="false" /> + <service android:name="org.chromium.content.app.SandboxedProcessService3" + android:process=":sandboxed_process3" + android:permission="org.chromium.chromecast.shell.permission.SANDBOX" + android:isolatedProcess="true" + android:exported="false" /> + <service android:name="org.chromium.content.app.SandboxedProcessService4" + android:process=":sandboxed_process4" + android:permission="org.chromium.chromecast.shell.permission.SANDBOX" + android:isolatedProcess="true" + android:exported="false" /> + <service android:name="org.chromium.content.app.SandboxedProcessService5" + android:process=":sandboxed_process5" + android:permission="org.chromium.chromecast.shell.permission.SANDBOX" + android:isolatedProcess="true" + android:exported="false" /> + <service android:name="org.chromium.content.app.SandboxedProcessService6" + android:process=":sandboxed_process6" + android:permission="org.chromium.chromecast.shell.permission.SANDBOX" + android:isolatedProcess="true" + android:exported="false" /> + <service android:name="org.chromium.content.app.SandboxedProcessService7" + android:process=":sandboxed_process7" + android:permission="org.chromium.chromecast.shell.permission.SANDBOX" + android:isolatedProcess="true" + android:exported="false" /> + <service android:name="org.chromium.content.app.SandboxedProcessService8" + android:process=":sandboxed_process8" + android:permission="org.chromium.chromecast.shell.permission.SANDBOX" + android:isolatedProcess="true" + android:exported="false" /> + <service android:name="org.chromium.content.app.SandboxedProcessService9" + android:process=":sandboxed_process9" + android:permission="org.chromium.chromecast.shell.permission.SANDBOX" + android:isolatedProcess="true" + android:exported="false" /> + <service android:name="org.chromium.content.app.SandboxedProcessService10" + android:process=":sandboxed_process10" + android:permission="org.chromium.chromecast.shell.permission.SANDBOX" + android:isolatedProcess="true" + android:exported="false" /> + <service android:name="org.chromium.content.app.SandboxedProcessService11" + android:process=":sandboxed_process11" + android:permission="org.chromium.chromecast.shell.permission.SANDBOX" + android:isolatedProcess="true" + android:exported="false" /> + <service android:name="org.chromium.content.app.SandboxedProcessService12" + android:process=":sandboxed_process12" + android:permission="org.chromium.chromecast.shell.permission.SANDBOX" + android:isolatedProcess="true" + android:exported="false" /> + <service android:name="org.chromium.content.app.SandboxedProcessService13" + android:process=":sandboxed_process13" + android:permission="org.chromium.content_shell.permission.SANDBOX" + android:isolatedProcess="true" + android:exported="false" /> + <service android:name="org.chromium.content.app.SandboxedProcessService14" + android:process=":sandboxed_process14" + android:permission="org.chromium.content_shell.permission.SANDBOX" + android:isolatedProcess="true" + android:exported="false" /> + <service android:name="org.chromium.content.app.SandboxedProcessService15" + android:process=":sandboxed_process15" + android:permission="org.chromium.content_shell.permission.SANDBOX" + android:isolatedProcess="true" + android:exported="false" /> + <service android:name="org.chromium.content.app.SandboxedProcessService16" + android:process=":sandboxed_process16" + android:permission="org.chromium.content_shell.permission.SANDBOX" + android:isolatedProcess="true" + android:exported="false" /> + <service android:name="org.chromium.content.app.SandboxedProcessService17" + android:process=":sandboxed_process17" + android:permission="org.chromium.content_shell.permission.SANDBOX" + android:isolatedProcess="true" + android:exported="false" /> + <service android:name="org.chromium.content.app.SandboxedProcessService18" + android:process=":sandboxed_process18" + android:permission="org.chromium.content_shell.permission.SANDBOX" + android:isolatedProcess="true" + android:exported="false" /> + <service android:name="org.chromium.content.app.SandboxedProcessService19" + android:process=":sandboxed_process19" + android:permission="org.chromium.content_shell.permission.SANDBOX" + android:isolatedProcess="true" + android:exported="false" /> + </application> + + <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="19" /> + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> + <uses-permission android:name="android.permission.INTERNET"/> + <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/> + <uses-permission android:name="android.permission.WAKE_LOCK"/> + +</manifest> diff --git a/chromecast/shell/android/apk/res/layout/cast_shell_activity.xml b/chromecast/shell/android/apk/res/layout/cast_shell_activity.xml new file mode 100644 index 0000000000..428b1a512a --- /dev/null +++ b/chromecast/shell/android/apk/res/layout/cast_shell_activity.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- Copyright 2014 The Chromium Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. + --> + +<merge xmlns:android="http://schemas.android.com/apk/res/android"> + <org.chromium.chromecast.shell.CastWindowManager + android:id="@+id/shell_container" + android:layout_width="match_parent" + android:layout_height="match_parent" /> +</merge> diff --git a/chromecast/shell/android/apk/res/layout/cast_window_view.xml b/chromecast/shell/android/apk/res/layout/cast_window_view.xml new file mode 100644 index 0000000000..ce57c9e556 --- /dev/null +++ b/chromecast/shell/android/apk/res/layout/cast_window_view.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- Copyright 2014 The Chromium Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. + --> + +<org.chromium.chromecast.shell.CastWindowAndroid + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/container" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" > + <FrameLayout android:id="@+id/contentview_holder" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" /> +</org.chromium.chromecast.shell.CastWindowAndroid> diff --git a/chromecast/shell/android/apk/res/mipmap-hdpi/app_icon.png b/chromecast/shell/android/apk/res/mipmap-hdpi/app_icon.png Binary files differnew file mode 100644 index 0000000000..72d8318a89 --- /dev/null +++ b/chromecast/shell/android/apk/res/mipmap-hdpi/app_icon.png diff --git a/chromecast/shell/android/apk/res/mipmap-mdpi/app_icon.png b/chromecast/shell/android/apk/res/mipmap-mdpi/app_icon.png Binary files differnew file mode 100644 index 0000000000..4076d5d276 --- /dev/null +++ b/chromecast/shell/android/apk/res/mipmap-mdpi/app_icon.png diff --git a/chromecast/shell/android/apk/res/mipmap-xhdpi/app_icon.png b/chromecast/shell/android/apk/res/mipmap-xhdpi/app_icon.png Binary files differnew file mode 100644 index 0000000000..c7f092c36b --- /dev/null +++ b/chromecast/shell/android/apk/res/mipmap-xhdpi/app_icon.png diff --git a/chromecast/shell/android/apk/res/mipmap-xxhdpi/app_icon.png b/chromecast/shell/android/apk/res/mipmap-xxhdpi/app_icon.png Binary files differnew file mode 100644 index 0000000000..9addc3b09f --- /dev/null +++ b/chromecast/shell/android/apk/res/mipmap-xxhdpi/app_icon.png diff --git a/chromecast/shell/android/apk/res/values-v17/styles.xml b/chromecast/shell/android/apk/res/values-v17/styles.xml new file mode 100644 index 0000000000..80eaf9be38 --- /dev/null +++ b/chromecast/shell/android/apk/res/values-v17/styles.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- Copyright 2014 The Chromium Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. +--> + +<resources> + <style name="CastShellTheme" parent="@android:style/Theme.Holo.Light.NoActionBar"> + <item name="android:windowBackground">@android:color/black</item> + </style> +</resources> diff --git a/chromecast/shell/android/apk/res/values/strings.xml b/chromecast/shell/android/apk/res/values/strings.xml new file mode 100644 index 0000000000..9ca510a6ef --- /dev/null +++ b/chromecast/shell/android/apk/res/values/strings.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- Copyright 2014 The Chromium Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android"> + <string name="app_name">Chromecast Android Shell</string> + <string name="browser_process_initialization_failed">Initialization failed.</string> +</resources> diff --git a/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastApplication.java b/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastApplication.java new file mode 100644 index 0000000000..65ad319c7c --- /dev/null +++ b/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastApplication.java @@ -0,0 +1,35 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.chromecast.shell; + +import org.chromium.base.PathUtils; +import org.chromium.base.ResourceExtractor; +import org.chromium.content.app.ContentApplication; + +/** + * Entry point for the Android cast shell application. Handles initialization of information that + * needs to be shared across the main activity and the child services created. + * + * Note that this gets run for each process, including sandboxed child render processes. Child + * processes don't need most of the full "setup" performed in CastBrowserHelper.java, but they do + * require a few basic pieces (found here). + */ +public class CastApplication extends ContentApplication { + + private static final String[] MANDATORY_PAK_FILES = new String[] {"cast_shell.pak"}; + private static final String PRIVATE_DATA_DIRECTORY_SUFFIX = "cast_shell"; + + @Override + public void onCreate() { + super.onCreate(); + initializeApplicationParameters(); + } + + public static void initializeApplicationParameters() { + ResourceExtractor.setMandatoryPaksToExtract(MANDATORY_PAK_FILES); + PathUtils.setPrivateDataDirectorySuffix(PRIVATE_DATA_DIRECTORY_SUFFIX); + } + +} diff --git a/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastBrowserHelper.java b/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastBrowserHelper.java new file mode 100644 index 0000000000..d3ea8eaebf --- /dev/null +++ b/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastBrowserHelper.java @@ -0,0 +1,100 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.chromecast.shell; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.os.Build; +import android.os.Debug; +import android.util.Log; + +import org.chromium.base.BaseSwitches; +import org.chromium.base.CommandLine; +import org.chromium.base.library_loader.LibraryLoader; +import org.chromium.base.library_loader.ProcessInitException; +import org.chromium.content.browser.BrowserStartupController; +import org.chromium.content.browser.DeviceUtils; +import org.chromium.content.common.ContentSwitches; + +/** + * Static, one-time initialization for the browser process. + */ +public class CastBrowserHelper { + private static final String TAG = "CastBrowserHelper"; + + public static final String COMMAND_LINE_FILE = "/data/local/tmp/castshell-command-line"; + public static final String COMMAND_LINE_ARGS_KEY = "commandLineArgs"; + + private static boolean sIsBrowserInitialized = false; + + /** + * Starts the browser process synchronously, returning success or failure. If the browser has + * already started, immediately returns true without performing any more initialization. + * This may only be called on the UI thread. + * + * @return whether or not the process started successfully + */ + public static boolean initializeBrowser(Context context) { + if (sIsBrowserInitialized) return true; + + Log.d(TAG, "Performing one-time browser initialization"); + + // Initializing the command line must occur before loading the library. + if (!CommandLine.isInitialized()) { + if (allowCommandLineImport()) { + Log.d(TAG, "Initializing command line from " + COMMAND_LINE_FILE); + CommandLine.initFromFile(COMMAND_LINE_FILE); + } else { + CommandLine.init(null); + } + + if (context instanceof Activity) { + Intent launchingIntent = ((Activity) context).getIntent(); + String[] commandLineParams = getCommandLineParamsFromIntent(launchingIntent); + if (commandLineParams != null) { + CommandLine.getInstance().appendSwitchesAndArguments(commandLineParams); + } + } + } + + CommandLine.getInstance().appendSwitchWithValue( + ContentSwitches.FORCE_DEVICE_SCALE_FACTOR, "1"); + + waitForDebuggerIfNeeded(); + + DeviceUtils.addDeviceSpecificUserAgentSwitch(context); + + try { + LibraryLoader.ensureInitialized(); + + Log.d(TAG, "Loading BrowserStartupController..."); + BrowserStartupController.get(context).startBrowserProcessesSync(false); + + sIsBrowserInitialized = true; + return true; + } catch (ProcessInitException e) { + Log.e(TAG, "Unable to launch browser process.", e); + return false; + } + } + + private static boolean allowCommandLineImport() { + return !Build.TYPE.equals("user"); + } + + private static String[] getCommandLineParamsFromIntent(Intent intent) { + return intent != null ? intent.getStringArrayExtra(COMMAND_LINE_ARGS_KEY) : null; + } + + private static void waitForDebuggerIfNeeded() { + if (!CommandLine.getInstance().hasSwitch(BaseSwitches.WAIT_FOR_JAVA_DEBUGGER)) { + return; + } + Log.e(TAG, "Waiting for Java debugger to connect..."); + Debug.waitForDebugger(); + Log.e(TAG, "Java debugger connected. Resuming execution."); + } +} diff --git a/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastShellActivity.java b/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastShellActivity.java new file mode 100644 index 0000000000..46152e7b63 --- /dev/null +++ b/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastShellActivity.java @@ -0,0 +1,291 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.chromecast.shell; + +import android.app.Activity; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.media.AudioManager; +import android.net.Uri; +import android.os.Bundle; +import android.support.v4.content.LocalBroadcastManager; +import android.util.Log; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.WindowManager; +import android.widget.Toast; + +import org.chromium.base.CommandLine; +import org.chromium.content.browser.ActivityContentVideoViewClient; +import org.chromium.content.browser.ContentVideoViewClient; +import org.chromium.content.browser.ContentViewClient; +import org.chromium.content.browser.ContentViewCore; +import org.chromium.ui.base.WindowAndroid; + +/** + * Activity for managing the Cast shell. + */ +public class CastShellActivity extends Activity { + private static final String TAG = "CastShellActivity"; + + private static final String ACTIVE_SHELL_URL_KEY = "activeUrl"; + private static final int DEFAULT_HEIGHT_PIXELS = 720; + public static final String ACTION_EXTRA_RESOLUTION_HEIGHT = + "org.chromium.chromecast.shell.intent.extra.RESOLUTION_HEIGHT"; + + private CastWindowManager mCastWindowManager; + private AudioManager mAudioManager; + private BroadcastReceiver mBroadcastReceiver; + + // Native window instance. + // TODO(byungchul, gunsch): CastShellActivity, CastWindowAndroid, and native CastWindowAndroid + // have a one-to-one relationship. Consider instantiating CastWindow here and CastWindow having + // this native shell instance. + private long mNativeCastWindow; + + /** + * Returns whether or not CastShellActivity should launch the browser startup sequence. + * Intended to be overridden. + */ + protected boolean shouldLaunchBrowser() { + return true; + } + + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + exitIfUrlMissing(); + + if (shouldLaunchBrowser()) { + if (!CastBrowserHelper.initializeBrowser(getApplicationContext())) { + Toast.makeText(this, + R.string.browser_process_initialization_failed, + Toast.LENGTH_SHORT).show(); + finish(); + } + } + + // Whenever our app is visible, volume controls should modify the music stream. + // For more information read: + // http://developer.android.com/training/managing-audio/volume-playback.html + setVolumeControlStream(AudioManager.STREAM_MUSIC); + + // Set flags to both exit sleep mode when this activity starts and + // avoid entering sleep mode while playing media. We cannot distinguish + // between video and audio so this applies to both. + getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + + mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); + + setContentView(R.layout.cast_shell_activity); + mCastWindowManager = (CastWindowManager) findViewById(R.id.shell_container); + mCastWindowManager.setDelegate(new CastWindowManager.Delegate() { + @Override + public void onCreated() { + } + + @Override + public void onClosed() { + mNativeCastWindow = 0; + mCastWindowManager.setDelegate(null); + finish(); + } + }); + setResolution(); + mCastWindowManager.setWindow(new WindowAndroid(this)); + + registerBroadcastReceiver(); + + String url = getIntent().getDataString(); + Log.d(TAG, "onCreate startupUrl: " + url); + mNativeCastWindow = mCastWindowManager.launchCastWindow(url); + + getActiveContentViewCore().setContentViewClient(new ContentViewClient() { + @Override + public ContentVideoViewClient getContentVideoViewClient() { + return new ActivityContentVideoViewClient(CastShellActivity.this); + } + }); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + + unregisterBroadcastReceiver(); + + if (mNativeCastWindow != 0) { + mCastWindowManager.stopCastWindow(mNativeCastWindow, false /* gracefully */); + mNativeCastWindow = 0; + } + } + + @Override + protected void onNewIntent(Intent intent) { + // Only handle direct intents (e.g. "fling") if this activity is also managing + // the browser process. + if (!shouldLaunchBrowser()) return; + + String url = intent.getDataString(); + Log.d(TAG, "onNewIntent: " + url); + + // Reset broadcast intent uri and receiver. + setIntent(intent); + exitIfUrlMissing(); + getActiveCastWindow().loadUrl(url); + } + + @Override + protected void onResume() { + super.onResume(); + + // Inform ContentView that this activity is being shown. + ContentViewCore view = getActiveContentViewCore(); + if (view != null) view.onShow(); + + // Request audio focus so any other audio playback doesn't continue in the background. + if (mAudioManager.requestAudioFocus( + null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN) + != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { + Log.e(TAG, "Failed to obtain audio focus"); + } + } + + @Override + protected void onPause() { + // As soon as the cast app is no longer in the foreground, we ought to immediately tear + // everything down. Apps should not continue running and playing sound in the background. + super.onPause(); + + // Release the audio focus. Note that releasing audio focus does not stop audio playback, + // it just notifies the framework that this activity has stopped playing audio. + if (mAudioManager.abandonAudioFocus(null) != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { + Log.e(TAG, "Failed to abandon audio focus"); + } + + ContentViewCore view = getActiveContentViewCore(); + if (view != null) view.onHide(); + + finishGracefully(); + } + + protected void finishGracefully() { + if (mNativeCastWindow != 0) { + mCastWindowManager.stopCastWindow(mNativeCastWindow, true /* gracefully */); + mNativeCastWindow = 0; + } + } + + private void registerBroadcastReceiver() { + if (mBroadcastReceiver == null) { + mBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + Log.d(TAG, "Received intent: action=" + intent.getAction()); + if (CastWindowAndroid.ACTION_ENABLE_DEV_TOOLS.equals(intent.getAction())) { + mCastWindowManager.nativeEnableDevTools(true); + } else if (CastWindowAndroid.ACTION_DISABLE_DEV_TOOLS.equals( + intent.getAction())) { + mCastWindowManager.nativeEnableDevTools(false); + } + } + }; + } + + IntentFilter devtoolsBroadcastIntentFilter = new IntentFilter(); + devtoolsBroadcastIntentFilter.addAction(CastWindowAndroid.ACTION_ENABLE_DEV_TOOLS); + devtoolsBroadcastIntentFilter.addAction(CastWindowAndroid.ACTION_DISABLE_DEV_TOOLS); + LocalBroadcastManager.getInstance(this) + .registerReceiver(mBroadcastReceiver, devtoolsBroadcastIntentFilter); + } + + private void unregisterBroadcastReceiver() { + LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(this); + broadcastManager.unregisterReceiver(mBroadcastReceiver); + } + + private void setResolution() { + int requestedHeight = getIntent().getIntExtra( + ACTION_EXTRA_RESOLUTION_HEIGHT, DEFAULT_HEIGHT_PIXELS); + int displayHeight = getResources().getDisplayMetrics().heightPixels; + // Clamp within [DEFAULT_HEIGHT_PIXELS, displayHeight] + int desiredHeight = + Math.min(displayHeight, Math.max(DEFAULT_HEIGHT_PIXELS, requestedHeight)); + double deviceScaleFactor = ((double) displayHeight) / desiredHeight; + Log.d(TAG, "Using scale factor " + deviceScaleFactor + " to set height " + desiredHeight); + CommandLine.getInstance().appendSwitchWithValue("force-device-scale-factor", + String.valueOf(deviceScaleFactor)); + } + + private void exitIfUrlMissing() { + Intent intent = getIntent(); + if (intent != null && intent.getData() != null && !intent.getData().equals(Uri.EMPTY)) { + return; + } + // Log an exception so that the exit cause is obvious when reading the logs. + Log.e(TAG, "Activity will not start", + new IllegalArgumentException("Intent did not contain a valid url")); + System.exit(-1); + } + + /** + * @return The currently visible {@link CastWindowAndroid} or null if one is not showing. + */ + public CastWindowAndroid getActiveCastWindow() { + return mCastWindowManager.getActiveCastWindow(); + } + + /** + * @return The {@link ContentViewCore} owned by the currently visible {@link CastWindowAndroid}, + * or null if one is not showing. + */ + public ContentViewCore getActiveContentViewCore() { + CastWindowAndroid shell = getActiveCastWindow(); + return shell != null ? shell.getContentViewCore() : null; + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + if (keyCode != KeyEvent.KEYCODE_BACK) { + return super.onKeyUp(keyCode, event); + } + + // Just finish this activity to go back to the previous activity or launcher. + finishGracefully(); + return true; + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + int keyCode = event.getKeyCode(); + if (keyCode == KeyEvent.KEYCODE_BACK) { + return super.dispatchKeyEvent(event); + } + return false; + } + + @Override + public boolean dispatchGenericMotionEvent(MotionEvent ev) { + return false; + } + + @Override + public boolean dispatchKeyShortcutEvent(KeyEvent event) { + return false; + } + + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + return false; + } + + @Override + public boolean dispatchTrackballEvent(MotionEvent ev) { + return false; + } +} diff --git a/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastWindowAndroid.java b/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastWindowAndroid.java new file mode 100644 index 0000000000..c0851b8638 --- /dev/null +++ b/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastWindowAndroid.java @@ -0,0 +1,161 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.chromecast.shell; + +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.support.v4.content.LocalBroadcastManager; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.util.Log; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.LinearLayout; + +import org.chromium.base.CalledByNative; +import org.chromium.base.JNINamespace; +import org.chromium.content.browser.ContentView; +import org.chromium.content.browser.ContentViewCore; +import org.chromium.content.browser.ContentViewRenderView; +import org.chromium.content.browser.WebContentsObserverAndroid; +import org.chromium.content_public.browser.LoadUrlParams; +import org.chromium.content_public.browser.NavigationController; +import org.chromium.content_public.browser.WebContents; +import org.chromium.ui.base.WindowAndroid; + +/** + * Container for the various UI components that make up a shell window. + */ +@JNINamespace("chromecast::shell") +public class CastWindowAndroid extends LinearLayout { + public static final String TAG = "CastWindowAndroid"; + + public static final String ACTION_PAGE_LOADED = "castPageLoaded"; + public static final String ACTION_ENABLE_DEV_TOOLS = "castEnableDevTools"; + public static final String ACTION_DISABLE_DEV_TOOLS = "castDisableDevTools"; + + private ContentViewCore mContentViewCore; + private ContentViewRenderView mContentViewRenderView; + private NavigationController mNavigationController; + private WebContents mWebContents; + private WebContentsObserverAndroid mWebContentsObserver; + private WindowAndroid mWindow; + + /** + * Constructor for inflating via XML. + */ + public CastWindowAndroid(Context context, AttributeSet attrs) { + super(context, attrs); + } + + /** + * Set the SurfaceView being renderered to as soon as it is available. + */ + public void setContentViewRenderView(ContentViewRenderView contentViewRenderView) { + FrameLayout contentViewHolder = (FrameLayout) findViewById(R.id.contentview_holder); + if (contentViewRenderView == null) { + if (mContentViewRenderView != null) { + contentViewHolder.removeView(mContentViewRenderView); + } + } else { + contentViewHolder.addView(contentViewRenderView, + new FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.MATCH_PARENT)); + } + mContentViewRenderView = contentViewRenderView; + } + + /** + * @param window The owning window for this shell. + */ + public void setWindow(WindowAndroid window) { + mWindow = window; + } + + /** + * Loads an URL. This will perform minimal amounts of sanitizing of the URL to attempt to + * make it valid. + * + * @param url The URL to be loaded by the shell. + */ + public void loadUrl(String url) { + if (url == null) return; + + if (TextUtils.equals(url, mWebContents.getUrl())) { + mNavigationController.reload(true); + } else { + mNavigationController.loadUrl(new LoadUrlParams(normalizeUrl(url))); + } + + // TODO(aurimas): Remove this when crbug.com/174541 is fixed. + mContentViewCore.getContainerView().clearFocus(); + mContentViewCore.getContainerView().requestFocus(); + } + + /** + * Given a URI String, performs minimal normalization to attempt to build a usable URL from it. + * @param uriString The passed-in path to be normalized. + * @return The normalized URL, as a string. + */ + private static String normalizeUrl(String uriString) { + if (uriString == null) return uriString; + Uri uri = Uri.parse(uriString); + if (uri.getScheme() == null) { + uri = Uri.parse("http://" + uriString); + } + return uri.toString(); + } + + /** + * Initializes the ContentView based on the native tab contents pointer passed in. + * @param nativeWebContents The pointer to the native tab contents object. + */ + @SuppressWarnings("unused") + @CalledByNative + private void initFromNativeWebContents(long nativeWebContents) { + Context context = getContext(); + mContentViewCore = new ContentViewCore(context); + ContentView view = ContentView.newInstance(context, mContentViewCore); + mContentViewCore.initialize(view, view, nativeWebContents, mWindow); + mWebContents = mContentViewCore.getWebContents(); + mNavigationController = mWebContents.getNavigationController(); + + if (getParent() != null) mContentViewCore.onShow(); + ((FrameLayout) findViewById(R.id.contentview_holder)).addView(view, + new FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.MATCH_PARENT)); + view.requestFocus(); + mContentViewRenderView.setCurrentContentViewCore(mContentViewCore); + + mWebContentsObserver = new WebContentsObserverAndroid(mWebContents) { + @Override + public void didStopLoading(String url) { + Uri intentUri = Uri.parse(mNavigationController + .getOriginalUrlForVisibleNavigationEntry()); + Log.v(TAG, "Broadcast ACTION_PAGE_LOADED: scheme=" + intentUri.getScheme() + + ", host=" + intentUri.getHost()); + LocalBroadcastManager.getInstance(getContext()).sendBroadcast( + new Intent(ACTION_PAGE_LOADED, intentUri)); + } + }; + } + + /** + * @return The {@link ViewGroup} currently shown by this Shell. + */ + public ViewGroup getContentView() { + return mContentViewCore.getContainerView(); + } + + /** + * @return The {@link ContentViewCore} currently managing the view shown by this Shell. + */ + public ContentViewCore getContentViewCore() { + return mContentViewCore; + } +} diff --git a/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastWindowManager.java b/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastWindowManager.java new file mode 100644 index 0000000000..3d3a4e8e50 --- /dev/null +++ b/chromecast/shell/android/apk/src/org/chromium/chromecast/shell/CastWindowManager.java @@ -0,0 +1,157 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.chromecast.shell; + +import android.content.Context; +import android.graphics.Color; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.widget.FrameLayout; + +import org.chromium.base.CalledByNative; +import org.chromium.base.JNINamespace; +import org.chromium.content.browser.ContentViewCore; +import org.chromium.content.browser.ContentViewRenderView; +import org.chromium.ui.base.WindowAndroid; + +/** + * Container and generator of CastWindow instances. + */ +@JNINamespace("chromecast::shell") +public class CastWindowManager extends FrameLayout { + private static final String TAG = "CastWindowManager"; + + private WindowAndroid mWindow; + private CastWindowAndroid mActiveCastWindow; + + // The target for all content rendering. + private ContentViewRenderView mContentViewRenderView; + + /** + * Delegate to deliver events from the native window. + */ + public interface Delegate { + public void onCreated(); + public void onClosed(); + } + private Delegate mDelegate; + + /** + * Constructor for inflating via XML. + */ + public CastWindowManager(Context context, AttributeSet attrs) { + super(context, attrs); + nativeInit(this); + } + + /** + * @param delegate Delegate to handle events. + */ + public void setDelegate(Delegate delegate) { + mDelegate = delegate; + } + + /** + * @param window Represents the activity window. + */ + public void setWindow(WindowAndroid window) { + assert window != null; + mWindow = window; + mContentViewRenderView = new ContentViewRenderView(getContext()) { + @Override + protected void onReadyToRender() { + setOverlayVideoMode(true); + } + }; + mContentViewRenderView.onNativeLibraryLoaded(window); + // Setting the background color to black avoids rendering a white splash screen + // before the players are loaded. See crbug/307113 for details. + mContentViewRenderView.setSurfaceViewBackgroundColor(Color.BLACK); + } + + /** + * @return The window used to generate all shells. + */ + public WindowAndroid getWindow() { + return mWindow; + } + + /** + * @return The currently visible shell view or null if one is not showing. + */ + public CastWindowAndroid getActiveCastWindow() { + return mActiveCastWindow; + } + + /** + * Creates a new shell pointing to the specified URL. + * @param url The URL the shell should load upon creation. + * @return Pointer of native cast shell instance. + */ + public long launchCastWindow(String url) { + return nativeLaunchCastWindow(url); + } + + /** + * Stops a native cast shell instance created by {@link #launchCastWindow(String)}. + * @param nativeCastWindow Pointer of native cast shell instance returned + * by {@link #launchCastWindow(String)}. + * @param gracefully Whether or not to call RVH::ClosePage to deliver unload event. + * @see #launchCastWindow(String) + */ + public void stopCastWindow(long nativeCastWindow, boolean gracefully) { + nativeStopCastWindow(nativeCastWindow, gracefully); + } + + @SuppressWarnings("unused") + @CalledByNative + private Object createCastWindow() { + assert mContentViewRenderView != null; + LayoutInflater inflater = + (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); + CastWindowAndroid shellView = + (CastWindowAndroid) inflater.inflate(R.layout.cast_window_view, null); + shellView.setWindow(mWindow); + + if (mActiveCastWindow != null) closeCastWindow(mActiveCastWindow); + + shellView.setContentViewRenderView(mContentViewRenderView); + addView(shellView, new FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)); + mActiveCastWindow = shellView; + ContentViewCore contentViewCore = mActiveCastWindow.getContentViewCore(); + if (contentViewCore != null) { + mContentViewRenderView.setCurrentContentViewCore(contentViewCore); + contentViewCore.onShow(); + } + + if (mDelegate != null) { + mDelegate.onCreated(); + } + + return shellView; + } + + @SuppressWarnings("unused") + @CalledByNative + private void closeCastWindow(CastWindowAndroid shellView) { + if (shellView == mActiveCastWindow) mActiveCastWindow = null; + ContentViewCore contentViewCore = shellView.getContentViewCore(); + if (contentViewCore != null) contentViewCore.onHide(); + shellView.setContentViewRenderView(null); + shellView.setWindow(null); + removeView(shellView); + + if (mDelegate != null) { + mDelegate.onClosed(); + } + } + + private static native void nativeInit(Object shellManagerInstance); + private static native long nativeLaunchCastWindow(String url); + private static native void nativeStopCastWindow(long pointerOfNativeCastWindow, + boolean gracefully); + public static native void nativeEnableDevTools(boolean enable); +} diff --git a/chromecast/shell/app/DEPS b/chromecast/shell/app/DEPS index 7c1793ed98..efc610d592 100644 --- a/chromecast/shell/app/DEPS +++ b/chromecast/shell/app/DEPS @@ -1,3 +1,4 @@ include_rules = [ "+content/public/app", + "+content/public/browser", ] diff --git a/chromecast/shell/app/android/cast_jni_loader.cc b/chromecast/shell/app/android/cast_jni_loader.cc new file mode 100644 index 0000000000..4cbf8f8e15 --- /dev/null +++ b/chromecast/shell/app/android/cast_jni_loader.cc @@ -0,0 +1,36 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/android/jni_android.h" +#include "base/android/jni_registrar.h" +#include "base/android/library_loader/library_loader_hooks.h" +#include "base/basictypes.h" +#include "base/debug/debugger.h" +#include "base/logging.h" +#include "chromecast/android/cast_jni_registrar.h" +#include "chromecast/android/platform_jni_loader.h" +#include "chromecast/shell/app/cast_main_delegate.h" +#include "content/public/app/android_library_loader_hooks.h" +#include "content/public/app/content_main.h" +#include "content/public/browser/android/compositor.h" + +// This is called by the VM when the shared library is first loaded. +JNI_EXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { + base::android::SetLibraryLoadedHook(&content::LibraryLoaded); + base::android::InitVM(vm); + JNIEnv* env = base::android::AttachCurrentThread(); + + if (!base::android::RegisterLibraryLoaderEntryHook(env)) return -1; + + // To be called only from the UI thread. If loading the library is done on + // a separate thread, this should be moved elsewhere. + if (!chromecast::android::RegisterJni(env)) return -1; + // Allow platform-specific implementations to perform more JNI registration. + if (!chromecast::android::PlatformRegisterJni(env)) return -1; + + content::Compositor::Initialize(); + content::SetContentMainDelegate(new chromecast::shell::CastMainDelegate); + + return JNI_VERSION_1_4; +} diff --git a/chromecast/shell/app/cast_main_delegate.cc b/chromecast/shell/app/cast_main_delegate.cc index 1fb2ad773a..719cc2b719 100644 --- a/chromecast/shell/app/cast_main_delegate.cc +++ b/chromecast/shell/app/cast_main_delegate.cc @@ -4,12 +4,16 @@ #include "chromecast/shell/app/cast_main_delegate.h" +#include "base/cpu.h" #include "base/logging.h" #include "base/path_service.h" +#include "base/posix/global_descriptors.h" #include "chromecast/common/cast_paths.h" #include "chromecast/common/cast_resource_delegate.h" +#include "chromecast/common/global_descriptors.h" #include "chromecast/shell/browser/cast_content_browser_client.h" #include "chromecast/shell/renderer/cast_content_renderer_client.h" +#include "content/public/browser/browser_main_runner.h" #include "content/public/common/content_switches.h" #include "ui/base/resource/resource_bundle.h" @@ -23,26 +27,74 @@ CastMainDelegate::~CastMainDelegate() { } bool CastMainDelegate::BasicStartupComplete(int* exit_code) { + RegisterPathProvider(); + logging::LoggingSettings settings; +#if defined(OS_ANDROID) + base::FilePath log_file; + PathService::Get(FILE_CAST_ANDROID_LOG, &log_file); + settings.logging_dest = logging::LOG_TO_ALL; + settings.log_file = log_file.value().c_str(); + settings.delete_old = logging::DELETE_OLD_LOG_FILE; +#else settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG; +#endif // defined(OS_ANDROID) logging::InitLogging(settings); // Time, process, and thread ID are available through logcat. logging::SetLogItems(true, true, false, false); - RegisterPathProvider(); - content::SetContentClient(&content_client_); return false; } void CastMainDelegate::PreSandboxStartup() { +#if defined(ARCH_CPU_ARM_FAMILY) && (defined(OS_ANDROID) || defined(OS_LINUX)) + // Create an instance of the CPU class to parse /proc/cpuinfo and cache the + // results. This data needs to be cached when file-reading is still allowed, + // since base::CPU expects to be callable later, when file-reading is no + // longer allowed. + base::CPU cpu_info; +#endif + InitializeResourceBundle(); } +int CastMainDelegate::RunProcess( + const std::string& process_type, + const content::MainFunctionParams& main_function_params) { +#if defined(OS_ANDROID) + if (!process_type.empty()) + return -1; + + // Note: Android must handle running its own browser process. + // See ChromeMainDelegateAndroid::RunProcess. + browser_runner_.reset(content::BrowserMainRunner::Create()); + return browser_runner_->Initialize(main_function_params); +#else + return -1; +#endif // defined(OS_ANDROID) +} + +#if !defined(OS_ANDROID) void CastMainDelegate::ZygoteForked() { } +#endif // !defined(OS_ANDROID) void CastMainDelegate::InitializeResourceBundle() { +#if defined(OS_ANDROID) + // On Android, the renderer runs with a different UID and can never access + // the file system. Use the file descriptor passed in at launch time. + int pak_fd = + base::GlobalDescriptors::GetInstance()->MaybeGet(kAndroidPakDescriptor); + if (pak_fd >= 0) { + ui::ResourceBundle::InitSharedInstanceWithPakFileRegion( + base::File(pak_fd), base::MemoryMappedFile::Region::kWholeFile); + ui::ResourceBundle::GetSharedInstance().AddDataPackFromFile( + base::File(pak_fd), ui::SCALE_FACTOR_100P); + return; + } +#endif // defined(OS_ANDROID) + resource_delegate_.reset(new CastResourceDelegate()); // TODO(gunsch): Use LOAD_COMMON_RESOURCES once ResourceBundle no longer // hardcodes resource file names. diff --git a/chromecast/shell/app/cast_main_delegate.h b/chromecast/shell/app/cast_main_delegate.h index 8c9ed620dd..424095302c 100644 --- a/chromecast/shell/app/cast_main_delegate.h +++ b/chromecast/shell/app/cast_main_delegate.h @@ -10,6 +10,10 @@ #include "chromecast/shell/common/cast_content_client.h" #include "content/public/app/content_main_delegate.h" +namespace content { +class BrowserMainRunner; +} // namespace content + namespace chromecast { class CastResourceDelegate; @@ -27,7 +31,12 @@ class CastMainDelegate : public content::ContentMainDelegate { // content::ContentMainDelegate implementation: virtual bool BasicStartupComplete(int* exit_code) OVERRIDE; virtual void PreSandboxStartup() OVERRIDE; + virtual int RunProcess( + const std::string& process_type, + const content::MainFunctionParams& main_function_params) OVERRIDE; +#if !defined(OS_ANDROID) virtual void ZygoteForked() OVERRIDE; +#endif // !defined(OS_ANDROID) virtual content::ContentBrowserClient* CreateContentBrowserClient() OVERRIDE; virtual content::ContentRendererClient* CreateContentRendererClient() OVERRIDE; @@ -40,6 +49,10 @@ class CastMainDelegate : public content::ContentMainDelegate { scoped_ptr<CastResourceDelegate> resource_delegate_; CastContentClient content_client_; +#if defined(OS_ANDROID) + scoped_ptr<content::BrowserMainRunner> browser_runner_; +#endif // defined(OS_ANDROID) + DISALLOW_COPY_AND_ASSIGN(CastMainDelegate); }; diff --git a/chromecast/shell/browser/android/cast_window_android.cc b/chromecast/shell/browser/android/cast_window_android.cc new file mode 100644 index 0000000000..4b68cb95d3 --- /dev/null +++ b/chromecast/shell/browser/android/cast_window_android.cc @@ -0,0 +1,173 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chromecast/shell/browser/android/cast_window_android.h" + +#include "base/message_loop/message_loop_proxy.h" +#include "base/path_service.h" +#include "chromecast/shell/browser/android/cast_window_manager.h" +#include "content/public/browser/devtools_agent_host.h" +#include "content/public/browser/navigation_controller.h" +#include "content/public/browser/navigation_entry.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/common/renderer_preferences.h" +#include "jni/CastWindowAndroid_jni.h" + +namespace chromecast { +namespace shell { + +namespace { + +// The time (in milliseconds) we wait for after a page is closed (i.e. +// after an app is stopped) before we delete the corresponding WebContents. +const int kWebContentsDestructionDelayInMs = 50; + +} // namespace + +// static +bool CastWindowAndroid::RegisterJni(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +CastWindowAndroid::CastWindowAndroid(content::WebContents* web_contents) + : content::WebContentsObserver(web_contents), + weak_factory_(this) { +} + +CastWindowAndroid::~CastWindowAndroid() { +} + +// static +CastWindowAndroid* CastWindowAndroid::CreateNewWindow( + content::BrowserContext* browser_context, + const GURL& url) { + content::WebContents::CreateParams create_params(browser_context); + create_params.routing_id = MSG_ROUTING_NONE; + content::WebContents* web_contents = + content::WebContents::Create(create_params); + CastWindowAndroid* shell = CreateCastWindowAndroid( + web_contents, + create_params.initial_size); + if (!url.is_empty()) + shell->LoadURL(url); + return shell; +} + +// static +CastWindowAndroid* CastWindowAndroid::CreateCastWindowAndroid( + content::WebContents* web_contents, + const gfx::Size& initial_size) { + CastWindowAndroid* shell = new CastWindowAndroid(web_contents); + + JNIEnv* env = base::android::AttachCurrentThread(); + base::android::ScopedJavaLocalRef<jobject> shell_android( + CreateCastWindowView(shell)); + + shell->java_object_.Reset(env, shell_android.Release()); + shell->web_contents_.reset(web_contents); + web_contents->SetDelegate(shell); + + Java_CastWindowAndroid_initFromNativeWebContents( + env, shell->java_object_.obj(), reinterpret_cast<jint>(web_contents)); + + // Enabling hole-punching also requires runtime renderer preference + web_contents->GetMutableRendererPrefs()-> + use_video_overlay_for_embedded_encrypted_video = true; + web_contents->GetRenderViewHost()->SyncRendererPrefs(); + + return shell; +} + +void CastWindowAndroid::Close() { + // Close page first, which fires the window.unload event. The WebContents + // itself will be destroyed after browser-process has received renderer + // notification that the page is closed. + web_contents_->GetRenderViewHost()->ClosePage(); +} + +void CastWindowAndroid::Destroy() { + // Note: if multiple windows becomes supported, this may close other devtools + // sessions. + content::DevToolsAgentHost::DetachAllClients(); + CloseCastWindowView(java_object_.obj()); + delete this; +} + +void CastWindowAndroid::LoadURL(const GURL& url) { + content::NavigationController::LoadURLParams params(url); + params.transition_type = ui::PageTransitionFromInt( + ui::PAGE_TRANSITION_TYPED | + ui::PAGE_TRANSITION_FROM_ADDRESS_BAR); + web_contents_->GetController().LoadURLWithParams(params); + web_contents_->Focus(); +} + +void CastWindowAndroid::AddNewContents(content::WebContents* source, + content::WebContents* new_contents, + WindowOpenDisposition disposition, + const gfx::Rect& initial_pos, + bool user_gesture, + bool* was_blocked) { + NOTIMPLEMENTED(); + if (was_blocked) { + *was_blocked = true; + } +} + +void CastWindowAndroid::CloseContents(content::WebContents* source) { + DCHECK_EQ(source, web_contents_.get()); + + // We need to delay the deletion of web_contents_ (currently for 50ms) to + // give (and guarantee) the renderer enough time to finish 'onunload' + // handler (but we don't want to wait any longer than that to delay the + // starting of next app). + + if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kTestType)) { + // When shutting down in a test context, the last remaining WebContents + // is torn down at browser-thread shutdown time. Call Destroy directly to + // avoid losing the last posted task to delete this object. + // TODO(gunsch): This could probably be avoided by using a + // CompletionCallback in StopCurrentApp to wait until the app is completely + // stopped. This might require a separate message loop and might only be + // appropriate for test contexts or during shutdown, since it triggers a + // wait on the main thread. + Destroy(); + return; + } + + base::MessageLoopProxy::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&CastWindowAndroid::Destroy, weak_factory_.GetWeakPtr()), + base::TimeDelta::FromMilliseconds(kWebContentsDestructionDelayInMs)); +} + +bool CastWindowAndroid::CanOverscrollContent() const { + return false; +} + +bool CastWindowAndroid::AddMessageToConsole(content::WebContents* source, + int32 level, + const base::string16& message, + int32 line_no, + const base::string16& source_id) { + return false; +} + +void CastWindowAndroid::ActivateContents(content::WebContents* contents) { + DCHECK_EQ(contents, web_contents_.get()); + contents->GetRenderViewHost()->Focus(); +} + +void CastWindowAndroid::DeactivateContents(content::WebContents* contents) { + DCHECK_EQ(contents, web_contents_.get()); + contents->GetRenderViewHost()->Blur(); +} + +void CastWindowAndroid::RenderProcessGone(base::TerminationStatus status) { + LOG(ERROR) << "Render process gone: status=" << status; + Destroy(); +} + +} // namespace shell +} // namespace chromecast diff --git a/chromecast/shell/browser/android/cast_window_android.h b/chromecast/shell/browser/android/cast_window_android.h new file mode 100644 index 0000000000..bcb5c1f7ef --- /dev/null +++ b/chromecast/shell/browser/android/cast_window_android.h @@ -0,0 +1,97 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROMECAST_SHELL_BROWSER_ANDROID_CAST_WINDOW_ANDROID_H_ +#define CHROMECAST_SHELL_BROWSER_ANDROID_CAST_WINDOW_ANDROID_H_ + +#include <jni.h> +#include <vector> + +#include "base/android/jni_string.h" +#include "base/android/scoped_java_ref.h" +#include "base/basictypes.h" +#include "base/callback_forward.h" +#include "base/command_line.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "build/build_config.h" +#include "content/public/browser/web_contents.h" +#include "content/public/browser/web_contents_delegate.h" +#include "content/public/browser/web_contents_observer.h" +#include "content/public/common/content_switches.h" +#include "ui/gfx/native_widget_types.h" +#include "ui/gfx/size.h" + +class GURL; + +namespace content { +class BrowserContext; +class SiteInstance; +class WebContents; +} // namespace content + +namespace chromecast { +namespace shell { + +class CastWindowAndroid : public content::WebContentsDelegate, + public content::WebContentsObserver { + public: + // Creates a new window and immediately loads the given URL. + static CastWindowAndroid* CreateNewWindow( + content::BrowserContext* browser_context, + const GURL& url); + + virtual ~CastWindowAndroid(); + + void LoadURL(const GURL& url); + // Calls RVH::ClosePage() and waits for acknowledgement before closing/ + // deleting the window. + void Close(); + // Destroys this window immediately. + void Destroy(); + + // Registers the JNI methods for CastWindowAndroid. + static bool RegisterJni(JNIEnv* env); + + // content::WebContentsDelegate implementation: + virtual void AddNewContents(content::WebContents* source, + content::WebContents* new_contents, + WindowOpenDisposition disposition, + const gfx::Rect& initial_pos, + bool user_gesture, + bool* was_blocked) OVERRIDE; + virtual void CloseContents(content::WebContents* source) OVERRIDE; + virtual bool CanOverscrollContent() const OVERRIDE; + virtual bool AddMessageToConsole(content::WebContents* source, + int32 level, + const base::string16& message, + int32 line_no, + const base::string16& source_id) OVERRIDE; + virtual void ActivateContents(content::WebContents* contents) OVERRIDE; + virtual void DeactivateContents(content::WebContents* contents) OVERRIDE; + + // content::WebContentsObserver implementation: + virtual void RenderProcessGone(base::TerminationStatus status) OVERRIDE; + + private: + explicit CastWindowAndroid(content::WebContents* web_contents); + + // Helper to create a new CastWindowAndroid given a newly created WebContents. + static CastWindowAndroid* CreateCastWindowAndroid( + content::WebContents* web_contents, + const gfx::Size& initial_size); + + base::android::ScopedJavaGlobalRef<jobject> java_object_; + scoped_ptr<content::WebContents> web_contents_; + + base::WeakPtrFactory<CastWindowAndroid> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(CastWindowAndroid); +}; + +} // namespace shell +} // namespace chromecast + +#endif // CHROMECAST_SHELL_BROWSER_ANDROID_CAST_WINDOW_ANDROID_H_ diff --git a/chromecast/shell/browser/android/cast_window_manager.cc b/chromecast/shell/browser/android/cast_window_manager.cc new file mode 100644 index 0000000000..481eb4bbff --- /dev/null +++ b/chromecast/shell/browser/android/cast_window_manager.cc @@ -0,0 +1,88 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chromecast/shell/browser/android/cast_window_manager.h" + +#include <jni.h> + +#include "base/android/jni_android.h" +#include "base/android/jni_string.h" +#include "base/android/scoped_java_ref.h" +#include "base/bind.h" +#include "base/lazy_instance.h" +#include "chromecast/common/chromecast_config.h" +#include "chromecast/common/pref_names.h" +#include "chromecast/shell/browser/android/cast_window_android.h" +#include "chromecast/shell/browser/cast_browser_context.h" +#include "chromecast/shell/browser/cast_browser_main_parts.h" +#include "chromecast/shell/browser/cast_browser_process.h" +#include "chromecast/shell/browser/cast_content_browser_client.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/web_contents.h" +#include "ipc/ipc_channel.h" +#include "jni/CastWindowManager_jni.h" +#include "url/gurl.h" + +namespace { + +base::LazyInstance<base::android::ScopedJavaGlobalRef<jobject> > + g_window_manager = LAZY_INSTANCE_INITIALIZER; + +} // namespace + +namespace chromecast { +namespace shell { + +base::android::ScopedJavaLocalRef<jobject> +CreateCastWindowView(CastWindowAndroid* shell) { + JNIEnv* env = base::android::AttachCurrentThread(); + jobject j_window_manager = g_window_manager.Get().obj(); + return Java_CastWindowManager_createCastWindow(env, j_window_manager); +} + +void CloseCastWindowView(jobject shell_wrapper) { + JNIEnv* env = base::android::AttachCurrentThread(); + jobject j_window_manager = g_window_manager.Get().obj(); + Java_CastWindowManager_closeCastWindow(env, j_window_manager, shell_wrapper); +} + +// Register native methods +bool RegisterCastWindowManager(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +void Init(JNIEnv* env, jclass clazz, jobject obj) { + g_window_manager.Get().Reset( + base::android::ScopedJavaLocalRef<jobject>(env, obj)); +} + +jlong LaunchCastWindow(JNIEnv* env, jclass clazz, jstring jurl) { + GURL url(base::android::ConvertJavaStringToUTF8(env, jurl)); + return reinterpret_cast<jlong>( + CastWindowAndroid::CreateNewWindow( + CastBrowserProcess::GetInstance()->browser_context(), + url)); +} + +void StopCastWindow(JNIEnv* env, jclass clazz, + jlong nativeCastWindow, jboolean gracefully) { + CastWindowAndroid* window = + reinterpret_cast<CastWindowAndroid*>(nativeCastWindow); + DCHECK(window); + if (gracefully) + window->Close(); + else + window->Destroy(); +} + +void EnableDevTools(JNIEnv* env, jclass clazz, jboolean enable) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + // The specific port value doesn't matter since Android uses Unix domain + // sockets, only whether or not it is zero. + chromecast::ChromecastConfig::GetInstance()->pref_service()-> + SetInteger(prefs::kRemoteDebuggingPort, enable ? 1 : 0); +} + +} // namespace shell +} // namespace chromecast diff --git a/chromecast/shell/browser/android/cast_window_manager.h b/chromecast/shell/browser/android/cast_window_manager.h new file mode 100644 index 0000000000..a7c2e0a918 --- /dev/null +++ b/chromecast/shell/browser/android/cast_window_manager.h @@ -0,0 +1,35 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROMECAST_SHELL_BROWSER_ANDROID_CAST_WINDOW_MANAGER_H_ +#define CHROMECAST_SHELL_BROWSER_ANDROID_CAST_WINDOW_MANAGER_H_ + +#include <jni.h> + +#include "base/android/jni_android.h" +#include "base/android/scoped_java_ref.h" + +class CastWindowAndroid; + +namespace content { +class BrowserContext; +} + +namespace chromecast { +namespace shell { + +// Given a CastWindowAndroid instance, creates and returns a Java wrapper. +base::android::ScopedJavaLocalRef<jobject> +CreateCastWindowView(CastWindowAndroid* shell); + +// Closes a previously created Java wrapper. +void CloseCastWindowView(jobject shell_wrapper); + +// Registers the CastWindowManager native methods. +bool RegisterCastWindowManager(JNIEnv* env); + +} // namespace shell +} // namespace chromecast + +#endif // CHROMECAST_SHELL_BROWSER_ANDROID_CAST_WINDOW_MANAGER_H_ diff --git a/chromecast/shell/browser/cast_browser_context.cc b/chromecast/shell/browser/cast_browser_context.cc index 295c34b8d4..b0280f67a3 100644 --- a/chromecast/shell/browser/cast_browser_context.cc +++ b/chromecast/shell/browser/cast_browser_context.cc @@ -5,9 +5,11 @@ #include "chromecast/shell/browser/cast_browser_context.h" #include "base/command_line.h" +#include "base/files/file_util.h" #include "base/macros.h" #include "base/path_service.h" #include "chromecast/common/cast_paths.h" +#include "chromecast/shell/browser/cast_download_manager_delegate.h" #include "chromecast/shell/browser/url_request_context_factory.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/resource_context.h" @@ -22,7 +24,8 @@ namespace shell { class CastBrowserContext::CastResourceContext : public content::ResourceContext { public: - CastResourceContext(URLRequestContextFactory* url_request_context_factory) : + explicit CastResourceContext( + URLRequestContextFactory* url_request_context_factory) : url_request_context_factory_(url_request_context_factory) {} virtual ~CastResourceContext() {} @@ -37,14 +40,6 @@ class CastBrowserContext::CastResourceContext : GetURLRequestContext(); } - virtual bool AllowMicAccess(const GURL& origin) OVERRIDE { - return false; - } - - virtual bool AllowCameraAccess(const GURL& origin) OVERRIDE { - return false; - } - private: URLRequestContextFactory* url_request_context_factory_; @@ -54,19 +49,32 @@ class CastBrowserContext::CastResourceContext : CastBrowserContext::CastBrowserContext( URLRequestContextFactory* url_request_context_factory) : url_request_context_factory_(url_request_context_factory), - resource_context_(new CastResourceContext(url_request_context_factory)) { + resource_context_(new CastResourceContext(url_request_context_factory)), + download_manager_delegate_(new CastDownloadManagerDelegate()) { InitWhileIOAllowed(); } CastBrowserContext::~CastBrowserContext() { + content::BrowserThread::DeleteSoon( + content::BrowserThread::IO, + FROM_HERE, + resource_context_.release()); } void CastBrowserContext::InitWhileIOAllowed() { +#if defined(OS_ANDROID) + CHECK(PathService::Get(base::DIR_ANDROID_APP_DATA, &path_)); + path_ = path_.Append(FILE_PATH_LITERAL("cast_shell")); + + if (!base::PathExists(path_)) + base::CreateDirectory(path_); +#else // Chromecast doesn't support user profiles nor does it have // incognito mode. This means that all of the persistent // data (currently only cookies and local storage) will be // shared in a single location as defined here. CHECK(PathService::Get(DIR_CAST_HOME, &path_)); +#endif // defined(OS_ANDROID) } base::FilePath CastBrowserContext::GetPath() const { @@ -108,8 +116,7 @@ content::ResourceContext* CastBrowserContext::GetResourceContext() { content::DownloadManagerDelegate* CastBrowserContext::GetDownloadManagerDelegate() { - NOTIMPLEMENTED(); - return NULL; + return download_manager_delegate_.get(); } content::BrowserPluginGuestManager* CastBrowserContext::GetGuestManager() { diff --git a/chromecast/shell/browser/cast_browser_context.h b/chromecast/shell/browser/cast_browser_context.h index 1f124a5d48..527e7e2eac 100644 --- a/chromecast/shell/browser/cast_browser_context.h +++ b/chromecast/shell/browser/cast_browser_context.h @@ -13,6 +13,7 @@ namespace chromecast { namespace shell { +class CastDownloadManagerDelegate; class URLRequestContextFactory; // Chromecast does not currently support multiple profiles. So there is a @@ -55,6 +56,7 @@ class CastBrowserContext : public content::BrowserContext { URLRequestContextFactory* const url_request_context_factory_; base::FilePath path_; scoped_ptr<CastResourceContext> resource_context_; + scoped_ptr<CastDownloadManagerDelegate> download_manager_delegate_; DISALLOW_COPY_AND_ASSIGN(CastBrowserContext); }; diff --git a/chromecast/shell/browser/cast_browser_main_parts.cc b/chromecast/shell/browser/cast_browser_main_parts.cc index 72c75440b4..0fe3a41290 100644 --- a/chromecast/shell/browser/cast_browser_main_parts.cc +++ b/chromecast/shell/browser/cast_browser_main_parts.cc @@ -5,17 +5,25 @@ #include "chromecast/shell/browser/cast_browser_main_parts.h" #include "base/command_line.h" +#include "base/message_loop/message_loop.h" #include "base/prefs/pref_registry_simple.h" #include "chromecast/common/chromecast_config.h" +#include "chromecast/metrics/cast_metrics_service_client.h" #include "chromecast/net/network_change_notifier_cast.h" #include "chromecast/net/network_change_notifier_factory_cast.h" #include "chromecast/service/cast_service.h" #include "chromecast/shell/browser/cast_browser_context.h" +#include "chromecast/shell/browser/cast_browser_process.h" #include "chromecast/shell/browser/devtools/remote_debugging_server.h" #include "chromecast/shell/browser/url_request_context_factory.h" #include "chromecast/shell/browser/webui/webui_cast.h" +#include "content/public/browser/browser_thread.h" #include "content/public/common/content_switches.h" +#if defined(OS_ANDROID) +#include "net/android/network_change_notifier_factory_android.h" +#endif // defined(OS_ANDROID) + namespace chromecast { namespace shell { @@ -29,6 +37,8 @@ struct DefaultCommandLineSwitch { DefaultCommandLineSwitch g_default_switches[] = { { switches::kDisableApplicationCache, "" }, { switches::kDisablePlugins, "" }, + // Always enable HTMLMediaElement logs. + { switches::kBlinkPlatformLogChannels, "Media"}, { NULL, NULL }, // Termination }; @@ -48,6 +58,8 @@ CastBrowserMainParts::CastBrowserMainParts( const content::MainFunctionParams& parameters, URLRequestContextFactory* url_request_context_factory) : BrowserMainParts(), + cast_browser_process_(new CastBrowserProcess()), + parameters_(parameters), url_request_context_factory_(url_request_context_factory) { CommandLine* command_line = CommandLine::ForCurrentProcess(); AddDefaultCommandLineSwitches(command_line); @@ -57,12 +69,19 @@ CastBrowserMainParts::~CastBrowserMainParts() { } void CastBrowserMainParts::PreMainMessageLoopStart() { +#if defined(OS_ANDROID) + net::NetworkChangeNotifier::SetFactory( + new net::NetworkChangeNotifierFactoryAndroid()); +#else net::NetworkChangeNotifier::SetFactory( new NetworkChangeNotifierFactoryCast()); +#endif // defined(OS_ANDROID) } void CastBrowserMainParts::PostMainMessageLoopStart() { - NOTIMPLEMENTED(); +#if defined(OS_ANDROID) + base::MessageLoopForUI::current()->Start(); +#endif // defined(OS_ANDROID) } int CastBrowserMainParts::PreCreateThreads() { @@ -73,26 +92,37 @@ int CastBrowserMainParts::PreCreateThreads() { void CastBrowserMainParts::PreMainMessageLoopRun() { url_request_context_factory_->InitializeOnUIThread(); - browser_context_.reset(new CastBrowserContext(url_request_context_factory_)); - dev_tools_.reset(new RemoteDebuggingServer()); + cast_browser_process_->SetBrowserContext( + new CastBrowserContext(url_request_context_factory_)); + cast_browser_process_->SetMetricsServiceClient( + metrics::CastMetricsServiceClient::Create( + content::BrowserThread::GetBlockingPool(), + ChromecastConfig::GetInstance()->pref_service(), + cast_browser_process_->browser_context()->GetRequestContext())); + cast_browser_process_->SetRemoteDebuggingServer(new RemoteDebuggingServer()); InitializeWebUI(); - cast_service_.reset(CastService::Create(browser_context_.get())); - cast_service_->Start(); + cast_browser_process_->SetCastService( + CastService::Create(cast_browser_process_->browser_context(), + url_request_context_factory_->GetSystemGetter())); + cast_browser_process_->cast_service()->Start(); } bool CastBrowserMainParts::MainMessageLoopRun(int* result_code) { - base::MessageLoopForUI::current()->Run(); + // If parameters_.ui_task is not NULL, we are running browser tests. In this + // case, the browser's main message loop will not run. + if (parameters_.ui_task) { + parameters_.ui_task->Run(); + } else { + base::MessageLoopForUI::current()->Run(); + } return true; } void CastBrowserMainParts::PostMainMessageLoopRun() { - cast_service_->Stop(); - - cast_service_.reset(); - dev_tools_.reset(); - browser_context_.reset(); + cast_browser_process_->cast_service()->Stop(); + cast_browser_process_.reset(); } } // namespace shell diff --git a/chromecast/shell/browser/cast_browser_main_parts.h b/chromecast/shell/browser/cast_browser_main_parts.h index 988ceea92f..f1d695a066 100644 --- a/chromecast/shell/browser/cast_browser_main_parts.h +++ b/chromecast/shell/browser/cast_browser_main_parts.h @@ -9,19 +9,11 @@ #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "content/public/browser/browser_main_parts.h" - -namespace content { -struct MainFunctionParams; -} +#include "content/public/common/main_function_params.h" namespace chromecast { - -class CastService; - namespace shell { - -class CastBrowserContext; -class RemoteDebuggingServer; +class CastBrowserProcess; class URLRequestContextFactory; class CastBrowserMainParts : public content::BrowserMainParts { @@ -39,14 +31,9 @@ class CastBrowserMainParts : public content::BrowserMainParts { virtual bool MainMessageLoopRun(int* result_code) OVERRIDE; virtual void PostMainMessageLoopRun() OVERRIDE; - CastBrowserContext* browser_context() { - return browser_context_.get(); - } - private: - scoped_ptr<CastBrowserContext> browser_context_; - scoped_ptr<CastService> cast_service_; - scoped_ptr<RemoteDebuggingServer> dev_tools_; + scoped_ptr<CastBrowserProcess> cast_browser_process_; + const content::MainFunctionParams parameters_; // For running browser tests. URLRequestContextFactory* const url_request_context_factory_; DISALLOW_COPY_AND_ASSIGN(CastBrowserMainParts); diff --git a/chromecast/shell/browser/cast_browser_process.cc b/chromecast/shell/browser/cast_browser_process.cc new file mode 100644 index 0000000000..70802aac0b --- /dev/null +++ b/chromecast/shell/browser/cast_browser_process.cc @@ -0,0 +1,60 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chromecast/shell/browser/cast_browser_process.h" + +#include "base/logging.h" +#include "chromecast/metrics/cast_metrics_service_client.h" +#include "chromecast/service/cast_service.h" +#include "chromecast/shell/browser/cast_browser_context.h" +#include "chromecast/shell/browser/devtools/remote_debugging_server.h" + +namespace chromecast { +namespace shell { + +namespace { +CastBrowserProcess* g_instance = NULL; +} // namespace + +// static +CastBrowserProcess* CastBrowserProcess::GetInstance() { + DCHECK(g_instance); + return g_instance; +} + +CastBrowserProcess::CastBrowserProcess() { + DCHECK(!g_instance); + g_instance = this; +} + +CastBrowserProcess::~CastBrowserProcess() { + DCHECK_EQ(g_instance, this); + g_instance = NULL; +} + +void CastBrowserProcess::SetBrowserContext( + CastBrowserContext* browser_context) { + DCHECK(!browser_context_); + browser_context_.reset(browser_context); +} + +void CastBrowserProcess::SetCastService(CastService* cast_service) { + DCHECK(!cast_service_); + cast_service_.reset(cast_service); +} + +void CastBrowserProcess::SetRemoteDebuggingServer( + RemoteDebuggingServer* remote_debugging_server) { + DCHECK(!remote_debugging_server_); + remote_debugging_server_.reset(remote_debugging_server); +} + +void CastBrowserProcess::SetMetricsServiceClient( + metrics::CastMetricsServiceClient* metrics_service_client) { + DCHECK(!metrics_service_client_); + metrics_service_client_.reset(metrics_service_client); +} + +} // namespace shell +} // namespace chromecast diff --git a/chromecast/shell/browser/cast_browser_process.h b/chromecast/shell/browser/cast_browser_process.h new file mode 100644 index 0000000000..65830c2a3a --- /dev/null +++ b/chromecast/shell/browser/cast_browser_process.h @@ -0,0 +1,63 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROMECAST_SHELL_BROWSER_CAST_BROWSER_PROCESS_H_ +#define CHROMECAST_SHELL_BROWSER_CAST_BROWSER_PROCESS_H_ + +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" + +namespace breakpad { +class CrashDumpManager; +} // namespace breakpad + +namespace chromecast { +class CastService; +class WebCryptoServer; + +namespace metrics { +class CastMetricsHelper; +class CastMetricsServiceClient; +} // namespace metrics + +namespace shell { +class CastBrowserContext; +class RemoteDebuggingServer; + +class CastBrowserProcess { + public: + // Gets the global instance of CastBrowserProcess. Does not create lazily and + // assumes the instance already exists. + static CastBrowserProcess* GetInstance(); + + CastBrowserProcess(); + virtual ~CastBrowserProcess(); + + void SetBrowserContext(CastBrowserContext* browser_context); + void SetCastService(CastService* cast_service); + void SetRemoteDebuggingServer(RemoteDebuggingServer* remote_debugging_server); + void SetMetricsServiceClient( + metrics::CastMetricsServiceClient* metrics_service_client); + + CastBrowserContext* browser_context() const { return browser_context_.get(); } + CastService* cast_service() const { return cast_service_.get(); } + metrics::CastMetricsServiceClient* metrics_service_client() const { + return metrics_service_client_.get(); + } + + private: + scoped_ptr<CastBrowserContext> browser_context_; + scoped_ptr<metrics::CastMetricsServiceClient> metrics_service_client_; + scoped_ptr<RemoteDebuggingServer> remote_debugging_server_; + + // Note: CastService must be destroyed before others. + scoped_ptr<CastService> cast_service_; + + DISALLOW_COPY_AND_ASSIGN(CastBrowserProcess); +}; + +} // namespace shell +} // namespace chromecast + +#endif // CHROMECAST_SHELL_BROWSER_CAST_BROWSER_PROCESS_H_ diff --git a/chromecast/shell/browser/cast_content_browser_client.cc b/chromecast/shell/browser/cast_content_browser_client.cc index 720bbb82de..b2e6db72af 100644 --- a/chromecast/shell/browser/cast_content_browser_client.cc +++ b/chromecast/shell/browser/cast_content_browser_client.cc @@ -5,11 +5,19 @@ #include "chromecast/shell/browser/cast_content_browser_client.h" #include "base/command_line.h" +#include "base/i18n/rtl.h" +#include "base/path_service.h" +#include "chromecast/common/cast_paths.h" +#include "chromecast/common/global_descriptors.h" #include "chromecast/shell/browser/cast_browser_context.h" #include "chromecast/shell/browser/cast_browser_main_parts.h" +#include "chromecast/shell/browser/cast_browser_process.h" +#include "chromecast/shell/browser/devtools/cast_dev_tools_delegate.h" #include "chromecast/shell/browser/geolocation/cast_access_token_store.h" #include "chromecast/shell/browser/url_request_context_factory.h" +#include "content/public/browser/browser_thread.h" #include "content/public/browser/certificate_request_result_type.h" +#include "content/public/browser/file_descriptor_info.h" #include "content/public/browser/render_process_host.h" #include "content/public/common/content_descriptors.h" #include "content/public/common/content_switches.h" @@ -24,13 +32,16 @@ CastContentBrowserClient::CastContentBrowserClient() } CastContentBrowserClient::~CastContentBrowserClient() { + content::BrowserThread::DeleteSoon( + content::BrowserThread::IO, + FROM_HERE, + url_request_context_factory_.release()); } content::BrowserMainParts* CastContentBrowserClient::CreateBrowserMainParts( const content::MainFunctionParams& parameters) { - shell_browser_main_parts_ = - new CastBrowserMainParts(parameters, url_request_context_factory_.get()); - return shell_browser_main_parts_; + return new CastBrowserMainParts(parameters, + url_request_context_factory_.get()); } void CastContentBrowserClient::RenderProcessWillLaunch( @@ -57,6 +68,9 @@ bool CastContentBrowserClient::IsHandledURL(const GURL& url) { content::kChromeUIScheme, content::kChromeDevToolsScheme, url::kDataScheme, +#if defined(OS_ANDROID) + url::kFileScheme, +#endif // defined(OS_ANDROID) }; const std::string& scheme = url.scheme(); @@ -81,7 +95,8 @@ void CastContentBrowserClient::AppendExtraCommandLineSwitches( } content::AccessTokenStore* CastContentBrowserClient::CreateAccessTokenStore() { - return new CastAccessTokenStore(shell_browser_main_parts_->browser_context()); + return new CastAccessTokenStore( + CastBrowserProcess::GetInstance()->browser_context()); } void CastContentBrowserClient::OverrideWebkitPrefs( @@ -97,7 +112,8 @@ void CastContentBrowserClient::OverrideWebkitPrefs( } std::string CastContentBrowserClient::GetApplicationLocale() { - return "en-US"; + const std::string locale(base::i18n::GetConfiguredLocale()); + return locale.empty() ? "en-US" : locale; } void CastContentBrowserClient::AllowCertificateError( @@ -137,10 +153,28 @@ bool CastContentBrowserClient::CanCreateWindow( return false; } +content::DevToolsManagerDelegate* +CastContentBrowserClient::GetDevToolsManagerDelegate() { + return new CastDevToolsManagerDelegate(); +} + void CastContentBrowserClient::GetAdditionalMappedFilesForChildProcess( const base::CommandLine& command_line, int child_process_id, std::vector<content::FileDescriptorInfo>* mappings) { +#if defined(OS_ANDROID) + int flags = base::File::FLAG_OPEN | base::File::FLAG_READ; + base::FilePath pak_file; + CHECK(PathService::Get(FILE_CAST_PAK, &pak_file)); + base::File pak_with_flags(pak_file, flags); + if (!pak_with_flags.IsValid()) { + NOTREACHED() << "Failed to open file when creating renderer process: " + << "cast_shell.pak"; + } + mappings->push_back(content::FileDescriptorInfo( + kAndroidPakDescriptor, + base::FileDescriptor(base::File(pak_file, flags)))); +#endif // defined(OS_ANDROID) } } // namespace shell diff --git a/chromecast/shell/browser/cast_content_browser_client.h b/chromecast/shell/browser/cast_content_browser_client.h index 98504e3581..2bd8ef4d20 100644 --- a/chromecast/shell/browser/cast_content_browser_client.h +++ b/chromecast/shell/browser/cast_content_browser_client.h @@ -65,15 +65,14 @@ class CastContentBrowserClient: public content::ContentBrowserClient { int render_process_id, int opener_id, bool* no_javascript_access) OVERRIDE; + virtual content::DevToolsManagerDelegate* + GetDevToolsManagerDelegate() OVERRIDE; virtual void GetAdditionalMappedFilesForChildProcess( const base::CommandLine& command_line, int child_process_id, std::vector<content::FileDescriptorInfo>* mappings) OVERRIDE; private: - // Note: BrowserMainLoop holds ownership of CastBrowserMainParts after it is - // created. - CastBrowserMainParts* shell_browser_main_parts_; scoped_ptr<URLRequestContextFactory> url_request_context_factory_; DISALLOW_COPY_AND_ASSIGN(CastContentBrowserClient); diff --git a/chromecast/shell/browser/cast_download_manager_delegate.cc b/chromecast/shell/browser/cast_download_manager_delegate.cc new file mode 100644 index 0000000000..edc35f0da5 --- /dev/null +++ b/chromecast/shell/browser/cast_download_manager_delegate.cc @@ -0,0 +1,58 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chromecast/shell/browser/cast_download_manager_delegate.h" + +#include "base/files/file_path.h" +#include "base/logging.h" +#include "content/public/browser/download_danger_type.h" +#include "content/public/browser/download_item.h" + +namespace chromecast { +namespace shell { + +CastDownloadManagerDelegate::CastDownloadManagerDelegate() {} + +CastDownloadManagerDelegate::~CastDownloadManagerDelegate() {} + +void CastDownloadManagerDelegate::GetNextId( + const content::DownloadIdCallback& callback) { + // See default behavior of DownloadManagerImpl::GetNextId() + static uint32 next_id = content::DownloadItem::kInvalidId + 1; + callback.Run(next_id++); +} + +bool CastDownloadManagerDelegate::DetermineDownloadTarget( + content::DownloadItem* item, + const content::DownloadTargetCallback& callback) { + // Running the DownloadTargetCallback with an empty FilePath signals + // that the download should be cancelled. + base::FilePath empty; + callback.Run( + empty, + content::DownloadItem::TARGET_DISPOSITION_OVERWRITE, + content::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT, + empty); + return true; +} + +bool CastDownloadManagerDelegate::ShouldOpenFileBasedOnExtension( + const base::FilePath& path) { + return false; +} + +bool CastDownloadManagerDelegate::ShouldCompleteDownload( + content::DownloadItem* item, + const base::Closure& callback) { + return false; +} + +bool CastDownloadManagerDelegate::ShouldOpenDownload( + content::DownloadItem* item, + const content::DownloadOpenDelayedCallback& callback) { + return false; +} + +} // namespace shell +} // namespace chromecast
\ No newline at end of file diff --git a/chromecast/shell/browser/cast_download_manager_delegate.h b/chromecast/shell/browser/cast_download_manager_delegate.h new file mode 100644 index 0000000000..95ec81a9d5 --- /dev/null +++ b/chromecast/shell/browser/cast_download_manager_delegate.h @@ -0,0 +1,41 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROMECAST_SHELL_BROWSER_CAST_DOWNLOAD_MANAGER_DELEGATE_H_ +#define CHROMECAST_SHELL_BROWSER_CAST_DOWNLOAD_MANAGER_DELEGATE_H_ + +#include "base/macros.h" +#include "content/public/browser/download_manager_delegate.h" + +namespace chromecast { +namespace shell { + +class CastDownloadManagerDelegate : public content::DownloadManagerDelegate { + public: + CastDownloadManagerDelegate(); + virtual ~CastDownloadManagerDelegate(); + + // content::DownloadManagerDelegate implementation: + virtual void GetNextId( + const content::DownloadIdCallback& callback) OVERRIDE; + virtual bool DetermineDownloadTarget( + content::DownloadItem* item, + const content::DownloadTargetCallback& callback) OVERRIDE; + virtual bool ShouldOpenFileBasedOnExtension( + const base::FilePath& path) OVERRIDE; + virtual bool ShouldCompleteDownload( + content::DownloadItem* item, + const base::Closure& complete_callback) OVERRIDE; + virtual bool ShouldOpenDownload( + content::DownloadItem* item, + const content::DownloadOpenDelayedCallback& callback) OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(CastDownloadManagerDelegate); +}; + +} // namespace shell +} // namespace chromecast + +#endif // CHROMECAST_SHELL_BROWSER_CAST_DOWNLOAD_MANAGER_DELEGATE_H_
\ No newline at end of file diff --git a/chromecast/shell/browser/cast_http_user_agent_settings.cc b/chromecast/shell/browser/cast_http_user_agent_settings.cc index fbc43b3916..e4a1537d77 100644 --- a/chromecast/shell/browser/cast_http_user_agent_settings.cc +++ b/chromecast/shell/browser/cast_http_user_agent_settings.cc @@ -11,6 +11,10 @@ #include "net/http/http_util.h" #include "ui/base/l10n/l10n_util.h" +#if defined(OS_ANDROID) +#include "ui/base/l10n/l10n_util_android.h" +#endif // defined(OS_ANDROID) + namespace chromecast { namespace shell { @@ -26,7 +30,12 @@ std::string CastHttpUserAgentSettings::GetAcceptLanguage() const { DCHECK_CURRENTLY_ON(content::BrowserThread::IO); if (accept_language_.empty()) { accept_language_ = net::HttpUtil::GenerateAcceptLanguageHeader( - l10n_util::GetStringUTF8(IDS_CHROMECAST_SETTINGS_ACCEPT_LANGUAGES)); +#if defined(OS_ANDROID) + l10n_util::GetDefaultLocale() +#else + l10n_util::GetStringUTF8(IDS_CHROMECAST_SETTINGS_ACCEPT_LANGUAGES) +#endif // defined(OS_ANDROID) + ); } return accept_language_; } diff --git a/chromecast/shell/browser/devtools/cast_dev_tools_delegate.cc b/chromecast/shell/browser/devtools/cast_dev_tools_delegate.cc index 77cfba1f3d..eaf526d194 100644 --- a/chromecast/shell/browser/devtools/cast_dev_tools_delegate.cc +++ b/chromecast/shell/browser/devtools/cast_dev_tools_delegate.cc @@ -23,17 +23,36 @@ namespace shell { namespace { const char kTargetTypePage[] = "page"; +const char kTargetTypeServiceWorker[] = "service_worker"; +const char kTargetTypeSharedWorker[] = "worker"; +const char kTargetTypeOther[] = "other"; class Target : public content::DevToolsTarget { public: - explicit Target(content::WebContents* web_contents); + explicit Target(scoped_refptr<content::DevToolsAgentHost> agent_host); - virtual std::string GetId() const OVERRIDE { return id_; } + virtual std::string GetId() const OVERRIDE { return agent_host_->GetId(); } virtual std::string GetParentId() const OVERRIDE { return std::string(); } - virtual std::string GetType() const OVERRIDE { return kTargetTypePage; } - virtual std::string GetTitle() const OVERRIDE { return title_; } + virtual std::string GetType() const OVERRIDE { + switch (agent_host_->GetType()) { + case content::DevToolsAgentHost::TYPE_WEB_CONTENTS: + return kTargetTypePage; + case content::DevToolsAgentHost::TYPE_SERVICE_WORKER: + return kTargetTypeServiceWorker; + case content::DevToolsAgentHost::TYPE_SHARED_WORKER: + return kTargetTypeSharedWorker; + default: + break; + } + return kTargetTypeOther; + } + virtual std::string GetTitle() const OVERRIDE { + return agent_host_->GetTitle(); + } virtual std::string GetDescription() const OVERRIDE { return std::string(); } - virtual GURL GetURL() const OVERRIDE { return url_; } + virtual GURL GetURL() const OVERRIDE { + return agent_host_->GetURL(); + } virtual GURL GetFaviconURL() const OVERRIDE { return favicon_url_; } virtual base::TimeTicks GetLastActivityTime() const OVERRIDE { return last_activity_time_; @@ -45,50 +64,36 @@ class Target : public content::DevToolsTarget { const OVERRIDE { return agent_host_; } - virtual bool Activate() const OVERRIDE; - virtual bool Close() const OVERRIDE; + virtual bool Activate() const OVERRIDE { + return agent_host_->Activate(); + } + virtual bool Close() const OVERRIDE { + return agent_host_->Close(); + } private: scoped_refptr<content::DevToolsAgentHost> agent_host_; - std::string id_; - std::string title_; - GURL url_; GURL favicon_url_; base::TimeTicks last_activity_time_; DISALLOW_COPY_AND_ASSIGN(Target); }; -Target::Target(content::WebContents* web_contents) { - agent_host_ = content::DevToolsAgentHost::GetOrCreateFor(web_contents); - id_ = agent_host_->GetId(); - title_ = base::UTF16ToUTF8(web_contents->GetTitle()); - url_ = web_contents->GetURL(); - content::NavigationController& controller = web_contents->GetController(); - content::NavigationEntry* entry = controller.GetActiveEntry(); - if (entry != NULL && entry->GetURL().is_valid()) - favicon_url_ = entry->GetFavicon().url; - last_activity_time_ = web_contents->GetLastActiveTime(); -} - -bool Target::Activate() const { - content::WebContents* web_contents = agent_host_->GetWebContents(); - if (!web_contents) - return false; - web_contents->GetDelegate()->ActivateContents(web_contents); - return true; -} - -bool Target::Close() const { - content::WebContents* web_contents = agent_host_->GetWebContents(); - if (!web_contents) - return false; - web_contents->GetRenderViewHost()->ClosePage(); - return true; +Target::Target(scoped_refptr<content::DevToolsAgentHost> agent_host) + : agent_host_(agent_host) { + if (content::WebContents* web_contents = agent_host_->GetWebContents()) { + content::NavigationController& controller = web_contents->GetController(); + content::NavigationEntry* entry = controller.GetActiveEntry(); + if (entry != NULL && entry->GetURL().is_valid()) + favicon_url_ = entry->GetFavicon().url; + last_activity_time_ = web_contents->GetLastActiveTime(); + } } } // namespace +// CastDevToolsDelegate ----------------------------------------------------- + CastDevToolsDelegate::CastDevToolsDelegate() { } @@ -96,55 +101,59 @@ CastDevToolsDelegate::~CastDevToolsDelegate() { } std::string CastDevToolsDelegate::GetDiscoveryPageHTML() { -#if defined(OS_ANDROID) - return std::string(); -#else return ResourceBundle::GetSharedInstance().GetRawDataResource( IDR_CAST_SHELL_DEVTOOLS_DISCOVERY_PAGE).as_string(); -#endif // defined(OS_ANDROID) } bool CastDevToolsDelegate::BundlesFrontendResources() { -#if defined(OS_ANDROID) - // Since Android remote debugging connects over a Unix domain socket, Chrome - // will not load the same homepage. - return false; -#else return true; -#endif // defined(OS_ANDROID) } base::FilePath CastDevToolsDelegate::GetDebugFrontendDir() { return base::FilePath(); } -std::string CastDevToolsDelegate::GetPageThumbnailData(const GURL& url) { - return ""; +scoped_ptr<net::StreamListenSocket> +CastDevToolsDelegate::CreateSocketForTethering( + net::StreamListenSocket::Delegate* delegate, + std::string* name) { + return scoped_ptr<net::StreamListenSocket>(); } -scoped_ptr<content::DevToolsTarget> CastDevToolsDelegate::CreateNewTarget( +// CastDevToolsManagerDelegate ----------------------------------------------- + +CastDevToolsManagerDelegate::CastDevToolsManagerDelegate() { +} + +CastDevToolsManagerDelegate::~CastDevToolsManagerDelegate() { +} + +base::DictionaryValue* CastDevToolsManagerDelegate::HandleCommand( + content::DevToolsAgentHost* agent_host, + base::DictionaryValue* command) { + return NULL; +} + +std::string CastDevToolsManagerDelegate::GetPageThumbnailData( const GURL& url) { + return ""; +} + +scoped_ptr<content::DevToolsTarget> +CastDevToolsManagerDelegate::CreateNewTarget(const GURL& url) { return scoped_ptr<content::DevToolsTarget>(); } -void CastDevToolsDelegate::EnumerateTargets(TargetCallback callback) { +void CastDevToolsManagerDelegate::EnumerateTargets(TargetCallback callback) { TargetList targets; - std::vector<content::WebContents*> wc_list = - content::DevToolsAgentHost::GetInspectableWebContents(); - for (std::vector<content::WebContents*>::iterator it = wc_list.begin(); - it != wc_list.end(); - ++it) { + content::DevToolsAgentHost::List agents = + content::DevToolsAgentHost::GetOrCreateAll(); + for (content::DevToolsAgentHost::List::iterator it = agents.begin(); + it != agents.end(); ++it) { targets.push_back(new Target(*it)); } callback.Run(targets); } -scoped_ptr<net::StreamListenSocket> -CastDevToolsDelegate::CreateSocketForTethering( - net::StreamListenSocket::Delegate* delegate, - std::string* name) { - return scoped_ptr<net::StreamListenSocket>(); -} - } // namespace shell } // namespace chromecast diff --git a/chromecast/shell/browser/devtools/cast_dev_tools_delegate.h b/chromecast/shell/browser/devtools/cast_dev_tools_delegate.h index e41e1a85bd..c180159105 100644 --- a/chromecast/shell/browser/devtools/cast_dev_tools_delegate.h +++ b/chromecast/shell/browser/devtools/cast_dev_tools_delegate.h @@ -6,12 +6,17 @@ #define CHROMECAST_SHELL_BROWSER_DEVTOOLS_CAST_DEV_TOOLS_DELEGATE_H_ #include "content/public/browser/devtools_http_handler_delegate.h" +#include "content/public/browser/devtools_manager_delegate.h" #include "net/socket/stream_listen_socket.h" namespace base { class FilePath; } +namespace content { +class BrowserContext; +} + namespace chromecast { namespace shell { @@ -24,10 +29,6 @@ class CastDevToolsDelegate : public content::DevToolsHttpHandlerDelegate { virtual std::string GetDiscoveryPageHTML() OVERRIDE; virtual bool BundlesFrontendResources() OVERRIDE; virtual base::FilePath GetDebugFrontendDir() OVERRIDE; - virtual std::string GetPageThumbnailData(const GURL& url) OVERRIDE; - virtual scoped_ptr<content::DevToolsTarget> CreateNewTarget( - const GURL& url) OVERRIDE; - virtual void EnumerateTargets(TargetCallback callback) OVERRIDE; virtual scoped_ptr<net::StreamListenSocket> CreateSocketForTethering( net::StreamListenSocket::Delegate* delegate, std::string* name) OVERRIDE; @@ -36,6 +37,30 @@ class CastDevToolsDelegate : public content::DevToolsHttpHandlerDelegate { DISALLOW_COPY_AND_ASSIGN(CastDevToolsDelegate); }; +class CastDevToolsManagerDelegate : public content::DevToolsManagerDelegate { + public: + CastDevToolsManagerDelegate(); + virtual ~CastDevToolsManagerDelegate(); + + // DevToolsManagerDelegate implementation. + virtual void Inspect( + content::BrowserContext* browser_context, + content::DevToolsAgentHost* agent_host) OVERRIDE {} + virtual void DevToolsAgentStateChanged( + content::DevToolsAgentHost* agent_host, + bool attached) OVERRIDE {} + virtual base::DictionaryValue* HandleCommand( + content::DevToolsAgentHost* agent_host, + base::DictionaryValue* command) OVERRIDE; + virtual scoped_ptr<content::DevToolsTarget> CreateNewTarget( + const GURL& url) OVERRIDE; + virtual void EnumerateTargets(TargetCallback callback) OVERRIDE; + virtual std::string GetPageThumbnailData(const GURL& url) OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(CastDevToolsManagerDelegate); +}; + } // namespace shell } // namespace chromecast diff --git a/chromecast/shell/browser/devtools/remote_debugging_server.cc b/chromecast/shell/browser/devtools/remote_debugging_server.cc index 076b06654e..f34af1e5a8 100644 --- a/chromecast/shell/browser/devtools/remote_debugging_server.cc +++ b/chromecast/shell/browser/devtools/remote_debugging_server.cc @@ -17,11 +17,11 @@ #include "content/public/browser/devtools_http_handler.h" #include "content/public/common/content_switches.h" #include "content/public/common/user_agent.h" -#include "net/socket/tcp_listen_socket.h" +#include "net/socket/tcp_server_socket.h" #if defined(OS_ANDROID) #include "content/public/browser/android/devtools_auth.h" -#include "net/socket/unix_domain_socket_posix.h" +#include "net/socket/unix_domain_server_socket_posix.h" #endif // defined(OS_ANDROID) namespace chromecast { @@ -29,13 +29,47 @@ namespace shell { namespace { -#if defined(OS_ANDROID) -const char kFrontEndURL[] = - "http://chrome-devtools-frontend.appspot.com/serve_rev/%s/devtools.html"; -#endif // defined(OS_ANDROID) const int kDefaultRemoteDebuggingPort = 9222; -net::StreamListenSocketFactory* CreateSocketFactory(int port) { +#if defined(OS_ANDROID) +class UnixDomainServerSocketFactory + : public content::DevToolsHttpHandler::ServerSocketFactory { + public: + explicit UnixDomainServerSocketFactory(const std::string& socket_name) + : content::DevToolsHttpHandler::ServerSocketFactory(socket_name, 0, 1) {} + + private: + // content::DevToolsHttpHandler::ServerSocketFactory. + virtual scoped_ptr<net::ServerSocket> Create() const OVERRIDE { + return scoped_ptr<net::ServerSocket>( + new net::UnixDomainServerSocket( + base::Bind(&content::CanUserConnectToDevTools), + true /* use_abstract_namespace */)); + } + + DISALLOW_COPY_AND_ASSIGN(UnixDomainServerSocketFactory); +}; +#else +class TCPServerSocketFactory + : public content::DevToolsHttpHandler::ServerSocketFactory { + public: + TCPServerSocketFactory(const std::string& address, int port, int backlog) + : content::DevToolsHttpHandler::ServerSocketFactory( + address, port, backlog) {} + + private: + // content::DevToolsHttpHandler::ServerSocketFactory. + virtual scoped_ptr<net::ServerSocket> Create() const OVERRIDE { + return scoped_ptr<net::ServerSocket>( + new net::TCPServerSocket(NULL, net::NetLog::Source())); + } + + DISALLOW_COPY_AND_ASSIGN(TCPServerSocketFactory); +}; +#endif + +scoped_ptr<content::DevToolsHttpHandler::ServerSocketFactory> +CreateSocketFactory(int port) { #if defined(OS_ANDROID) base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); std::string socket_name = "content_shell_devtools_remote"; @@ -43,19 +77,12 @@ net::StreamListenSocketFactory* CreateSocketFactory(int port) { socket_name = command_line->GetSwitchValueASCII( switches::kRemoteDebuggingSocketName); } - return new net::UnixDomainSocketWithAbstractNamespaceFactory( - socket_name, "", base::Bind(&content::CanUserConnectToDevTools)); + return scoped_ptr<content::DevToolsHttpHandler::ServerSocketFactory>( + new UnixDomainServerSocketFactory(socket_name)); #else - return new net::TCPListenSocketFactory("0.0.0.0", port); -#endif // defined(OS_ANDROID) -} - -std::string GetFrontendUrl() { -#if defined(OS_ANDROID) - return base::StringPrintf(kFrontEndURL, content::GetWebKitRevision().c_str()); -#else - return std::string(); -#endif // defined(OS_ANDROID) + return scoped_ptr<content::DevToolsHttpHandler::ServerSocketFactory>( + new TCPServerSocketFactory("0.0.0.0", port, 1)); +#endif } } // namespace @@ -105,7 +132,7 @@ void RemoteDebuggingServer::OnPortChanged() { if (port_ > 0) { devtools_http_handler_ = content::DevToolsHttpHandler::Start( CreateSocketFactory(port_), - GetFrontendUrl(), + std::string(), new CastDevToolsDelegate(), base::FilePath()); LOG(INFO) << "Devtools started: port=" << port_; diff --git a/chromecast/shell/browser/test/DEPS b/chromecast/shell/browser/test/DEPS new file mode 100644 index 0000000000..b969a40c30 --- /dev/null +++ b/chromecast/shell/browser/test/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+content/public/test", +] diff --git a/chromecast/shell/browser/test/OWNERS b/chromecast/shell/browser/test/OWNERS new file mode 100644 index 0000000000..23be33d4fc --- /dev/null +++ b/chromecast/shell/browser/test/OWNERS @@ -0,0 +1,4 @@ +# content/public/test OWNERS for review of Chromecast browser test code +jcivelli@chromium.org +phajdan.jr@chromium.org +sky@chromium.org diff --git a/chromecast/shell/browser/test/chromecast_browser_test.cc b/chromecast/shell/browser/test/chromecast_browser_test.cc new file mode 100644 index 0000000000..19250751e5 --- /dev/null +++ b/chromecast/shell/browser/test/chromecast_browser_test.cc @@ -0,0 +1,79 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chromecast/shell/browser/test/chromecast_browser_test.h" + +#include "base/command_line.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "chromecast/shell/browser/cast_browser_context.h" +#include "chromecast/shell/browser/cast_browser_process.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/render_process_host.h" +#include "content/public/browser/web_contents.h" +#include "content/public/test/browser_test_utils.h" +#include "content/public/test/test_navigation_observer.h" + +namespace chromecast { +namespace shell { + +ChromecastBrowserTest::ChromecastBrowserTest() + : setup_called_(false) { +} + +ChromecastBrowserTest::~ChromecastBrowserTest() { + CHECK(setup_called_) << "Overridden SetUp() did not call parent " + << "implementation, so test not run."; +} + +void ChromecastBrowserTest::SetUp() { + SetUpCommandLine(CommandLine::ForCurrentProcess()); + setup_called_ = true; + BrowserTestBase::SetUp(); +} + +void ChromecastBrowserTest::RunTestOnMainThreadLoop() { + // Pump startup related events. + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + base::RunLoop().RunUntilIdle(); + + SetUpOnMainThread(); + + RunTestOnMainThread(); + + TearDownOnMainThread(); + + for (content::RenderProcessHost::iterator i( + content::RenderProcessHost::AllHostsIterator()); + !i.IsAtEnd(); i.Advance()) { + i.GetCurrentValue()->FastShutdownIfPossible(); + } + + web_contents_.reset(); +} + +void ChromecastBrowserTest::NavigateToURL(content::WebContents* window, + const GURL& url) { + content::WaitForLoadStop(window); + content::TestNavigationObserver same_tab_observer(window, 1); + content::NavigationController::LoadURLParams params(url); + params.transition_type = ui::PageTransitionFromInt( + ui::PAGE_TRANSITION_TYPED | + ui::PAGE_TRANSITION_FROM_ADDRESS_BAR); + window->GetController().LoadURLWithParams(params); + same_tab_observer.Wait(); +} + +content::WebContents* ChromecastBrowserTest::CreateBrowser() { + content::WebContents::CreateParams create_params( + CastBrowserProcess::GetInstance()->browser_context(), + NULL); + create_params.routing_id = MSG_ROUTING_NONE; + create_params.initial_size = gfx::Size(1280, 720); + web_contents_.reset(content::WebContents::Create(create_params)); + return web_contents_.get(); +} + +} // namespace shell +} // namespace chromecast diff --git a/chromecast/shell/browser/test/chromecast_browser_test.h b/chromecast/shell/browser/test/chromecast_browser_test.h new file mode 100644 index 0000000000..da031e7901 --- /dev/null +++ b/chromecast/shell/browser/test/chromecast_browser_test.h @@ -0,0 +1,56 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROMECAST_SHELL_BROWSER_TEST_CHROMECAST_BROWSER_TEST_H_ +#define CHROMECAST_SHELL_BROWSER_TEST_CHROMECAST_BROWSER_TEST_H_ + +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "content/public/test/browser_test.h" +#include "content/public/test/browser_test_base.h" + +namespace content { +class WebContents; +} + +namespace chromecast { +namespace shell { + +// This test allows for running an entire browser-process lifecycle per unit +// test, using Chromecast's cast_shell. This starts up the shell, runs a test +// case, then shuts down the entire shell. +// Note that this process takes 7-10 seconds per test case on Chromecast, so +// fewer test cases with more assertions are preferable. +class ChromecastBrowserTest : public content::BrowserTestBase { + protected: + ChromecastBrowserTest(); + virtual ~ChromecastBrowserTest(); + + // testing::Test implementation: + virtual void SetUp() OVERRIDE; + + // BrowserTestBase implementation: + virtual void RunTestOnMainThreadLoop() OVERRIDE; + + protected: + void NavigateToURL(content::WebContents* window, const GURL& gurl); + + // Creates a new window and loads about:blank. + content::WebContents* CreateBrowser(); + + // Returns the window for the test. + content::WebContents* web_contents() const { return web_contents_.get(); } + + private: + scoped_ptr<content::WebContents> web_contents_; + + bool setup_called_; + + DISALLOW_COPY_AND_ASSIGN(ChromecastBrowserTest); +}; + +} // namespace shell +} // namespace chromecast + +#endif // CHROMECAST_SHELL_BROWSER_TEST_CHROMECAST_BROWSER_TEST_H_ diff --git a/chromecast/shell/browser/test/chromecast_browser_test_runner.cc b/chromecast/shell/browser/test/chromecast_browser_test_runner.cc new file mode 100644 index 0000000000..baad234507 --- /dev/null +++ b/chromecast/shell/browser/test/chromecast_browser_test_runner.cc @@ -0,0 +1,68 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/command_line.h" +#include "base/macros.h" +#include "base/sys_info.h" +#include "chromecast/shell/app/cast_main_delegate.h" +#include "content/public/common/content_switches.h" +#include "content/public/test/content_test_suite_base.h" +#include "content/public/test/test_launcher.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace chromecast { +namespace shell { + +namespace { + +const char kTestTypeBrowser[] = "browser"; + +class BrowserTestSuite : public content::ContentTestSuiteBase { + public: + BrowserTestSuite(int argc, char** argv) + : content::ContentTestSuiteBase(argc, argv) { + } + virtual ~BrowserTestSuite() { + } + + private: + DISALLOW_COPY_AND_ASSIGN(BrowserTestSuite); +}; + +class ChromecastTestLauncherDelegate : public content::TestLauncherDelegate { + public: + ChromecastTestLauncherDelegate() {} + virtual ~ChromecastTestLauncherDelegate() {} + + virtual int RunTestSuite(int argc, char** argv) OVERRIDE { + return BrowserTestSuite(argc, argv).Run(); + } + + virtual bool AdjustChildProcessCommandLine( + base::CommandLine* command_line, + const base::FilePath& temp_data_dir) OVERRIDE { + // TODO(gunsch): handle temp_data_dir + command_line->AppendSwitchASCII(switches::kTestType, kTestTypeBrowser); + return true; + } + + protected: + virtual content::ContentMainDelegate* CreateContentMainDelegate() OVERRIDE { + return new CastMainDelegate(); + } + + private: + DISALLOW_COPY_AND_ASSIGN(ChromecastTestLauncherDelegate); +}; + +} // namespace + +} // namespace shell +} // namespace chromecast + +int main(int argc, char** argv) { + int default_jobs = std::max(1, base::SysInfo::NumberOfProcessors() / 2); + chromecast::shell::ChromecastTestLauncherDelegate launcher_delegate; + return LaunchTests(&launcher_delegate, default_jobs, argc, argv); +} diff --git a/chromecast/shell/browser/test/chromecast_shell_browser_test.cc b/chromecast/shell/browser/test/chromecast_shell_browser_test.cc new file mode 100644 index 0000000000..3016dcd0c2 --- /dev/null +++ b/chromecast/shell/browser/test/chromecast_shell_browser_test.cc @@ -0,0 +1,36 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/macros.h" +#include "chromecast/shell/browser/test/chromecast_browser_test.h" +#include "url/gurl.h" +#include "url/url_constants.h" + +namespace chromecast { +namespace shell { + +class ChromecastShellBrowserTest : public ChromecastBrowserTest { + public: + ChromecastShellBrowserTest() : url_(url::kAboutBlankURL) {} + + virtual void SetUpOnMainThread() OVERRIDE { + CreateBrowser(); + NavigateToURL(web_contents(), url_); + } + + private: + const GURL url_; + + DISALLOW_COPY_AND_ASSIGN(ChromecastShellBrowserTest); +}; + +IN_PROC_BROWSER_TEST_F(ChromecastShellBrowserTest, EmptyTest) { + // Run an entire browser lifecycle to ensure nothing breaks. + // TODO(gunsch): Remove this test case once there are actual assertions to + // test in a ChromecastBrowserTest instance. + EXPECT_TRUE(true); +} + +} // namespace shell +} // namespace chromecast diff --git a/chromecast/shell/browser/url_request_context_factory.cc b/chromecast/shell/browser/url_request_context_factory.cc index 03b5d9ddf6..e61bc6a914 100644 --- a/chromecast/shell/browser/url_request_context_factory.cc +++ b/chromecast/shell/browser/url_request_context_factory.cc @@ -30,6 +30,7 @@ #include "net/ssl/default_channel_id_store.h" #include "net/ssl/ssl_config_service_defaults.h" #include "net/url_request/data_protocol_handler.h" +#include "net/url_request/file_protocol_handler.h" #include "net/url_request/url_request_context.h" #include "net/url_request/url_request_context_getter.h" #include "net/url_request/url_request_intercepting_job_factory.h" @@ -63,8 +64,10 @@ class URLRequestContextFactory::URLRequestContextGetter request_context_.reset(factory_->CreateMediaRequestContext()); } else { request_context_.reset(factory_->CreateSystemRequestContext()); +#if defined(USE_NSS) // Set request context used by NSS for Crl requests. net::SetURLRequestContextForNSSHttpIO(request_context_.get()); +#endif // defined(USE_NSS) } } return request_context_.get(); @@ -144,13 +147,22 @@ void URLRequestContextFactory::InitializeOnUIThread() { // because it registers itself to pref notification observer which is not // thread safe. http_user_agent_settings_.reset(new CastHttpUserAgentSettings()); + + // Proxy config service should be initialized in UI thread, since + // ProxyConfigServiceDelegate on Android expects UI thread. + proxy_config_service_.reset(net::ProxyService::CreateSystemProxyConfigService( + content::BrowserThread::GetMessageLoopProxyForThread( + content::BrowserThread::IO), + content::BrowserThread::GetMessageLoopProxyForThread( + content::BrowserThread::FILE))); } net::URLRequestContextGetter* URLRequestContextFactory::CreateMainGetter( content::BrowserContext* browser_context, content::ProtocolHandlerMap* protocol_handlers, content::URLRequestInterceptorScopedVector request_interceptors) { - DCHECK(!main_getter_) << "Main URLRequestContextGetter already initialized"; + DCHECK(!main_getter_.get()) + << "Main URLRequestContextGetter already initialized"; main_getter_ = new MainURLRequestContextGetter(this, browser_context, protocol_handlers, @@ -159,19 +171,19 @@ net::URLRequestContextGetter* URLRequestContextFactory::CreateMainGetter( } net::URLRequestContextGetter* URLRequestContextFactory::GetMainGetter() { - CHECK(main_getter_); + CHECK(main_getter_.get()); return main_getter_.get(); } net::URLRequestContextGetter* URLRequestContextFactory::GetSystemGetter() { - if (!system_getter_) { + if (!system_getter_.get()) { system_getter_ = new URLRequestContextGetter(this, false); } return system_getter_.get(); } net::URLRequestContextGetter* URLRequestContextFactory::GetMediaGetter() { - if (!media_getter_) { + if (!media_getter_.get()) { media_getter_ = new URLRequestContextGetter(this, true); } return media_getter_.get(); @@ -201,13 +213,7 @@ void URLRequestContextFactory::InitializeSystemContextDependencies() { http_server_properties_.reset(new net::HttpServerPropertiesImpl); proxy_service_.reset(net::ProxyService::CreateUsingSystemProxyResolver( - net::ProxyService::CreateSystemProxyConfigService( - content::BrowserThread::GetMessageLoopProxyForThread( - content::BrowserThread::IO).get(), - content::BrowserThread::UnsafeGetMessageLoopForThread( - content::BrowserThread::FILE)), - 0, - NULL)); + proxy_config_service_.release(), 0, NULL)); system_dependencies_initialized_ = true; } @@ -235,6 +241,15 @@ void URLRequestContextFactory::InitializeMainContextDependencies( url::kDataScheme, new net::DataProtocolHandler); DCHECK(set_protocol); +#if defined(OS_ANDROID) + set_protocol = job_factory->SetProtocolHandler( + url::kFileScheme, + new net::FileProtocolHandler( + content::BrowserThread::GetBlockingPool()-> + GetTaskRunnerWithShutdownBehavior( + base::SequencedWorkerPool::SKIP_ON_SHUTDOWN))); + DCHECK(set_protocol); +#endif // defined(OS_ANDROID) // Set up interceptors in the reverse order. scoped_ptr<net::URLRequestJobFactory> top_job_factory = @@ -288,6 +303,7 @@ net::URLRequestContext* URLRequestContextFactory::CreateSystemRequestContext() { PopulateNetworkSessionParams(false, &system_params); system_transaction_factory_.reset(new net::HttpNetworkLayer( new net::HttpNetworkSession(system_params))); + system_job_factory_.reset(new net::URLRequestJobFactoryImpl()); net::URLRequestContext* system_context = new net::URLRequestContext(); system_context->set_host_resolver(host_resolver_.get()); @@ -305,6 +321,7 @@ net::URLRequestContext* URLRequestContextFactory::CreateSystemRequestContext() { system_transaction_factory_.get()); system_context->set_http_user_agent_settings( http_user_agent_settings_.get()); + system_context->set_job_factory(system_job_factory_.get()); system_context->set_cookie_store( content::CreateCookieStore(content::CookieStoreConfig())); return system_context; @@ -312,7 +329,7 @@ net::URLRequestContext* URLRequestContextFactory::CreateSystemRequestContext() { net::URLRequestContext* URLRequestContextFactory::CreateMediaRequestContext() { DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); - DCHECK(main_getter_) + DCHECK(main_getter_.get()) << "Getting MediaRequestContext before MainRequestContext"; net::URLRequestContext* main_context = main_getter_->GetURLRequestContext(); diff --git a/chromecast/shell/browser/url_request_context_factory.h b/chromecast/shell/browser/url_request_context_factory.h index 03d062da42..8de7437b93 100644 --- a/chromecast/shell/browser/url_request_context_factory.h +++ b/chromecast/shell/browser/url_request_context_factory.h @@ -11,6 +11,7 @@ namespace net { class HttpTransactionFactory; class HttpUserAgentSettings; +class ProxyConfigService; class URLRequestJobFactory; } // namespace net @@ -83,11 +84,13 @@ class URLRequestContextFactory { scoped_ptr<net::CertVerifier> cert_verifier_; scoped_refptr<net::SSLConfigService> ssl_config_service_; scoped_ptr<net::TransportSecurityState> transport_security_state_; + scoped_ptr<net::ProxyConfigService> proxy_config_service_; scoped_ptr<net::ProxyService> proxy_service_; scoped_ptr<net::HttpAuthHandlerFactory> http_auth_handler_factory_; scoped_ptr<net::HttpServerProperties> http_server_properties_; scoped_ptr<net::HttpUserAgentSettings> http_user_agent_settings_; scoped_ptr<net::HttpTransactionFactory> system_transaction_factory_; + scoped_ptr<net::URLRequestJobFactory> system_job_factory_; bool main_dependencies_initialized_; scoped_ptr<net::HttpTransactionFactory> main_transaction_factory_; diff --git a/chromecast/shell/common/cast_content_client.cc b/chromecast/shell/common/cast_content_client.cc index 65d50ec079..3b7eea27e6 100644 --- a/chromecast/shell/common/cast_content_client.cc +++ b/chromecast/shell/common/cast_content_client.cc @@ -14,7 +14,8 @@ namespace shell { std::string GetUserAgent() { std::string product = "Chrome/" PRODUCT_VERSION; - return content::BuildUserAgentFromProduct(product) + " CrKey"; + return content::BuildUserAgentFromProduct(product) + + " CrKey" CAST_BUILD_REVISION; } CastContentClient::~CastContentClient() { diff --git a/chromecast/shell/renderer/DEPS b/chromecast/shell/renderer/DEPS index ad0391cf78..161949b6be 100644 --- a/chromecast/shell/renderer/DEPS +++ b/chromecast/shell/renderer/DEPS @@ -1,4 +1,5 @@ include_rules = [ + "+components/cdm/renderer", "+content/public/renderer", "+third_party/WebKit/public/platform", "+third_party/WebKit/public/web", diff --git a/chromecast/shell/renderer/cast_content_renderer_client.cc b/chromecast/shell/renderer/cast_content_renderer_client.cc index e07e227929..59e6c1f469 100644 --- a/chromecast/shell/renderer/cast_content_renderer_client.cc +++ b/chromecast/shell/renderer/cast_content_renderer_client.cc @@ -8,10 +8,12 @@ #include "base/command_line.h" #include "base/memory/memory_pressure_listener.h" +#include "chromecast/shell/renderer/key_systems_cast.h" #include "content/public/common/content_switches.h" #include "content/public/renderer/render_view.h" #include "crypto/nss_util.h" #include "third_party/WebKit/public/platform/WebColor.h" +#include "third_party/WebKit/public/web/WebSettings.h" #include "third_party/WebKit/public/web/WebView.h" namespace chromecast { @@ -41,11 +43,14 @@ void CastContentRendererClient::RenderViewCreated( blink::WebView* webview = render_view->GetWebView(); if (webview) { webview->setBaseBackgroundColor(kColorBlack); + webview->settings()->setShrinksViewportContentToFit(false); } } void CastContentRendererClient::AddKeySystems( std::vector<content::KeySystemInfo>* key_systems) { + AddChromecastKeySystems(key_systems); + AddChromecastPlatformKeySystems(key_systems); } } // namespace shell diff --git a/chromecast/shell/renderer/key_systems_cast.cc b/chromecast/shell/renderer/key_systems_cast.cc new file mode 100644 index 0000000000..ceffef5f0c --- /dev/null +++ b/chromecast/shell/renderer/key_systems_cast.cc @@ -0,0 +1,43 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chromecast/shell/renderer/key_systems_cast.h" + +#include <string> + +#include "base/command_line.h" +#include "base/logging.h" +#include "chromecast/media/base/key_systems_common.h" +#include "components/cdm/renderer/widevine_key_systems.h" +#include "content/public/common/eme_codec.h" + +#include "widevine_cdm_version.h" // In SHARED_INTERMEDIATE_DIR. + +namespace chromecast { +namespace shell { + +void AddKeySystemWithCodecs( + const std::string& key_system_name, + std::vector<content::KeySystemInfo>* concrete_key_systems) { + content::KeySystemInfo info(key_system_name); + info.supported_codecs = content::EME_CODEC_MP4_ALL; + concrete_key_systems->push_back(info); +} + +void AddChromecastKeySystems( + std::vector<content::KeySystemInfo>* key_systems_info) { +#if defined(WIDEVINE_CDM_AVAILABLE) + AddWidevineWithCodecs(cdm::WIDEVINE, + content::EME_CODEC_MP4_ALL, + key_systems_info); +#endif + +#if defined(PLAYREADY_CDM_AVAILABLE) + AddKeySystemWithCodecs(media::kChromecastPlayreadyKeySystem, + key_systems_info); +#endif +} + +} // namespace shell +} // namespace chromecast diff --git a/chromecast/shell/renderer/key_systems_cast.h b/chromecast/shell/renderer/key_systems_cast.h new file mode 100644 index 0000000000..c291344a1b --- /dev/null +++ b/chromecast/shell/renderer/key_systems_cast.h @@ -0,0 +1,30 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROMECAST_SHELL_RENDERER_KEY_SYSTEMS_CAST_H_ +#define CHROMECAST_SHELL_RENDERER_KEY_SYSTEMS_CAST_H_ + +#include <vector> + +#include "content/public/renderer/key_system_info.h" + +namespace chromecast { +namespace shell { + +// Adds a single key system by name. +void AddKeySystemWithCodecs( + const std::string& key_system_name, + std::vector<content::KeySystemInfo>* concrete_key_systems); + +void AddChromecastKeySystems( + std::vector<content::KeySystemInfo>* key_systems_info); + +// TODO(gunsch): Remove when prefixed EME is removed. +void AddChromecastPlatformKeySystems( + std::vector<content::KeySystemInfo>* key_systems_info); + +} // namespace shell +} // namespace chromecast + +#endif // CHROMECAST_SHELL_RENDERER_KEY_SYSTEMS_CAST_H_ diff --git a/chromecast/shell/renderer/key_systems_cast_simple.cc b/chromecast/shell/renderer/key_systems_cast_simple.cc new file mode 100644 index 0000000000..752669f07a --- /dev/null +++ b/chromecast/shell/renderer/key_systems_cast_simple.cc @@ -0,0 +1,16 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chromecast/shell/renderer/key_systems_cast.h" + +namespace chromecast { +namespace shell { + +void AddChromecastPlatformKeySystems( + std::vector<content::KeySystemInfo>* key_systems_info) { + // Intentional no-op for public build. +} + +} // namespace shell +} // namespace chromecast |